开发一二三

1.简单实现复制功能

navigator.clipboard.writeText(url)

2.实现简易蒙层

<template>
  <div id="mask" ref="mask" v-if="passwordDialog">
  <n-card class="absolute"    style="width: 30vw;height: 25vh;top: 50%;left: 50%;transform: translate(-50%, -50%);">
    <div>密码保护</div>
    <n-divider></n-divider>
    <n-input placeholder="访问密码" v-model:value="password" type="password">
    </n-input>
    <div class="flex justify-center my-6">
      <!-- <n-button class="mx-3" style="margin:10px 20px 10px 10px" @click="goBack">退出</n-button> -->
      <n-button class="mx-3" type="primary" @click="checkPassword" style="margin:10px 0px 10px 20px">确定</n-button>
    </div>
  </n-card>
  </div>
</template>

<script>
import {onMounted,ref} from 'vue'
// import { useAuth } from '@/store/services/auth'
// import { usePublishStore } from "@/store/modules/publishStore/publishStore";
// import { useNotification } from 'naive-ui'

export default {
  name: 'PasswordCheck',
  setup() {
    let mask=ref(null)
    // const publishStore = usePublishStore();
    // const auth = useAuth();
    // const notification = useNotification();
    return {
      mask,
      // auth,
      // publishStore,
      // notification,
    };
  },
  props: {
    publishId: {
      type: String,
      required: true,
    },
    experienceId: {
      type: String,
      required: true,
    },
    checkMode: {
      type: String,
      default: 'play',
    },
    passwordDialog:{
      type: Boolean,
      default: false,
    }
  },
  data() {
    return {
      password: '',
    };
  },
  methods: {
    async checkPassword() {
      // todo: call api
      // let loginResponse = undefined;
      // try {
      //   // loginResponse = await this.auth.authenticate({
      //   //   strategy: 'publish',
      //   //   publishId: this.publishId,
      //   //   userInput: this.password,
      //   // });
      // } catch (e) {
      //   // this.notification['error']({
      //   //   content: '验证失败',
      //   //   placement: 'top',
      //   //   duration: 2500,
      //   //   keepAliveOnHover: true
      //   // });
      //   return;
      // }
      // if (!loginResponse || loginResponse.error) {
      //   // this.notification['error']({
      //   //   content: '验证失败',
      //   //   placement: 'top',
      //   //   duration: 2500,
      //   //   keepAliveOnHover: true
      //   // });
      //   return;
      // }
      // this.notification['success']({
      //   content: '验证成功',
      //   placement: 'top',
      //   duration: 2500,
      //   keepAliveOnHover: true
      // });
      // this.verified = true;
      // this.$emit('verified', true, this.checkMode);
     this.$emit('checkPassword',false,this.password)
    },
    goBack(){
      this.$emit('closePasswordDialog',false)
    }

  },
  // onMounted(() => {
  //   // this.$refs.mask.style.height = window.document.getElementById('app').clientHeight + 'px'
  // })
}
</script>

<style scoped>
#mask{
  width: 100%;
  height: 100%;
  /* opacity: 0.8; */
  background-color: rgba(240,240,240,1);
  bottom: 0;
  left: 0;
  position: fixed;
  z-index: 998;
}
.mask_img{
  width: 316px;
  height: 200px;
  z-index: 999;
  position: fixed;
  right: 0px;
}
</style>

3.实现随机密码

// 随机生成密码
function randPassword(len?:number, passwordArr?:Array<string>) {
      let length = len || 8;
     // 密码串----默认 大写字母 小写字母 数字
    let  passwordArray = passwordArr || ['ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz', '1234567890'];
     let password = '';
     // 随机生成开始字符串
     let startIndex:any = parseInt(Math.random() * (length));
     let randIndex = [];
     for (let i = 0; i < length; i++) {
         // 创建数组,用于取随机位置  [0,1,2,3,4,5,....]
         randIndex.push(i);
     }
     for (let i = 0; i < length; i++) {
         // 根据随机数组生成随机位置
         let r = parseInt(Math.random() * (randIndex.length));
         let num = randIndex[r] + startIndex;
         // 根据随机值取余数
         let randRemainder = num % passwordArray.length;
         // 当前密码串【大写字母,小写字母,数字等】
         let currentPassword = passwordArray[randRemainder];
         // 根据当前密码串长度取随机数
         let index = parseInt(Math.random() * (currentPassword.length));
         // 获取随机字符串
         let str = currentPassword.substr(index, 1);
         // 删除随机数组中已经使用的值
         randIndex.splice(r, 1);
         password += str;
     }
     return password;   
 }

4.实现弹窗显示的几种方式

(1)在父组件里使用子组件的时候使用v-if,可以实现初始化时候子组件不加载,触发才展示,初始化生命周期,

(2),在子组件里面控制弹框显隐,使用defineExpose向父组件暴露自己的方法

 父组件页面使用ref获取子组件dom,调用暴露出来的子组件的方法

 (3)子组件控制显影,使用父传子,子传父(笨老方法)

5.下载

const downloadExperience=(data:any)=> {
  console.log('下载!!!',data);
  const url = data&&data.GLB_file?data.GLB_file.objectStorageDownloadUrl:""
  //此处返回的blob对象
  // let fileName = url.slice(url.lastIndexOf('/') + 1);
  
  let fileName = data&&data.GLB_file&&data.GLB_file.objectStorageDownloadUrl?data.GLB_file.name:"";
  console.log('fileName',fileName);
  
  var fileURL = window.URL.createObjectURL(new Blob([url]));
  console.log('fileURL',fileURL)
  var fileLink = document.createElement('a');
  fileLink.href = fileURL;
  fileLink.setAttribute('download',fileName);
  document.body.appendChild(fileLink);
  fileLink.click();
  
}

6,预览.csv文件(和excel文件类似,但是只能插入一个表,excel可以插入多个表)

// 预览
const preview=()=> {
  console.log('preview',fileUrl.value);
  let xhr = new XMLHttpRequest();
  xhr.open("get", fileUrl.value, true);
  xhr.responseType = "blob";
  let that = getCurrentInstance();
  console.log('nnnnnnnnnnnnnnnnnnnnnnnnn');
  xhr.onload = function (e) {
    if (xhr.status === 200) {
      let fileReader = new FileReader();
      fileReader.readAsArrayBuffer(xhr.response);
      fileReader.onload = (res) => {
        console.log('转换成功!');
        let stream = res.target.result;
        let workbook = XLSX.read(stream,{ type: 'binary'});//解析二进制格式数据
        console.log('二进制数据的解析:')
        console.log(workbook)
        let worksheet = workbook.Sheets[workbook.SheetNames[0]];//获取第一个Sheet

        const sheet2JSONOpts = {    
          defval: ''
        }
        let result = XLSX.utils.sheet_to_json(worksheet,sheet2JSONOpts);//json数据格式
        console.log('worksheet',worksheet,'result',result);
        workResult.value=result as any
        previewBobShow.value=true;
      };
    }
  };
  xhr.send();
}

7.实现项目国际化

参考链接https://blog.csdn.net/qq_41809113/article/details/1266796

8.实现树结构的增删改查(以navie ui为例)

树结构组件

modifyGroup是新增弹框

<template>
  <div class=" h-full">
    <n-card class="tv-main h-full p-2">
      <div class="title flex items-center" style="flex-wrap: nowrap;">
        <n-button text>{{ $t('Team_list') }}</n-button>
        <n-input class="ml-2" v-model:value="filterName" style="border-radius: 6px;"></n-input>
      </div>
      <div class="content">
        <p class="mt-3 cursor-pointer" @click="getAll" :class="isAll ? 'text-green-500' : ''">{{ $t('All') }}</p>
        <!-- <p
          class="mt-2 cursor-pointer"
          :class="t.id === currentTeam.id && !isAll ? 'text-green-500' : ''"
          v-for="t in teamData"
          :key="t.id"
          @click="selectData(t)"
        >
          {{ t.name }}
        </p> -->

        <n-tree
          draggable
          block-line
          key-field="id"
          label-field="name"
          :data="teamData"
          expand-on-click
          :checked-keys="checkedKeys"
          :check-strategy="checkStrategy"
          :allow-checking-not-loaded="cascade"
          :cascade="cascade"
          :expanded-keys="expandedKeys"
          :on-load="handleLoad"
          :node-props="nodeProps"
          :render-suffix="renderSuffix"
          @drop="handleDrop"
          @update:checked-keys="handleCheckedKeysChange"
          @update:expanded-keys="handleExpandedKeysChange"
        />
      </div>
      <div v-if="editTeamShow" @click="addTeam">
       <n-icon>
          <Add />
        </n-icon>
        <span>新增团队</span>
      </div>
      <template #footer>
       <n-button class="edit-group-btn" v-if="!editTeamShow" @click="editGroup">编辑团队</n-button>
       <div v-if="editTeamShow" style="display:flex; justify-content:space-between ;">
        <n-button type="primary" class="operationBtn">保存</n-button>
        <n-button ghost class="operationBtn" @click="editTeamShow=false">取消</n-button>
       </div>
    </template>
    </n-card>
  </div>
  <ModifyGroup ref="editTeamRef" :editTeam="editTeam" :teamData="teamData" @ensure="ensure" @cancel="cancel"></ModifyGroup>
</template>

<script setup lang="ts">
import { useTeamsGroupsStore } from '@/store/modules/teamsStore/teamsGroupsStore'
import { EditGroup } from '@/views/chart/ContentEdit/components/EditGroup'
import { computed, onMounted, reactive, ref, watch,h,} from 'vue'
import {NButton,NIcon,NText,NInput} from 'naive-ui'
import ModifyGroup from './modifyGroup.vue'
import { icon } from '@/plugins'
import { Add } from '@vicons/ionicons5'
const teamsGroupsStore = useTeamsGroupsStore()
const {
  TrashIcon
} = icon.ionicons5

const $t = window['$t']

const filterName = ref('')
const isAll = ref(true)
const editTeamRef=ref()
const checkedKeys = ref<string[]>([])
const checkStrategy = ref<'all' | 'parent' | 'child'>('all')
const expandedKeys = ref<string[]>([])
let editTeamShow=ref(false)
let dbclickShow =ref(false)
let editTeam=ref({})
let teamData = ref(
  [
    {
      createdAt: "2023-06-14T17:17:02.720Z",
      deletedAt: null,
      description: "超级管理员团队",
      id: "1",
      name: "超级管理员团队",
      parentId: null,
      updatedAt: "2023-06-20T18:37:45.218Z",
      childern:[
        {
          createdAt: "2023-06-14T17:17:02.720Z",
          deletedAt: null,
          description: "超级管理员团队1",
          id: "24b38e21-17fa-4d79-a79a-7ed69e8d2255",
          name: "超级管理员团队1",
          parentId: '1',
          updatedAt: "2023-06-20T18:37:45.218Z",
        }
      ]
    },
     {
      createdAt: "2023-06-14T17:17:02.720Z",
      deletedAt: null,
      description: "管理员团队",
      id: "24b38e21-17fa-4d79-a79a-7ed69e8d2255",
      name: "管理员团队",
      parentId: null,
      updatedAt: "2023-06-20T18:37:45.218Z",
    }
  ]
)


const cascade = ref(true)
// const teamData = computed(() => {
//   return teamsGroupsStore.allData
// })
const selectData = (data: any) => {
  console.log('selectData',data);
  
  isAll.value = false
  teamsGroupsStore.setCurrentData(data)
}
const getAll = () => {
  isAll.value = true
  teamsGroupsStore.setCurrentData({})
  teamData.value.length = 0
  teamData.value.push(...teamsGroupsStore.allData)
}

const currentTeam = computed(() => {
  return teamsGroupsStore.currentData
})

const editGroup=()=> {
  editTeamShow.value=true
}
watch(
  () => teamsGroupsStore.currentData,
  val => {
    if (val.id) {
      isAll.value = false
    } else {
      isAll.value = true
    }
  },
  { immediate: true }
)
watch(
  () => filterName.value,
  async val => {
    await teamsGroupsStore.loadData({
      name: {
        $like: '%' + filterName.value + '%'
      }
    })
    teamData.value.length = 0
    teamData.value.push(...teamsGroupsStore.filterData)
  }
)
watch(
  () => teamsGroupsStore.allData,
  val => {
    teamData.value.length = 0
    teamData.value.push(...teamsGroupsStore.allData)
  }
)
// watch(editTeamShow,(newValue,oldValue)=> {
//   if(newValue){
//     renderSuffix()
//   }
// },{})
const renderSuffix=(info :any)=> {
  if(editTeamShow.value){
    return  h(NIcon, {onClick: () => deleteGroupItem(info.option.id)}, { default: () => h(TrashIcon) })
  }else {
    return null
  }
 
}
const handleDrop=()=>{

}
const nodeProps=(data :any)=> {
   return {
    ondblclick(){
      if(editTeamShow.value){
        editTeamRef.value.show('edit')
        // dbclickShow.value=true
        editTeam.value=data.option
      }else {
         editTeam.value=data.option={}
      }
      console.log('option',data.option,' editTeam.value', editTeam.value)
    },
    onClick(){
      selectData(data.option)
    }
   }
}
const addTeam=()=> {
   editTeamRef.value.show('add')
}
const ensure=async()=> {
  //  dbclickShow.value=false
   await teamsGroupsStore.loadData()

}
const cancel=()=> {
  // dbclickShow.value=false
}
const deleteGroupItem=async (data)=> {
    console.log('删除',data);
    const result = await teamsGroupsStore.remove(data)
    console.log('deleteGroupItem',result);
    await teamsGroupsStore.loadData()

}
const handleLoad = (node: TreeOption) => {
  console.log(node, 'node')
  // let params = {
  //   applicationId: node.applicationId,
  //   parentId: node.id
  // }
  // if (node.level > 0) {
  //   params.parentId = node.parentId
  // }
  // return commonStore.getCurrentToolChildren(params).then(res => {
  //   console.log('res',res,'params',params);
    
  //   const level = node['level'] + 1
  //   node.children = res.map(i => ({
  //     ...i,
  //     label: i.name,
  //     level,
  //     key: i.id,
  //     isLeaf: i.content && i.content.isLeaf === true ? true : false,
  //     parentId: level > 0 ? node.id : ''
  //   }))
  // })
}
const handleCheckedKeysChange = (keys: string[]) => {
  console.log(keys)
  checkedKeys.value = keys

}
const handleExpandedKeysChange = (keys: string[]) => {
  console.log(keys)
  expandedKeys.value = keys
}
// 一维数组转换为树结构
const arrayToTree = (list) => {
  let obj = {}
    let res = []
    for(let item of list){
        obj[item.id] = item
    }
    console.log('obj',obj);
    
    for(let item of list){
        if(obj[item.parentId]){
          // console.log('obj[item.parentId]',obj[item.parentId]);
          
            (obj[item.parentId].children || (obj[item.parentId].children = [])).push(item)
            let index = res.findIndex(e=>e.id=== obj[item.parentId].id)
            if(index){
              res.splice(index,1)
              res.push(obj[item.parentId])
            }
        }else{
            res.push(item)
        }
    }
    return res
};
onMounted(async() => {
  await teamsGroupsStore.loadData()
  teamData.value.length = 0
  const data= [...teamsGroupsStore.allData]
  if (teamData.value.length) {
    const data =teamData.value
    selectData(data[0])
  }
  teamData.value=data
  console.log('data',data,'teamsGroupsStore.allData',teamsGroupsStore.allData);
  
})
</script>

<style lang="scss" scoped>
@include TV(project) {
  .content-top {
    top: $--header-height;
    margin-top: 1px;
  }
}
:deep(.n-card > .n-card__content, .n-card > .n-card__footer){
  /* padding: 0; */
}
</style>

注意!!!!(删除图标的点击方法必须写箭头函数,否则会出问题)

9.实现项目换皮肤

页面效果展示

10.接口超时

(演示的项目接口是用feathers传输数据的,可参考feathersjs的安装和使用-CSDN博客

了解了之后接下来进入正题

获取数据处理之后,存在响应式数据里面,因为数据是响应式,为了优化效果,可以加些东西,比如我这里需要超时数据的是表单,那么我会在表单项下面加一行红字提示,一旦接受到数据,红字提示就会消失,因为数据是响应式,很简单的方法

11.外部excel文件实现字段匹配,导入到页面表格

先看例子效果

下面来展示如何实现该功能

外部菜单以及头部按钮组件,表格页面切换的文件

<template>
  <div class="data-table">
    <div class="header" v-if="props.tableinfo.showTitle">
      <div class="title">
        <img src="~img/right-arrow.png" alt="" class="mr-2" />
        <span>{{ props.tableinfo.title }}</span>
      </div>
      <div class="menu">
        <template v-for="item of props.tableinfo.menu">
          <el-tooltip
            v-if="item !== 'imports'"
            class="box-item"
            effect="dark"
            :content="getContent(item)"
          >
            <el-button
              type="primary"
              :loading="loadingStatus[item]"
              :icon="getIcon(item)"
              @click="handleButtonClicked(item)"
            ></el-button>
          </el-tooltip>
          <el-tooltip v-else class="box-item" effect="dark" content="导入">
            <el-button
              type="primary"
              :disabled="loadingStatus.imports"
              :icon="FolderOpened"
              @click="openExcelModal"
            ></el-button>
          </el-tooltip>
        </template>
      </div>
    </div>
    <div class="content">
      <router-view v-slot="{ Component }">
        <keep-alive>
          <component ref="content" :is="Component"></component>
        </keep-alive>
      </router-view>
    </div>
  </div>
  <ExcelDataImportDialog
    ref="excelDialog"
    :tableHead="content?.columnList"
    :selectItem="content?.activeType"
    :title="props.tableinfo.title"
    @update:value="refreshData"
  ></ExcelDataImportDialog>
</template>

<script setup lang="ts">
import {
  CirclePlus,
  Delete,
  DocumentChecked,
  FolderOpened,
  Refresh,
  Search,
  Download,
} from '@element-plus/icons-vue'
import { watch, ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import ExcelDataImportDialog from './TableComponent/ExcelDataImportDialog.vue'

const router = useRouter()
const props = defineProps<{ tableinfo: DATAMANAGETREE }>()

const content = ref()

let excelDialog = ref<typeof ExcelDataImportDialog>()
const loadingStatus = reactive({
  add: false,
  del: false,
  update: false,
  search: false,
  refresh: false,
  imports: false,
  exports: false,
})

watch(
  () => props.tableinfo,
  (value) => {
    router.push('/datamanage/' + value.index)
  },
  {
    immediate: true,
  }
)

const getIcon = (type: MENUOPERATION) => {
  switch (type) {
    case 'add':
      return CirclePlus
    case 'del':
      return Delete
    case 'update':
      return DocumentChecked
    case 'search':
      return Search
    case 'refresh':
      return Refresh
    case 'exports':
      return Download
  }
}

const getContent = (type: MENUOPERATION) => {
  switch (type) {
    case 'add':
      return '新增'
    case 'del':
      return '删除'
    case 'update':
      return '保存'
    case 'search':
      return '查询'
    case 'refresh':
      return '刷新'
    case 'imports':
      return '导入'
    case 'exports':
      return '导出'
  }
}

const handleButtonClicked = async (type: MENUOPERATION, data?: OBJ | OBJ[]) => {
  // console.log('handleButtonClicked',type);
  try {
    loadingStatus[type] = true
    await content.value[type](data)
  } catch (error) {
    console.error('未暴露的操作: ', type)
    console.error('error: ', error)
  } finally {
    loadingStatus[type] = false
  }
}

const openExcelModal = () => {
  excelDialog.value?.openExcelDialog()
}

const refreshData = () => {
  if (props.tableinfo.title === '井眼轨迹') {
    // console.log('xxxxxxxxxxxxxxxxxx', content.value.activeType);
    if (content.value.activeType === '实钻轨迹') {
      content.value.getDataList('Survey')
    } else {
      content.value.getDataList('Design')
    }
  } else if (props.tableinfo.title === '地层岩性') {
    if (content.value.activeType === '实际岩性') {
      content.value.getDataList('Survey')
    } else {
      content.value.getDataList('Design')
    }
  } else {
    content.value.getDataList()
  }
}
</script>

<style scoped lang="scss">
.data-table {
  position: relative;
  display: flex;
  flex-direction: column;
  margin: 0 0 0 2rem;
  width: 100%;
  height: 100%;

  .header {
    display: flex;
    justify-content: space-between;
    height: 2.1rem;
    padding: 0px 0.6rem;
    margin-bottom: 0.5rem;

    .title {
      display: flex;
      align-items: center;
      font-weight: 700;
      color: white;
      line-height: 1rem;
    }

    .menu {
      display: flex;
      justify-content: space-around;
    }
  }

  .content {
    height: 100%;
    background: url('img/tablebg.png');
    padding: 1rem 1.5rem 0.6rem 1.5rem;
    background-size: 100% 100%;
    background-repeat: no-repeat;
    overflow-y: auto;
    scrollbar-width: thin;

    &::-webkit-scrollbar {
      width: 3px;
      background-color: rgb(5, 20, 41);
    }

    &::-webkit-scrollbar-thumb {
      background-color: rgb(0, 46, 88);
    }

    .tablecontent {
      justify-content: space-between;

      :deep(.el-table) {
        height: 100%;
        background-color: transparent;
      }
    }
  }
}
</style>
<style lang='scss'>
:deep(.cell p) {
  height: 32px;
  margin: 10px 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.el-radio-button {
  --el-radio-button-checked-bg-color: rgb(1, 87, 165);
  --el-radio-button-checked-border-color: rgb(1, 87, 165);

  .el-radio-button__inner {
    color: white;
    background: rgb(1, 34, 65);
    border: none;
  }
}

.el-radio-button:first-child .el-radio-button__inner {
  border-left: none !important;
}
</style>

表格页面(地层信息页面)

<template>

  <div class="tablecontent relative" ref="tableRef">
    <el-table
        class="w-full"
        :data="tableData"
        v-loading="loading"
        highlight-current-row
    >
      <el-table-column
          v-for="item in columnList"
          :prop="item.prop"
          :label="item.label"
          :key="item.prop"
          align="center"
      >
        <template #default="scope">
          <p>
            <InputSpan
                v-model:value="scope.row[item.prop]"
                :enable-input="true"
                style="height: 100%"
            />
          </p>
        </template>
      </el-table-column>
    </el-table>
    <div v-if="addData">
      <div class="inline-flex m-2 w-full">
        <el-input
            class="mx-6"
            v-for="(_value, key) in addDataList"
            v-model="addDataList[key]"
            :autofocus="true"
            :key="key"
            size="large"
            text
        />
      </div>
      <div class="inline-flex mt-2 w-full justify-center">
        <el-button type="primary" @click="handleSumit">确定</el-button>
        <el-button type="primary" @click="handleExit">取消</el-button>
      </div>
    </div>
    <div class="mt-1">
      <el-pagination
          small
          background
          layout="prev, pager, next,"
          :total="total"
          :current-page="currentPage"
          @current-change="handleCurrentChange"
          @size-change="handleSizeChange"
          hide-on-single-page
          v-model:page-size="pageSize"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import {onMounted, ref} from 'vue'
import useStore from '@/store'
import InputSpan from '../../components/InputSpan/index.vue'
import {POST} from '@/utils/request.ts'
import {exportXLSX} from '@/utils'
import {reactive} from 'vue'

const appStore = useStore().AppStore
let tableData = ref<GEOLOGYWELLDATA[]>([])
let total = ref(0)
let pageSize = ref<number>(10)
let currentPage = ref<number>(1)
let loading = ref<boolean>(false)
let addData = ref<boolean>(false)
let tableRef = ref(null)
const columnList = [
  {prop: 'tvd', label: '井深'},
  {prop: 'dtc', label: '纵波时差'},
  {prop: 'formationDensity', label: '岩石密度'},
  {prop: 'gammaRay', label: 'GR'},
  {prop: 'fluidDensity', label: '钻井液密度'},
  {prop: 'cnl', label: 'CNL'},
]

const addDataList = reactive({
  tvd: 0,
  dtc: 0,
  formationDensity: 0,
  gammaRay: 0,
  fluidDensity: 0,
  cnl: 0,
})

const getDataList = async (force: boolean = false) => {
  loading.value = true
  currentPage.value = 1
  return appStore
      .loadGeologyWellData(force)
      .then((data) => {
        // console.log('ddddddddddddddddddddd',data);
        calculatePageSize()

        total.value = data.length
      })
      .finally(() => {
        loading.value = false
      })
}

const calculatePageSize = () => {
  const table = document.querySelector('.el-table') as HTMLElement
  const tableHeader = document.querySelector('.el-table__header') as HTMLElement
  if (table && table.clientHeight) {
    let number = Math.floor((table.clientHeight - tableHeader.clientHeight) / 76)
    handleSizeChange(number)
  }
}

const handleCurrentChange = (index: number) => {
  if (!appStore.geologyWellData) {
    return
  }
  currentPage.value = index
  tableData.value = appStore.geologyWellData.slice(
      (currentPage.value - 1) * pageSize.value,
      pageSize.value * index
  )
}

const handleSizeChange = (index: number) => {
  if (!appStore.geologyWellData) {
    return
  }
  pageSize.value = index
  tableData.value = appStore.geologyWellData.slice(
      (currentPage.value - 1) * pageSize.value,
      pageSize.value * currentPage.value
  )
}

const handleSumit = () => {
  if (!appStore.geologyWellData) {
    return
  }

  appStore.geologyWellData.push({
    ...addDataList,
    wellboreID: appStore.wellboreID,
  } as GEOLOGYWELLDATA)
  tableData.value = appStore.geologyWellData.slice(
      (currentPage.value - 1) * pageSize.value,
      pageSize.value * currentPage.value
  )
  total.value = appStore.geologyWellData.length
  addData.value = false
  // update().then(() => (addData.value = false))
}

const handleExit = () => {
  addData.value = false
}

// 添加
const add = async () => {
  addData.value = !addData.value
}

// 刷新
const refresh = async () => {
  return getDataList(true)
}

// 导入
const imports = async (excel: OBJ[]) => {
  const array: GEOLOGYWELLDATA[] = []
  excel.forEach((item) => {
    const obj = {
      wellboreID: appStore.wellboreID,
      tvd: item['井深'],
      dtc: item['纵波时差'],
      formationDensity: item['岩石密度'],
      gammaRay: item['GR'],
      fluidDensity: item['钻井液密度'],
      cnl: item['CNL'],
    }
    array.push(obj as GEOLOGYWELLDATA)
  })
  appStore.geologyWellData = array
  tableData.value = array.slice(0, pageSize.value)
  total.value = array.length
  return
}

// 保存
const update = async () => {
  return POST(
      appStore.baseURL + 'geology-well-log?wellboreID=' + appStore.wellboreID,
      appStore.geologyWellData
  ).then((res) => {
    ElMessage.success(res)
  })
}

//导出
const exports = async () => {
  if (!appStore.geologyWellData) {
    return
  }
  return exportXLSX(appStore.geologyWellData, columnList, '地层信息')
}

onMounted(() => {
  getDataList()
  window.addEventListener('resize', calculatePageSize)
})

defineExpose({
  add,
  refresh,
  imports,
  update,
  exports,
  getDataList,
  columnList,
})
</script>

<style lang="scss" scoped>
.tablecontent {
  display: flex;
  flex-direction: column;
  height: 100%;
}


</style>

导入弹框页面 

<template>
  <el-dialog
    v-model="dialogVisible"
    class="myDialog"
    title="Excel数据导入"
    width="50%"
    :before-close="handleClose"
  >
  <el-form :model="form">
    <el-form-item label="请选择需要导入的Excel文件:">
      <!-- <el-input v-model="form.importFileName" /> -->
      <el-upload
	      action=""
        accept="'.xlsx','.xls'"
	      :on-change="handleChange"
	      :auto-upload="false"
	      :file-list="fileList"
	      :show-file-list="false"
      > 
 	      <el-button size="small" type="primary" style="margin-left:10px;">
          <template #icon>
            <img src="../../../../assets/icon/uploadImg.png" alt=""  class="uploadImg"/>
          </template>
          上传
        </el-button>
     </el-upload>
    </el-form-item>
  </el-form>
  <div class="matchFields">
    <div>
      <span class="choiceTitle">文件字段</span>
      <el-card class="box-card">
        <el-scrollbar height="240px">
          <!-- <div v-for="o in 10" :key="o" class="text item">{{ o }}</div> -->
          <el-radio-group v-model="choseFileFiled">
            <el-radio v-for="o in fileField" :key="o"  :label="o" size="large"></el-radio>
          </el-radio-group>
        </el-scrollbar>
      </el-card>
    </div>
    <div class="addBox">
      <img src="../../../../assets/icon/add.png" alt=""  class="addImg"/>
    </div>
    <div>
      <span class="choiceTitle">数据字段</span>
      <el-card class="box-card">
        <el-scrollbar height="240px">
          <el-radio-group v-model="choseDataFiled">
            <el-radio
              v-for="o in dataField"
              :key="o"
              :label="o"
              size="large"
            ></el-radio>
          </el-radio-group>
        </el-scrollbar>
      </el-card>
    </div>
    <div class="mateButtons">
      <img src="../../../../assets/icon/arrow.png" alt=""  class="arrowImg"/>
      <el-button type="primary" @click="matchFields" class="match">匹配</el-button>
      <el-button type="primary" style="margin: 10px 0" @click="remove" class="remove"
        >移除</el-button
      >
    </div>
    <div class="mateContent">
      <span class="choiceTitle">匹配结果</span>
      <!-- <div> -->
      <el-card class="mateTableCard">
        <el-scrollbar height="240px">
          <div class="mateTableHead">
            <div class="mateTableColLeft">文件字段</div>
            <div class="mateTableColRight">数据字段</div>
          </div>
          <div
            v-for="(item, index) in matchTable"
            :key="index"
            class="mateTableRow"
            @click="choseDataRemove(index)"
            :style="{background:choseStyle===index?'rgba(10, 40, 82, 1)':''}"
          >
            <div class="mateTableColFile">{{ item.file }}</div>
            <div class="mateTableColData">{{ item.data }}</div>
          </div>
        </el-scrollbar>
      <!-- </div> -->
      </el-card>
     
    </div>
  </div>
  <template #footer>
    <span class="dialog-footer">
      <!-- <el-button @click="closeExcelDialog">返回</el-button> -->
      <el-button type="primary" @click="importExcelData">导入</el-button>
    </span>
  </template>
  </el-dialog>
</template>

<script lang="ts" setup>
import { onMounted, ref, reactive ,watch} from 'vue'
import useStore from '@/store'
import * as XLSX from 'xlsx'
import { storeToRefs } from 'pinia'
import InputSpan from '../../components/InputSpan/index.vue'
import { POST } from '@/utils/request.ts'
import { exportXLSX } from '@/utils'
const props= defineProps({
  tableHead:Object,
  title:String,
  selectItem: String
})
const emits = defineEmits(['update:value'])
const appStore = useStore().AppStore
let { geologyWellData,pressureData,trackData ,designTrackData,wellStructureData,stratumData,designStratumData,engineerFluidData} = storeToRefs(appStore)
let dialogVisible = ref(false)
let fileList = ref([])
let dataField = ref([])
let fileField = ref([])
let choseStyle=ref(null)
const form = reactive({
  importFileName: '',
})
// let checked1=ref(false)
let choseDataFiled = ref(null)
let choseFileFiled = ref(null)
let matchTable = ref([])
let deleteIndex = ref(null)
let excelContent = ref([])

const handleClose = () => {
  closeExcelDialog()
  // dialogVisible.value = false
}

const openExcelDialog = () => {
  dialogVisible.value = true
}

const closeExcelDialog = () => {
  dialogVisible.value = false
  // fileField.value=[]
  // form.importFileName=''
  // matchTable.value=[]
  deleteIndex.value = null
  reset()

}
const reset =()=> {
  // 置空
  fileField.value=[]
  let arr=[]
  let dealData = []
  props.tableHead.forEach(e=> {
    if(e.children){
      dealData.push(e.children)
    }else {
      dealData.push(e)
    }
    arr=dealData.flat().map(e=>e.label)
  })
  if(!arr.length){
    arr=props.tableHead.map(e=>e.label)
  }
  dataField.value=arr
   
  matchTable.value=[]
  choseFileFiled.value=null
  choseDataFiled.value=null
  fileList.value=[]
}
const getObjKeys=(key)=> {
  return matchTable.value.find(e=> e.data===key)?.file
}
const importExcelData = () => {
  let arr=[]
  let dealData = []
  props.tableHead.forEach(e=> {
    if(e.children){
      dealData.push(e.children)
    }else {
      dealData.push(e)
    }
    arr=dealData.flat().map(e=>e.label)
  })
  // console.log('props.tableHead',props.tableHead,arr);
  if (props.title!=='钻井液性能'&&matchTable.value.length !== props.tableHead.length) {
     ElMessage({
        message: '匹配未完成',
        type: 'warning',
      })
   
  }else if(arr.length!==0&&arr.length !== matchTable.value.length){
    ElMessage({
      message: '匹配未完成',
      type: 'warning',
    })
      
  }else{
    let data = []
    if(props.title==='地层信息'){
      const key=getObjKeys('井深')
      const keyOne=getObjKeys('纵波时差')  
      const keyTwo=getObjKeys('岩石密度')  
      const keyThree=getObjKeys('GR') 
      const keyFour=getObjKeys('钻井液密度') 
      const keyFive=getObjKeys('CNL') 
      data = excelContent.value.map((item,index) => {
        return {
          wellboreID: appStore.wellboreID,   
          tvd: item[key],
          dtc: item[keyOne],
          formationDensity: item[keyTwo],
          gammaRay: item[keyThree],
          fluidDensity: item[keyFour],
          cnl: item[keyFive],  
        }
      });
      geologyWellData.value=data
    }else if(props.title==='地层压力'){
      const key=getObjKeys('垂深(m)') 
      const keyOne=getObjKeys('孔隙压力(g/cm3)')
      const keyTwo=getObjKeys('破裂压力(g/cm3)')  
      const keyThree=getObjKeys('坍塌压力(g/cm3)')  
      data = excelContent.value.map((item,index) => {
        return {
          wellboreID: appStore.wellboreID,   
          tvd: item[key],
          porePressure: item[keyOne],
          fracturePressure: item[keyTwo],
          collapsePressure: item[keyThree],
        }
      });
      pressureData.value=data
    }else if(props.title==='钻井液性能'){
      const key=getObjKeys('密度g/cm3') 
      const keyOne=getObjKeys('漏斗粘度s')
      const keyTwo=getObjKeys('3转')  
      const keyThree=getObjKeys('6转')  
      const keyFour=getObjKeys('100转') 
      const keyFive=getObjKeys('200转')
      const keySix=getObjKeys('300转')  
      const keySeven=getObjKeys('600转')  
      const keyEight=getObjKeys('失水mm') 
      const keyNine=getObjKeys('日期')

      data = excelContent.value.map((item,index) => {
        return {
          wellboreID: appStore.wellboreID,   
          fluidDensity: item[key],
          funnelVisc: item[keyOne],
          r3: item[keyTwo],
          r6: item[keyThree],
          r100: item[keyFour],
          r200: item[keyFive],
          r300: item[keySix],
          r600: item[keySeven],
          waterLoss: item[keyEight],
          testDatetime: item[keyNine],
        }
      });
      engineerFluidData.value=data
    }else if(props.title==='井眼轨迹'){
      const key=getObjKeys('井深 (m)') 
      const keyOne=getObjKeys('井斜角 (°)')
      const keyTwo=getObjKeys('方位角 (°)')  
      data = excelContent.value.map((item,index) => {
        return {
          wellboreID: appStore.wellboreID,   
          md: item[key],
          inc: item[keyOne],
          azi: item[keyTwo],
        }
     });
     if(props.selectItem&&props.selectItem==='实钻轨迹'){
        trackData.value=data
      }else {
        designTrackData.value=data
      }
     
    }else if(props.title==='井身结构'){
      const key=getObjKeys('名称') 
      const keyOne=getObjKeys('井深(mm)')
      const keyTwo=getObjKeys('套管顶深(m)')  
      const keyThree=getObjKeys('套管下深(m)') 
      const keyFour=getObjKeys('套管外径(m)') 
      const keyFive=getObjKeys('套管内径(m)')
      const keySix=getObjKeys('水泥环顶深(m)')  
      const keySeven=getObjKeys('水泥环底深(m)')  
      data = excelContent.value.map((item,index) => {
        return {
          wellboreID: appStore.wellboreID,   
          casingName: item[key],
          wellboreSize: item[keyOne],
          casingTopDepth: item[keyTwo],
          casingSetDepth: item[keyThree],
          casingOuterD: item[keyFour],
          casingInnerD: item[keyFive],
          cementStartDepth: item[keySix],
          cementEndDepth: item[keySeven],
        }
      });
      wellStructureData.value=data
    }else if(props.title==='地层岩性'){
      const key=getObjKeys('岩层名称') 
      const keyOne=getObjKeys('顶深')
      const keyTwo=getObjKeys('底深')  
      const keyThree=getObjKeys('厚度') 
      const keyFour=getObjKeys('岩性')  
      data = excelContent.value.map((item,index) => {
        return {
          wellboreID: appStore.wellboreID,   
          formationName: item[key],
          topDepth: item[keyOne],
          bottomDepth: item[keyTwo],
          thickness: item[keyThree],
          lithology: item[keyFour],
          recordType: props.selectItem,
        }
      });
      if(props.selectItem&&props.selectItem==='实际岩性'){
        stratumData.value=data
      }else {
        designStratumData.value=data
      }
      
    }
    // console.log('55555555555551111111111111','matchTable',matchTable.value,'data',data);
    emits('update:value', data)
    dialogVisible.value = false
    reset()

  }
}

const handleChange = (file, fileLists) => {
  // console.log(file)
  // console.log(fileLists, document.getElementsByClassName('el-upload__input'))
  // // 本地服务器路径
  // console.log(URL.createObjectURL(file.raw))
  // // 本地电脑路径
  // console.log(document.getElementsByClassName('el-upload__input')[0].value)
  handleExcel(file)
  // const computedUrl =
  //   document.getElementsByClassName('el-upload__input')[0].value
  // form.importFileName = computedUrl
}
const handleExcel = (files: UploadFile) => {
  let file = files.raw
  if (!file) {
    return
  } else if (!/\.(xls|xlsx)$/.test(file.name.toLowerCase())) {
    ElMessage.error('上传格式不正确,请上传xls或者xlsx格式')
    return
  }
  const fileReader = new FileReader()
  fileReader.onload = (event: ProgressEvent<FileReader>) => {
    try {
      const data = event.target?.result
      const workbook = XLSX.read(data, {
        // 以字符编码的方式解析
        type: 'binary',
      })
      // 取第一张表
      const exlname = workbook.SheetNames[0]
      const excel = XLSX.utils.sheet_to_json(workbook.Sheets[exlname]) // 生成json表格内容
      excelContent.value = excel
      // fileField.value=Object.keys(excel[0])
      // console.log('cccccccccccccccccc',excel);
      let max=0
      excel.forEach(e=> {
        const data = Object.keys(e)
        max=Math.max(data.length,max)
      })
      excel.forEach(k=> {
        if(Object.keys(k).length===max){
          fileField.value=Object.keys(k)
        }
      })
      // console.log('mmmmmmmmmm',max, fileField.value);


      fileField.value.forEach((e,indexE) => {
         dataField.value.forEach((f,indexF)=> {
          if(e===f){
            matchTable.value.push({file:e,data:f})
          }
         })
      });

      matchTable.value.forEach((i,index)=>{
        fileField.value.forEach((j,index1)=> {
          if(i.file===j){
            fileField.value.splice(index1,1)
          }
        })
      })
      matchTable.value.forEach((i,index)=>{
        dataField.value.forEach((j,index1)=> {
          if(i.data===j){
            dataField.value.splice(index1,1)
          }
        })
      })


      // 将 JSON 数据挂到 data 里
      // handleButtonClicked('imports', excel)
    } catch (error) {
      ElMessage.error(String(error))
      console.error('解析文件失败: ', error)
    }
  }
  fileReader.readAsBinaryString(file)
}

// 匹配字段
const matchFields=()=> {
  // console.log('matchFields',choseDataFiled.value,choseFileFiled.value);
  if(choseDataFiled.value&&choseFileFiled.value){
    matchTable.value.push({file:choseFileFiled.value,data:choseDataFiled.value})
    const fileIndex = fileField.value.findIndex(e=>e=== choseFileFiled.value)
    fileField.value.splice(fileIndex,1)
    const dataIndex = dataField.value.findIndex(e=>e=== choseDataFiled.value)
    dataField.value.splice(dataIndex,1)
  }
 
}
// 移除
const remove=()=> {
  if(deleteIndex.value || deleteIndex.value===0){
    const deleteItem = matchTable.value.splice(deleteIndex.value,1)
    fileField.value.push(deleteItem[0].file)
    dataField.value.push(deleteItem[0].data)
    choseStyle.value=null
  }
}

const choseDataRemove = (i) => {
  deleteIndex.value = i
  choseStyle.value=i
}

const getDataList = async (force: boolean = false) => {
  // loading.value = true
  // currentPage.value = 1
  return appStore.loadGeologyWellData(force).then((data) => {
    dataField.value = Object.keys(data[0])
    // tableData.value = data.slice(0, pageSize.value)
    // total = data.length
  })
}
watch(()=>props.tableHead,(newValue,oldValue)=> {
  if(newValue&&newValue.length>0){
    let arr=[]
    let dealData = []
    newValue.forEach(e=> {
      if(e.children){
        dealData.push(e.children)
      }else{
        dealData.push(e)
      }
      arr=dealData.flat().map(e=>e.label)
    })
    if(!arr.length){
      arr=newValue.map(e=>e.label)
    }
   
    // console.log('xxxxxxxxxxxxxxxxxx',newValue,arr);
    dataField.value=arr
  }
  
},{
  deep:true,
  immediate:true
})

onMounted(() => {
  // getDataList()
 
  
})

defineExpose({
  openExcelDialog,
  closeExcelDialog,
})
</script>


<style lang="scss" scoped>
.myDialog{
  background-color:red!important;
}
.matchFields {
  width: 100%;
  /* height: 300px; */
  display: flex;
  /* justify-content: space-between; */
  .choiceTitle{
    font-size:14px;
    color:#fff;
    display:inline-block;
    margin-bottom:8px;
  }
  .mateTableCard{
    border: 1px solid rgba(10, 40, 82, 1);
    border-top:none;
    background: linear-gradient(0deg, #030D1B, #030D1B),linear-gradient(0deg, #0A2852, #0A2852);

  }
  .text {
    font-size: 14px;
  }

  .item {
    padding: 0 0 5px;
  }

  .box-card {
    width: 150px;
  }
  .mateContent {
    /* width: 300px; */
    height: 300px;
    /* background: #fff; */
    flex:1;
    :deep(.el-card__body){
      padding:0;
    }
  }
  .mateButtons {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: auto 30px;
    .arrowImg{
      width: 38px;
      height: 32px;
      margin-bottom:13px;

    }
    .match, .remove{
      width: 60px;
      height: 32px;
      padding:16px;
      border-radius: 6px;
      border: 1px solid rgba(16, 66, 137, 1);
      background: linear-gradient(0deg, #061A37, #061A37),linear-gradient(0deg, #104289, #104289);
      font-size:14px;
      color:#fff;
     

    }
  }
  .mateTableRow {
    width: 100%;
    height: 50px;
    /* border: 1px solid; */
  }
  .mateTableRow:hover{
    background:rgba(10, 40, 82, 1)
  }
  .mateTableHead{
    width: 100%;
    height: 50px;
    background: linear-gradient(0deg, #061A37, #061A37),linear-gradient(0deg, #0A2852, #0A2852);

  }
  .mateTableColLeft {
    border: 1px solid rgba(10, 40, 82, 1);
    border-left: none;
  }
  .mateTableColRight {
    border:1px solid rgba(10, 40, 82, 1);
    border-left: none;
    border-right:none;
  }
  .mateTableColLeft,
  .mateTableColRight,
  .mateTableColFile,
  .mateTableColData
  {
    display: inline-block;
    width: 50%;
    height: 50px;
    line-height: 50px;
    text-align: center;
    //styleName: Base/Strong;
    font-family: SF Pro Text;
    font-size: 14px;
    color: rgba(255, 255, 255, 0.85);
  }
  .mateTableColFile,.mateTableColData{
    border-right:1px solid rgba(10, 40, 82, 1);
    border-bottom:1px solid rgba(10, 40, 82, 1);
  }
  .addBox{
    width:100px;
    height:300px;
    display:flex;
    justify-content:center;
    align-items:center;
    .addImg {
      width:32px;
      height:32px;
      background-size:100%;
    }
  }
}

:deep(.el-form-item__content) {
  flex-wrap: nowrap;
}
:deep(.el-card__body) {
  padding: 10px;
}
:deep(.el-radio-group) {
  margin-left: 0px;
}
:deep(.el-radio.el-radio--large) {
  width: 100%;
}
:deep(.el-card__body){
  /* border: 1px solid rgba(13, 53, 110, 1); */
  background: linear-gradient(0deg, #030D1B, #030D1B),linear-gradient(0deg, #0D356E, #0D356E);
  /* width: Hug (240px)
  height: Fixed (270px) */
  /* border-radius: 8px; */


}
:deep(.el-card){
  border: 1px solid rgba(13, 53, 110, 1);
  border-radius: 8px;
}


</style>
<style lang="scss">
.myDialog {
  background:rgba(5, 20, 41, 1)!important;
  .el-dialog__header{
    width:100%;
    height:64px;
    /* line-height:64px; */
    font-family: OPPOSans;
    font-size: 18px;
    border-bottom: 1px solid rgba(27, 50, 83, 1);
    margin-right:0;
    padding:20px;
  }
  .el-dialog__body{
    /* padding: 20px 0 20px 24px; */
    padding-top:20px;
    padding-bottom: 20px;
   

  }
  .el-form-item__label {
    color : rgba(255, 255, 255, 1);
  }
  .el-button--small{
    width: 84px;
    height: 32px;
    padding: 0px, 16px, 0px, 16px;
    border-radius: 6px;
    background: linear-gradient(0deg, #061A37, #061A37),linear-gradient(0deg, #104289, #104289);
    border: 1px solid rgba(16, 66, 137, 1);
    //styleName: Base/Normal;
    font-family: SF Pro Text;
    font-size: 14px;
  }
  .el-dialog__footer {
    border-top: 1px solid rgba(27, 50, 83, 1)
  }
  
}
</style>

12.实现树节点的单选,以及将选中的树节点转化为新的树

页面效果如下:

 

实现代码如下:

两个树结构页面

<template>
  <div class="flex h-full">
    <div class="w-1/5 tree">
      <div>
        <div class="flex items-center title">展示井列表</div>
        <el-tree
          class="h-full"
          :data="treeData"
          :props="treeProps"
          show-checkbox
          node-key="uuid"
          ref="treeRef"
          @check="handleCurrentChange"
          v-loading="!appStore.fullWellInfoData"
          :default-expanded-keys="defaultExpandedKeys"
          :default-checked-keys="defaultCheckedKeys"
        />
      </div>
    </div>
    <div class="w-4/5 text-white">
      <!-- <p>{{ curObj.DisplayField }}</p>
      <ul>
        <li v-for="item in wellList">{{ item.label ? item.label : item }}</li>
      </ul> -->
       <el-tree
          class="h-full"
          :data="choseTree"
          :props="treeProps"
          node-key="label"
          v-loading="choseTreeStatus"
        />
      
      <!-- <el-button @click="handleSave">保存</el-button> -->
    </div>
  </div>
</template>

<script lang="ts" setup>
import useStore from '@/store'
import { POST, GET } from '@/utils/request'
import { onMounted, ref, watch } from 'vue'
import { getValue } from '@/utils'

const appStore = useStore().AppStore
const treeRef = ref(null)
interface obj {
  UsrName: string
  DisplayWellboreList: string
  CurrentWellboreID: string
  DisplayField: string
  CurrentDisplayParas: string
}
const wellList = ref([])
const defaultCheckedKeys = ref([])
let defaultExpandedKeys=  ref([])
let choseTree = ref([])
let choseTreeStatus= ref(true)
const treeData = ref<FULLWELLINFODATA[]>()
const curObj = ref<obj>({
  UsrName: '',
  DisplayWellboreList: '',
  CurrentWellboreID: '',
  DisplayField: '',
  CurrentDisplayParas: '',
})
// "UsrName": "test1",
//     "DisplayWellboreList": "DQ8R9-H8-1,DQ8R9-H9-1",
//     "CurrentWellboreID": "DQ8R9-H8-1",
//     "DisplayField": "北航DQ油田",
//     "CurrentDisplayParas": "MD, ROP, BitLocation, WOB, RotatryRPM, SPP, FlowrateIn"
// 表props
const treeProps = {
  children: 'children',
  label: 'label',
  disabled: 'disabled',
}
onMounted(async () => {
  await appStore.loadFulWellInfoData()
  treeData.value = appStore.fullWellInfoData
  console.log('tree!!!!!!!!!!!!!!!',treeData.value,treeData.value[0],treeData.value[0].$treeNodeId)

  await getList()
})

const findRoot = (currentData:any) => {
  return !currentData.parent ? currentData : findRoot(currentData.parent)
}

const deepMap = (selectedList:any, currentTreeData:any) => {
  let final = null
  for (let i = 0; i < currentTreeData.length; i++) {
    const item = currentTreeData[i]
    if (selectedList.some((dataItem:any) => {
      // return dataItem === item.label
      return dataItem === item.uuid
    })) {
      //匹配到当前父级
      if (item.parent) {
        //如果当前不是根节点,则继续往上找根节点
        final = findRoot(item.parent)
      } else {
        // 匹配到根节点
        final = item
      }
    } else if (item.children) {
      //否则继续往下匹配
      final = deepMap(selectedList, item.children)
    }
    if (final) {
      //一旦匹配上,即可终止循环
      break
    }
  }
  return final
}

const deepFilter = ((currentObj:any, selectedList:any) => {
  console.log('ffffffffffffffffffff',currentObj,selectedList);
  
  currentObj.children = currentObj.children.filter((item:any) => {
    console.log('xxxxxx',item);
    
    if (selectedList.some((dataItem:any) => {
      // return dataItem === item.label
      return dataItem === item.uuid
    })) {
      console.log('123123123');
      
      
      return item
    } else if (item.children){
      console.log('90909090');
      
      item = deepFilter(item, selectedList)
      if (item.children.length) return item
    }
  })
  return currentObj
})

const deepChildren = (childrenList:any) => {
  let list = []
  childrenList.forEach((item:any) => {
    let json = {
      label: item.label,
      uuid:item.uuid,
      disabled: item.disabled,
      type: item.type,
    }
    if (item.children){
      json.children = []
      json.children.push(...deepChildren(item.children))
      list.push(json)
    } else {
      list.push(json)
    }
  })
  return list
}

const cloneTree = (currentObj:any) => {
  let copyData = {
    label: currentObj.label,
    uuid:currentObj.uuid,
    disabled: currentObj.disabled,
    type:currentObj.type,
    children: []
  }
  copyData.children = deepChildren(currentObj.children)
  return copyData
}

const getSelectedTreeData = (data:any) => {
  choseTree.value = []
  
  if (treeData.value) {
    
    let obj = deepMap(data, treeData.value)
    
    let filterObj = null
    if (!obj) return
    let copyObj = cloneTree(obj)
    if (copyObj) filterObj = deepFilter(copyObj, data)
   
    
    if (filterObj) choseTree.value.push(filterObj)
    
  }
}

watch(
  () => treeRef.value?.getCheckedKeys(),
  // ()=>treeRef.value?.getCheckedNodes(),
  (newData:any) => {
    console.log('newData', newData)
    getSelectedTreeData(newData)
  },
  {
    immediate: true,
    deep: true
  }
)

const changeTreeData = (data) => {
  data?.forEach((ele) => {
    if (ele.type === 'Field') {
      ele.disabled = true
    } else {
      ele.disabled = false
    }

    if (ele.children?.length) {
      changeTreeData(ele.children)
    }
  })
}
// 处理后端返回的选中的子节点
let dealChildrenData = (tree)=> {
  let temp = []
  let dfs = (tree) => {
    for (let i = 0; i < tree.length; i++) {
      // temp.push(tree[i].label)
       temp.push(tree[i].uuid)
      let item = tree[i]
      if (item.children) {
        dfs(item.children)
      }
    }
  }
  dfs(tree)
   return temp
}


const getList = () => {
  GET(appStore.baseURL + 'usr-wellbore-display?UsrName=root').then((res) => {
  
    let data = res.DisplayWellboreList.split(',')
    wellList.value=data.filter(e=>e!=='')
    // wellList.value = res.DisplayWellboreList.split(',')
    let result = treeData.value.filter(e=> {
      // if(e.label==res.DisplayField){
      //  return e
      // }
       if(e.name==res.DisplayField){
       return e
      }
    })
    let selectKeys=dealChildrenData(result)
    // treeRef.value.setCheckedKeys(wellList.value)
    console.log('xxxxxxxxxxxxxxxxxxx',res,result,selectKeys);
    treeRef.value.setCheckedKeys(selectKeys)
    defaultExpandedKeys.value = selectKeys
    curObj.value = res
    choseTree.value=result
    choseTreeStatus.value=false
    // defaultCheckedKeys.value.push(curObj.value.DisplayField)
    changeTreeData(treeData.value)
  })
}
// const handleNodeClick = (data: FULLWELLINFODATA) => {
//   console.log('handleNodeClick ccccccccccccccccccc',data);
//   // const info = data.info
//   // switch (data.type) {
//   //   case 'Wellbore':
//   //     curObj.value.DisplayField = getValue(data.parent?.parent?.parent, 'label')
//   // }
//   // if (!info) {
//   //   return
//   // }
// }

function arrayToTree(list, root) {
  const result = [] // 用于存放结果
  const map = {} // 用于存放 list 下的节点

  // 1. 遍历 list,将 list 下的所有节点以 id 作为索引存入 map
  for (const item of list) {
    map[item.label] = { ...item } // 浅拷贝
  }

  // 2. 再次遍历,将根节点放入最外层,子节点放入父节点
  for (const item of list) {
    // 3. 获取节点的 id 和 父 id
    const { id, parent_id,children,parent } = item // ES6 解构赋值
    // 4. 如果是根节点,存入 result
    if (item.parent === root) {
      result.push(map[item.label])
    } else {
      // 5. 反之,存入到父节点
      map[parent].children
        ? map[parent_id].children.push(map[id])
        : (map[parent_id].children = [map[id]])
    }
  }

  // 将结果返回
  return result
}
// 函数拷贝
const copyObj = (obj = {}) => {
    let newObj = null
    // 判断是否需要继续进行递归
    if(typeof obj == 'object' && obj !== null){
        newObj = obj instanceof Array?[]:{}
        // 进行下一层递归克隆
        for(let i in obj){
            newObj[i] = copyObj(obj[i])
        }
    }else{
        newObj = obj
    }
    return newObj
}
//  function dealRightTree(result,blockName,wellName,wellBoreName){
//   // let result=JSON.parse(JSON.stringify(data))
//   console.log('00000000000000000',result,blockName,wellName,wellBoreName);
  
//   for(let i in result){
//     let item = result[i]
//     console.log('item/',item);
    
//     if(item.type==='Block'){
//        if(item.label!==blockName){
//           result.splice(i,1)
//         }
//     }else if(item.type==='Well'){
//       console.log('well;;;;;;;;;;;;',item.label,wellName);
//       // debugger
//         if(item.label!==wellName){
//           result.splice(i,1)
//         }
//     }else if(item.type==='Wellbore'){
//       if(item.label!==wellBoreName){
//         result.splice(i,1)
//       }
//     }
//     dealRightTree(item.children)
//   }
//   return result
// }

const handleCurrentChange = (data, node) => {
  console.log('handleCurrentChange',data,node);
  // let type = data.type
  // let field: string
  // let fieldParent :string
  // if (type === 'Wellbore') {
  //   field = getValue(data.parent?.parent, 'label')
  //   fieldParent = getValue(data.parent?.parent.parent, 'label')
  // } else if (type === 'Block') {
  //   field = getValue(data, 'label')
  //   fieldParent = getValue(data.parent, 'label')
  // } else if (type === 'Well') {
  //   field = getValue(data.parent, 'label')
  //   fieldParent = getValue(data.parent?.parent, 'label')
  // }
  

  node.checkedNodes.push(...node.halfCheckedNodes)
  let arr = node.checkedNodes.filter((ele) => {
    return ele.type === 'Block'
  })
  
  if(arr.length>1){
    ElMessage.warning('只可选择一个区块')
    treeRef.value.setChecked(data.uuid, false,true)
    return
  }
  return
  // if(arr.length>1){
  //   ElMessage.warning('只可选择一个区块')
  //   treeRef.value.setChecked(data.label, false,true)
  // }else{
  //   curObj.value.DisplayField=fieldParent
  //   let data = treeRef.value.getCheckedNodes()
  //   let select=null
  //   let findData = null
  //   let res = null
  //   console.log('data-------------',data);
    
  //   if(data.find(e=>e.type==='Block')){
  //     findData=data.find(e=>e.type==='Block')
  //     select = getValue(findData.parent, 'label')
  //     let blockData = findData.label
      
  //     // choseTree.value=treeData.value.filter(e=>e.label===select)
  //   }else if(data.find(e=>e.type==='Well')){
  //     findData= data.find(e=>e.type==='Well')
  //     select = getValue(findData.parent?.parent, 'label')
  //     let blockData = getValue(findData.parent, 'label')
  //     let wellBoreName =null
  //     // let yy = copyObj(treeData)
  //     let result =treeData.value.filter(e=>e.label===select)
  //     // debugger
  //     // let cloneData = JSON.parse(JSON.stringify(result))



  //     function dealRightTree(result){
  //       //  let result=JSON.parse(JSON.stringify(data))
        
  //       for(let i in result){
  //       let item = result[i]
  //       console.log('item/',item);
        
  //       if(item.type==='Block'){
  //          if(item.label!==blockData){
  //             result.splice(i,1)
  //           }
  //       }else if(item.type==='Well'){
  //         console.log('well;;;;;;;;;;;;',item.label,findData.label);
  //         // debugger
  //           if(item.label!==findData.label){
  //             result.splice(i,1)
  //           }
  //       }else if(item.type==='Wellbore'&&wellBoreName){
  //         if(item.label!==wellBoreName){
  //           result.splice(i,1)
  //         }
  //       }
  //       dealRightTree(item.children)
  //     }
  //     return result
  //   }
     
  //     console.log(',,,,,,,,,,,,,',result,treeData,blockData,findData.label);
  //     // dealRightTree(result)
  //     // console.log('----------------',result,treeData);
  //     // choseTree.value=mmm(result)
    
  //   }else if(data.find(e=>e.type==='Wellbore')){
  //     findData=data.find(e=>e.type==='Wellbore')
  //     select =getValue(findData.parent?.parent.parent, 'label')
  //     let wellBoreName = findData.label
  //     let wellName = getValue(findData.parent, 'label')
  //     let blockName = getValue(findData?.parent.parent, 'label')
  //     let result =treeData.value.filter(e=>e.label===select)
  //     // dealRightTree(result,blockName,wellName,wellBoreName)
      
  //   }
  //   console.log('cccccccccccccccccccc',findData,select);
    
  //   choseTree.value=treeData.value.filter(e=>e.label===select)
  //   choseTreeStatus.value=false
  //   console.log('rrrrrrrrrrrrr','choseTree.value',choseTree.value);
    
    
  // } 


  // if(data.type !== 'field'){
    // let arr = node.checkedNodes.filter((ele) => {
    //   return ele.type === 'Wellbore'
    // })
    // if(node.checkedKeys!==0){
    //   ElMessage.warning('只可选择一个油田')
    //   treeRef.value.setChecked(data.label, false,true)
    //   console.log('bbbbbbbbbbbbbbbbbbbbbb');
      
    // }else{
    //   treeRef.value.setChecked(data.label, true,true)
    // }

  // }
  // if (data.type !== 'field') {
  //   let arr = node.checkedNodes.filter((ele) => {
  //     return ele.type === 'Wellbore'
  //   })
  //   if (!curObj.value.DisplayField) {
  //     curObj.value.DisplayField = field
  //     wellList.value = arr
  //   }
  //   if (curObj.value.DisplayField === field ) {
  //     wellList.value = arr
  //   } else {
  //     ElMessage.warning('只可选同一油田的')

  //     treeRef.value.setChecked(data.label, false,true)
  //     return false
  //   }
  //   wellList.value = [...new Set(wellList.value)]

  //   if (!arr.length) {
  //     curObj.value.DisplayField = ''
  //   }
  // }
}
const handleSave = () => {
  let list = wellList.value.map((ele) => {
    return ele.label
  })
  curObj.value.DisplayWellboreList = list.join(',')
  curObj.value.CurrentWellboreID = list[list.length - 1]

  POST(appStore.baseURL + 'usr-wellbore-display', curObj.value)
    .then((data) => {
      ElMessage.success(data)
      appStore.loadFulWellInfoData(true)
    })
    .catch((data) => {
      ElMessage.error(data)
    })
}
</script>

<style scoped lang="scss">
.el-tree {
  background: none;
  height: 100%;
  --el-tree-node-hover-bg-color: rgb(37, 49, 68);
  --el-tree-text-color: white;
}
.tree {
  background: url('src/assets/images/treebg.png');
  background-size: 100% 99%;
  background-repeat: no-repeat;
  .title {
    color: white;
    padding-left: 10px;
    height: 5%;
  }
}
.el-button {
  background: rgb(15, 76, 138);
  border: 1px solid rgb(0, 137, 241);
  border-radius: 3px;
  color: white;
  box-shadow: 0px 0px 11px 0px #01c6f847;
}
li {
  list-style: none;
}
</style>

transformData方法

/**
 * 递归解析基础数据
 * @param tempData
 * @param parent
 */
export const transformData = (tempData: OBJ, parent?: FULLWELLINFODATA) => {
    const result = []
    for (const key in tempData) {
        // 默认赋予油田属性
        const wellInfo: FULLWELLINFODATA = {
            uuid:uuidv4(),
            label: key,
            // label: uuidv4(),
            name:key,
            type: 'Field',
            info: { Field: key },
        }
        if (parent) {
            // 附加parent
            wellInfo.parent = parent
        }
        if (Array.isArray(tempData[key])) {
            // 基本信息
            // const fieldName = wellInfo.parent?.parent?.label
            // const blockName = wellInfo.parent?.label
            const fieldName = wellInfo.parent?.parent?.name
            const blockName = wellInfo.parent?.name
            // 井筒
            wellInfo.children = tempData[key].map((item: WELLBLOCK) => {
                return {
                    // uuid: String,
                    info: {
                        Field: fieldName,
                        Block: blockName,
                        WellID: key,
                        ...item,
                    },
                    type: 'Wellbore',
                    label: item.WellboreName,
                    // label:uuidv4(),
                    name: item.WellboreName,
                    uuid:uuidv4(),
                    parent: wellInfo,
                } as FULLWELLINFODATA
            })
            // 井
            wellInfo.label = key
            // wellInfo.label=uuidv4()
            wellInfo.name = key
            wellInfo.uuid=uuidv4()
            wellInfo.type = 'Well'
            wellInfo.info = {
                Field: fieldName,
                Block: blockName,
                WellID: key,
            }
            // 区块
            const block = wellInfo.parent!
            block.type = 'Block'
            block.info = {
                Field: fieldName,
                Block: blockName,
            }
        } else {
            wellInfo.children = transformData(tempData[key], wellInfo)
        }
        result.push(wellInfo)
    }
    return result
}

13.实现任意角度的扇形

如何快速绘制任意角度的扇形 - 掘金

<template>
  <div class="ontology">
    <div class="modelShow">
        <div class="airscrewModel">
            <div class="airscrewModelData">
                <img src="../../assets/img/toLeft.png" alt="" class="directionIcon" v-show="this.roll.toString().includes('-')" />
                <img src="../../assets/img/toRight.png" class="directionIcon" alt=""  v-show="!this.roll.toString().includes('-')"/>
                <span class="directionData">{{roll}}</span>
            </div>
            <div :style="{transform: `rotate(${roll}deg)`}" class="airscrewModelImg"></div>
        </div>
        <div class="submarine"  v-show="!this.angel.toString().includes('-')">
          <div class="chuan" :style="{ transform: `rotate(${'-'+angel}deg)` }"></div>
          <svg width="300" height="300" xmlns="http://www.w3.org/2000/svg" class="upCircle"> 
            <circle
              r="150"
              cx="0"
              cy="300"
              fill="transparent"
              stroke="rgba(176, 176, 176, 0.2)"
              stroke-width="300"
              :stroke-dasharray="300 * 3.1415926"
              :stroke-dashoffset="300 * 3.1415926 / 360 * (360 + angel)"
            />
          </svg>
          <hr class="linetwo" />
          <div class="rotateData">
            <img src="../../assets/img/toTop.png" alt="" class="directionIcon" />
            <!-- <img src="../../assets/img/toBottom.png" class="directionIcon" alt="" /> -->
            <span class="directionData">{{angel}}</span>
          </div>
        </div>
        <div class="submarine" v-show="this.angel.toString().includes('-')">
          <div class="downChuan" :style="{ transform: `rotate(${angel.toString().substring(1)}deg)` }"></div>
          <svg width="480" height="250" xmlns="http://www.w3.org/2000/svg" class="downCircle"> 
            <circle
              r="150"
              cx="10"
              cy="10"
              fill="transparent"
              stroke="rgba(176, 176, 176, 0.2)"
              stroke-width="300"
              :stroke-dasharray="300 * 3.1415926"
              :stroke-dashoffset="rotateData(angel)"
            />
          </svg>
          <hr class="linetwo" />
          <div class="downRotateData">
            <!-- <img src="../../assets/img/toTop.png" alt="" class="directionIcon" /> -->
            <img src="../../assets/img/toBottom.png" class="directionIcon" alt="" />
            <span class="directionData">{{angel}}</span>
          </div>
        </div>
       
    </div>
    <div class="modelIndex">
        <div v-for="(item,index) in modelIndex" class="modelIndexItem">
            <img :src="item.img" class="modelItemImg"  alt=""/>
            <div class="modelContent">
                <p class="modelIndexFont">{{item.key}}</p>
                <p class="modelIndexValue">
                    <span class="controlModelValue">{{item.value}}</span>
                    <span class="controlModelUnit">{{item.unit}}</span>
                </p>
            </div>
        </div>
    </div>
   

  </div>
</template>
<script>
export default {
  name: 'Ontology',
  props:{
    controlObj: Object
  },
  data () {
    return {
        angel: -2.06,
        // i: Math.floor(Math.random()*10),
        roll: 30,
        modelIndex:[
            {
              key:'编号',
              img: require('../../assets/img/number.png'),
              value: '3',
              unit: ''
            },
            {
              key:'主体',
              img: require('../../assets/img/mainBody.png'),
              value: '静默运行',
              unit: ''
            },
            {
              key:'航深',
              img: require('../../assets/img/depthOfNavigation.png'),
              value: '50',
              unit: 'm'
            },
            {
              key:'经度',
              img: require('../../assets/img/longItude.png'),
              value: '120',
              unit: '°E'
            },
            {
              key:'纬度',
              img: require('../../assets/img/latitude.png'),
              value: '35',
              unit:'°N'
            },
            {
              key:'航程',
              img: require('../../assets/img/voyage.png'),
              value: '200',
              unit:'NA'
            },
            {
              key:'对底深度',
              img: require('../../assets/img/bottomDepth.png'),
              value: '46',
              unit:'M'
            },
            {
              key:'电击电流',
              img: require('../../assets/img/motorCurrent.png'),
              value: '100',
              unit:'A'
            },
            {
              key:'电击电压',
              img: require('../../assets/img/MotorVoltage.png'),
              value: '400',
              unit:'V'
            },
            {
              key:'仪表电池电量',
              img: require('../../assets/img/batteryLevel.png'),
              value: '50',
              unit:'%'
            },
            {
              key:'动力电池电量',
              img: require('../../assets/img/powerBatteryLevel.png'),
              value: '70',
              unit: '%'
            },
            {
              key:'左上舵',
              img: require('../../assets/img/upperShip.png'),
              value: '10',
              unit:'m'
            },
            {
              key:'右下舵',
              img: require('../../assets/img/leftShip.png'),
              value: '20',
              unit:'°'
            },
            {
              key:'右上舵',
              img: require('../../assets/img/rightShip.png'),
              value: '15',
               unit:'°'
            },
            {
              key:'左下舵',
              img: require('../../assets/img/upperShip.png'),
              value: '3',
              unit:'m'
            },
        ]
    }
  },
  watch:{
    controlObj(newVal,oldVal){
      if(newVal){
        // console.log('66666666666666',newVal,newVal.pitchAngle);
        this.modelIndex.forEach(e => {
            if(e.key==="编号"){
              e.value = newVal.id
            }else if(e.key=== '主体'){

            }else if(e.key=== '航深'){
              e.value = newVal.shipDepth
            }else if(e.key=== '经度'){
              e.value = newVal.longitude
            }else if(e.key=== '纬度'){
              e.value = newVal.latitude
            }else if(e.key=== '航程'){
              e.value = newVal.range   
            }else if(e.key=== '对底深度'){
              e.value = newVal.bottomDepth    
            }else if(e.key=== '电击电流'){
              e.value = newVal.electricity   
            }else if(e.key=== '电击电压'){
              e.value = newVal.voltage  
            }else if(e.key=== '仪表电池电量'){
              e.value = newVal.dashbordBattery  
            }else if(e.key=== '动力电池电量'){
              e.value = newVal.engineBattery  
            }else if(e.key=== '左上舵'){
              e.value = newVal.rudderLeftTop    
            }else if(e.key=== '左下舵'){
              e.value = newVal.rudderLeftBottom 
            }else if(e.key=== '右上舵'){
              e.value = newVal.rudderRightTop    
            }else if(e.key=== '右下舵'){
              e.value = newVal.rudderRightBottom  
            }
        });
        this.angel=Number(newVal.pitchAngle)
        this.roll=Number(newVal.roll)
      }
    }
  },
  methods: {
    rotateData(angle){
      let str=angle.toString()
      if(str.includes('-')){
        const data = str.substring(1)
        return 300 * 3.1415926 / 360 * (360 - data)
      }else{
        // console.log('+++++++++++++++++++++',300 * 3.1415926 / 360 * (360 - angle));
        return 300 * 3.1415926 / 360 * (360 - angle)
      }
   
    }
  },
  created() {
  },
  mounted() {
  },
}
</script>
<style lang="sass" scoped>
.ontology
  width: 100%
  height: 594px
  .modelShow
    width: 100%
    height: 286px
    position: relative
  .modelIndex
    display: flex
    flex-wrap: wrap
    .modelIndexItem
      display: flex
      width: 33%
      height: 40px
      margin-bottom: 16px
      padding-left: 23px
      box-sizing: border-box
    .modelItemImg
      width: 36px
      height: 36px
      background-size: 100%
    .modelIndexFont
      font-family: MiSans;
      font-size: 14px;
      color: rgba(168, 214, 255, 1);
      margin: 0
    .modelIndexValue
      font-family: Furore;
      font-size: 18px;
      color: rgba(255, 255, 255, 1);
      margin: 0
    .controlModelValue
      font-family: Furore;
      font-size: 18px;
      color: rgba(255, 255, 255, 1);
    .controlModelUnit
      font-family: PingFang SC;
      font-size: 14px;
      color: rgba(222, 240, 255, 1);



    .modelContent
      display: inline-block
      margin-left: 10px
  .circleBlock
    width: 200px;
    height: 200px;
    position: relative;
    overflow: hidden;
    border-radius: 50%;
    .circle 
      width: 50%;
      height: 50%;
      background: #6C609E
      position: absolute;
      top: 0
      right: 0
      transform-origin: 0% 100%
      overflow: hidden
      transform: skewY(-45deg)
  .rotateModel
    position: absolute
    bottom: -300px;
    background-color: red;
    width: 600px;
    height: 600px;
    border-radius: 50%;
    clip-path: polygon(50% 50%, 20% 0%, 80% 0%);
.airscrewModel
  width: 176px
  height: 110px
  top: 4px
  left: 23px
  position: absolute
  
.airscrewModelImg
  width: 100%
  height: 100px
  transition: all 3s
  background: url('../../assets/img/airscrew.png')  no-repeat center center    
.airscrewModelData
  display: flex
.directionIcon
    width: 24px
    height: 24px
.directionData
  font-family: MiSans;
  font-size: 18px;
  font-weight: 500;
  color: rgba(255, 255, 255, 1);
.submarine
  width: 100%;
  height: 100%
  box-sizing: border-box
  .rotateData
    position: absolute
    bottom: 90px
    right: 0
    display: flex
.downRotateData
   position: absolute
   bottom: 90px
   left: 30px
   display: flex

</style>
<style scoped>
.upCircle{
  /* transform: rotate(-30deg);
  transform-origin: center center; */
  position: absolute;
  left: 100px;
  bottom: 65px;
  /* margin-top: -150px; */
  z-index: 2;
}
.downCircle{
  transform: rotate(-180deg);
  transform-origin: center center;
  position: absolute;
  left: -66px;
  bottom: 61px;
}
.chuan, .downChuan {
  /* // display: block; */
  height: 100px;
  width: 300px;
  position: absolute;
  bottom: 68px;
  left: 100px;
  /* // height: 5px; */
  z-index: 3;
  background-color: transparent !important;
  transition: all 3s;
  transform-origin: left bottom;
  border-bottom: 1px dashed #fff;
  background: url('../../assets/img/submarine.png') no-repeat center center;
 }
 .chuanbacground {
  position: absolute;
  left: 121px;
  bottom: -40px;
  /* margin-top: -150px; */
  z-index: 2;
  /* background-color: rgba(176, 176, 176, 0.2); */
  /* width: 250px;
  height: 250px; */
  /* border-radius: 50%; */
  /* // clip-path: polygon(50% 50%, 20% 0%, 80% 0%); */
  /* clip-path: polygon(100% 50%, 0 50%, 0 0, 100% 0); */
  /* // clip-path: polygon(100% 50%, 0 50%, 0 0, 100% 0); */
  /* // clip-path: polygon(50% 0, 100% 0, 100% 3600%, 50% 50%); */
}
.downChuan{
    bottom: 70px;
   transform-origin: right bottom;
}

.linetwo {
  /* // width: 50%; */
  width: 300px;
  /* margin-top: -5px; */
  /* // margin-left: -472px; */
  border: 1px dashed;
  position: absolute;
  border: 1px dashed #fff;
  bottom: 60px;
  left: 100px;
 }

</style>

效果截图

14.手写一个进度条

<template>
  <div class="slider" ref="slider" @click.stop="handelClickSlider">
    <div class="process" :style="{ width,background:bgColor }"></div>
    <div class="thunk" ref="trunk" :style="{ left }">
      <div class="block" ref="dot"></div>
    </div>
  </div>
</template>

<script>
/*
 * min 进度条最小值
 * max 进度条最大值
 * v-model 对当前值进行双向绑定实时显示拖拽进度
 * */
import { mapActions,mapState } from 'vuex';
export default {
  name: 'Slisd',
  props: {
    // 最小值
    title: String,
    min: {
      type: Number,
      default: 0,
    },
    // 最大值
    max: {
      type: Number,
      default: 100,
    },
    // 当前值
    value: {
      type: Number,
      default: 0,
    },
    // 进度条颜色
    bgColor: {
      type: String,
      default: "#4ab157",
    },
    // 是否可拖拽
    isDrag: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      slider: null, //滚动条DOM元素
      thunk: null, //拖拽DOM元素
      per: this.value, //当前值
      // sendSEMsg: false,
      currentWidth: null,
      currentStatus: null,
      number: 0
    };
  },
  mounted() {
    this.slider = this.$refs.slider;
    this.thunk = this.$refs.trunk;
    var _this = this;
    if (!this.isDrag) return;
    this.thunk.onmousedown = function (e) {
      var width = parseInt(_this.width);
      var disX = e.clientX;
      document.onmousemove = function (e) {
        // value, left, width
        // 当value变化的时候,会通过计算属性修改left,width
        // 拖拽的时候获取的新width
        var newWidth = e.clientX - disX + width;
        // 计算百分比
        var scale = newWidth / _this.slider.offsetWidth;
        _this.per = Math.ceil((_this.max - _this.min) * scale + _this.min); //取整
        // 限制值大小
        _this.per = Math.max(_this.per, _this.min);
        _this.per = Math.min(_this.per, _this.max);

        _this.$emit("input", _this.per);
        _this.$store.state.slideStatus = true
      };
      document.onmouseup = function () {
        //当拖拽停止发送事件
        _this.$emit("stop", _this.per);
        //清除拖拽事件
        document.onmousemove = document.onmouseup = null;
        //  this.sendSEMsg = true
        this.number = 1
      };
    };
  },
  methods: {
    handelClickSlider(event) {
      this.$store.state.slideStatus = true
      //禁止点击
      if (!this.isDrag) return;
      const dot = this.$refs.dot;
      if (event.target == dot) return;
      //获取元素的宽度l
      let width = this.slider.offsetWidth;
      //获取元素的左边距
      let ev = event || window.event;
      //获取当前点击位置的百分比
      let scale = ((ev.offsetX / width) * 100).toFixed(2);
      this.per = scale;

    },
    ...mapActions('fui-commands', { createFuiCommands: 'create' }),
    async sendMesToSE(value,status){
      let obj={}
      if(this.title==="12 小时光照"){
        obj = {
          userId: this.$store.state.auth.user._id,
          sources: 'fui',
          command: 'illuminationStretch',
          data: status.toFixed(2),
        };
      }else if(this.title==="海浪等级"){
        obj = {
          userId: this.$store.state.auth.user._id,
          sources: 'fui',
          command: 'seaWaveStretch',
          data: (Number(status)+1).toString().substring(0,1),
        };
      }else {
        obj = {
          userId: this.$store.state.auth.user._id,
          sources: 'fui',
          command: 'underwaterBrightness',
          data: (Number(status)+1).toString().substring(0,1),
        };
      }
     
      const res = await this.createFuiCommands(obj);
     
      // console.log('-----------------------',this.title,res,status);
    }
  },
  computed: {
    ...mapState(['slideStatus']),
    // 设置一个百分比,提供计算slider进度宽度和trunk的left值
    // 对应公式为  当前值-最小值/最大值-最小值 = slider进度width / slider总width
    // trunk left =  slider进度width + trunk宽度/2
    scale() {
      return (this.per - this.min) / (this.max - this.min);
    },
    width() {
      return this.slider ? this.slider.offsetWidth * this.scale + "px" : "0px";
    },
    left() {
      return this.slider ? this.slider.offsetWidth * this.scale - this.thunk.offsetWidth / 2 +"px" : "0px";
    },
  },
  watch: {
    value: {
      handler: function () {
        this.per = this.value;
      },
    },
    width: function(newVal,oldVal){
      // console.log('ccccccccccccccccccc',newVal,oldVal,this.$store.state,this.$store.state.slideStatus);
      let data = newVal.slice(0,-2)
      let scrollStatus = Number(data).toFixed()/416
      let status
      if(this.title==="12 小时光照"){
        status = 12*scrollStatus
        this.$emit('scrollStatus',status)
      }else if(this.title==="海浪等级") {
        status = 3*scrollStatus
        this.$emit('scrollStatus',status)
      }else {
        status = 3*scrollStatus
        this.$emit('scrollStatus',status)
      }
      this.currentWidth = newVal
      this.currentStatus = status
      // console.log('........................',status);
      if(this.$store.state&&this.$store.state.slideStatus){
        console.log('send SE message');
        this.sendMesToSE(newVal,status)
      }
      
    },

  },
};
</script>

<style lang="sass" scoped>
/* @import url(); 引入css类 */

</style>
<style scoped>
.box {
  margin: 100px auto 0;
  width: 80%;
}
.clear:after {
  content: "";
  display: block;
  clear: both;
}
.slider {
  position: relative;
  margin: 9px 0;
  top: 50%;
  width: 100%;
  height: 12px;
  background: rgba(59, 59, 59, 0.38);
  border: 1px solid rgba(189, 189, 189, 1);
  /* background: #747475;
  border-radius: 5px; */
  cursor: pointer;
  z-index: 99999;
}
.slider .process {
  position: absolute;
  left: 0;
  top: 0;
  width: 112px;
  height: 10px;
  border-radius: 5px;
  /* background: #4ab157; */
  background: linear-gradient(270deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 100%);
  z-index: 111;
}
.slider .thunk {
  position: absolute;
  left: 100px;
  top: -4px;
  width: 10px;
  height: 6px;
  z-index: 122;
}
.slider .block {
  width: 11px;
  height: 18px;
  /* border-radius: 50%; */
  /* background: rgba(255, 255, 255, 1); */
  background: url('../../assets/img/scheduleHead.png') no-repeat;

  transition: 0.2s all;
}
.slider .block:hover {
  transform: scale(1.1);
  opacity: 0.6;
}
</style>

组件使用

<template>
  <div>
    <div class="progressTitle">
      <span class="processFont">{{title}}</span>
      <div class="processTime">{{title==='12 小时光照'?status:status.toString().substring(0,1)+'级'}}</div>
    </div>
    <!-- <div class="processBar">
      <div class="scrollBar" :style="{width:scroll+'px'}"></div>
      <img src="../../assets/img/scheduleHead.png" alt=""  class="scrollDrop"/>
    </div> -->
    <Slisd :title="title" :min="0" :max="100" :value="50" :isDrag="true" bgColor="linear-gradient(270deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 100%)" @scrollStatus="scrollStatus"></Slisd>
    <div class="timeFiled">
      <div class="tipField">{{start}}</div>
      <div class="tipField">{{end}}</div>
    </div>
  </div>
</template>
<script>
import Slisd from '../slisd/index.vue'
export default {
  name: 'SceneConfigItem',
  components:{
    Slisd
  },
  props:{
    scroll: String,
    title:String,
    // status:String,
    start: String,
    end: String
  },
  data () {
    return {
      status: 0
    }
  },
  methods: {
    scrollStatus(value){
      // console.log('hhhhhhhhhhhhhhh',value,Number(value.toFixed(0)));
      if(this.title==='12 小时光照'){
        let data = value.toFixed(2)
        this.status = data.toString().replace('.',':')
      }else if(this.title==='海浪等级') {
        let data = Number(value)+1
        this.status = data.toFixed(2)+ '级'
      }else {
        let data = Number(value)+1
        this.status = data.toFixed(2)+ '级'
      }
    }
  },
  created () {
  },
  mounted () {
  },
}
</script>
<style lang="sass" scoped>
.progressTitle
  width: 100%
  height: 27px
  .processFont
    font-family: MiSans;
    font-size: 20px;
    color: rgba(255, 255, 255, 1);
  .processTime
    float: right
    display: inline-block
    width: 55px
    height: 22px
    text-align: center
    line-height: 22px
    border-radius: 2px
    border: 1px solid rgba(11, 219, 94, 1)
    background: linear-gradient(0deg, #0BDB5E, #0BDB5E),linear-gradient(0deg, #0BDB5E, #0BDB5E);
    font-family: MiSans;
    font-size: 12px;
    font-weight: 500;
    color: rgba(255, 255, 255, 1);

.processBar
  margin: 6px 0;
  display: flex;
  align-items: center
  width: 100%
  height: 12px
  background: rgba(59, 59, 59, 0.38);
  border: 1px solid rgba(189, 189, 189, 1)
  .scrollBar
    display: inline-block
    width: 100px
    height: 12px
    background: linear-gradient(270deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 100%);
  .scrollDrop
    display: inline-block
    width: 11px
    height: 18px
    
.timeFiled
  margin-bottom: 10px
  width: 100%;
  height: 24px
  display: flex;
  justify-content: space-between
  align-items: center
  .tipField
    padding: 4px 10px 4px 10px
    box-sizing: border-box
    color: rgba(15, 15, 15, 0.34);
    font-family: MiSans;
    font-size: 12px;
    color: rgba(255, 255, 255, 1);
    background: rgba(15, 15, 15, 0.34);








      





</style>

效果图如下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值