step 步骤条

一,step1页面

<template>
  <div
    class="add-data card"
    v-loading="loading"
    :element-loading-text="loadingText"
  >
    <div class="add-data-box">
      <div class="steps">
        <div
          class="step"
          :class="currentComponent === AddForm ? 'action' : 'success'"
        >
          <div class="step-icon">1</div>
          <div class="success-icon"><i class="el-icon-check" /></div>
          <div class="step-name">配置数据信息</div>
        </div>
        <template>
          <div class="step-line-box">
            <i class="step-line" />
          </div>
          <div class="step" :class="{ action: currentComponent !== AddForm }">
            <div class="step-icon">2</div>
            <div class="step-name">元数据管理</div>
          </div>
        </template>
      </div>

      <keep-alive>
        <component
          :is="currentComponent"
          ref="currentComponent"
          :set-loading="setLoading"
          :fields="fields"
          @next="handleNext"
          @handleTypeChange="handleTypeChange"
        />
      </keep-alive>

      <div class="text-center mt-3" v-if="currentComponent === ConfigData">
        <el-button @click="back">上一步</el-button>
        <el-button type="primary" @click="ok">完成</el-button>
      </div>
    </div>
  </div>
</template>

<script>
import AddForm from './AddForm'
import ConfigData from './ConfigData'

import { createMySql, findFiledsList, checkDataSetStatus } from '@/api/dataSet'

import CheckData from './CheckData'

export default {
  components: { AddForm, ConfigData },
  props: {},
  data() {
    return {
      currentComponent: AddForm,
      loading: false,
      AddForm,
      ConfigData,
      formData: null,
      fields: [],
      dialogVisible: true,
      isCsvComp: true,
      loadingText: '',
      sampleId: '',
    }
  },

  created() {
    this.setLoading = this.setLoading.bind(this)
  },

  computed: {},
  methods: {
    handleTypeChange(comp) {
      this.isCsvComp = comp
    },
    setLoading(isLoading, loadingText = '检测中') {
      this.loading = isLoading
      this.loadingText = loadingText
    },
    handleNext(formAndParams) {
      if (formAndParams === null) return

      this.formData = formAndParams.form

      this.setLoading(true, '')

      createMySql(formAndParams.form)
        .then(async res => {
          this.sampleId = res.businessId
          const result = await findFiledsList({ sampleId: res.businessId })
          this.fields = result.sampleFields
          this.currentComponent = ConfigData
        })
        .finally(() => {
          this.setLoading(false, '')
        })
    },
    back() {
      this.currentComponent = AddForm
      this.fields = []
      this.formData = null
      this.sampleId = ''
    },
    async ok() {
      this.setLoading(true, '保存中')
      const { fields } = this
      const checkResult = new CheckData(this.formData, fields).checkAll()

      if (!checkResult.status) {
        this.$message.warning(checkResult.list[0].message)

        this.setLoading(false)

        return
      }

      checkDataSetStatus({
        type: '1',
        sampleId: this.sampleId,
        sampleFields: fields,
      })
        .then(() => {
          this.$message.success('添加成功')
          this.setLoading(false)
          this.$store.commit('DEL_VIEW_TAG', {
            view: this.$route,
            callback: () => {
              this.$router.push({ name: 'DataList' })
            },
          })
        })
        .catch(err => {
          this.setLoading(false)
          this.$message.error(err.respMsg)
        })
        .finally(() => {})
    },
  },
}
</script>

<style lang="scss" scoped>
.add-data {
  padding: 40px 20px 24px 20px;
  margin-bottom: 24px;
  min-height: calc(100% - 88px);
}
.steps,
.step {
  display: flex;
}
.step-name {
  font-size: 14px;
}
/deep/ .el-form-item__label,
/deep/ .el-input,
/deep/ .el-textarea__inner {
  font-size: 12px;
}
.font-twelve {
  /deep/ .el-select-dropdown__item {
    font-size: 12px;
  }
}

.steps {
  justify-content: center;
  height: 34px;
  padding-bottom: 32px;
}

.step {
  & > div {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  &-icon {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1px solid rgba(0, 0, 0, 0.15);
    color: rgba(0, 0, 0, 0.25);
  }
  &-name {
    color: rgba(0, 0, 0, 0.45);
  }
  &.action &-icon {
    background: #e95d21;
    border-color: #e95d21;
    color: #fff;
  }
  &.action &-name {
    color: rgba(0, 0, 0, 0.85);
  }
  .success-icon {
    display: none;
  }
  &.success .success-icon {
    display: flex;
    width: 32px;
    height: 32px;
    border: #e95d21 solid 1px;
    border-radius: 50%;
    color: #e95d21;
  }
  &.success &-icon {
    display: none;
  }
  &-name {
    margin-left: 6px;
  }
  &-line-box {
    width: 160px;
    margin: 0 32px;
    display: flex;
    align-items: center;
  }
  &-line {
    width: 100%;
    height: 1px;
    background: rgba(0, 0, 0, 0.15);
  }
}
</style>
二,step1页面中AddForm组件
 
<template>
  <div style="max-width: 600px; margin: auto">
  <!-- 输入框组件 -->
    <BaseForm
      ref="baseForm"
      @typeChange="handleTypeChange"
      @sampleTypeChange="value => (sampleType = value)"
    />
<!-- 文件上传组件 -->
    <keep-alive>
      <component
        :is="MysqlOrCsvComponent"
        ref="MysqlOrCsvComponent"
        :sample-type="sampleType"
        :set-loading="setLoading"
      />
    </keep-alive>

    <div slot="footer" class="dialog-footer text-center">
      <el-button @click="cancel">取消</el-button>
      <el-button type="primary" @click="next">下一步</el-button>
    </div>
  </div>
</template>

<script>
import BaseForm from './BaseForm'
import CsvTypeForm from './CsvTypeForm'
import MySqlTypeForm from './MySqlTypeForm'

export default {
  components: { BaseForm, CsvTypeForm, MySqlTypeForm },
  props: {
    setLoading: { type: Function, required: true },
  },
  data() {
    return {
      MysqlOrCsvComponent: CsvTypeForm,
      baseForm: null,
      sampleType: '1',
    }
  },
  methods: {
    cancel() {
      this.$store.commit('DEL_VIEW_TAG', {
        view: this.$route,
        callback: nearTag => {
          if (nearTag.last) {
            this.$router.push(nearTag.last.fullPath)
          } else if (nearTag.next) {
            this.$router.push(nearTag.next.fullPath)
          } else {
            this.$router.push('/')
          }
        },
      })
    },
    async next() {
      const baseForm = this.$refs.baseForm.getForm()
      const csvOrMysqlForm = this.$refs.MysqlOrCsvComponent
        ? this.$refs.MysqlOrCsvComponent.getForm()
        : null

      if (baseForm === null || csvOrMysqlForm === null) return

      let params = null

      if (this.MysqlOrCsvComponent === CsvTypeForm) {
        params = { type: '1', path: csvOrMysqlForm.path }
      }

      if (this.MysqlOrCsvComponent === MySqlTypeForm) {
        params = { ...csvOrMysqlForm, type: '2' }
      }

      this.$emit('next', {
        form: { ...baseForm, ...csvOrMysqlForm },
        params,
      })
    },
    handleTypeChange(value) {
      if (value === '1') {
        this.MysqlOrCsvComponent = CsvTypeForm
        this.$emit('handleTypeChange', true)
      } else if (value === '3') {
        this.MysqlOrCsvComponent = MySqlTypeForm
        this.$emit('handleTypeChange', false)
      }
    },
  },
}
</script>
三,step1页面中AddForm组件中的BaseForm
<template>
  <el-form :model="form" label-width="100px" :rules="rules" ref="form">
    <el-form-item label="数据集名称" prop="name">
      <el-input v-model="form.name" placeholder="请输入数据集名称" />
    </el-form-item>
    <el-form-item label="数据集简介" prop="comments">
      <el-input
        v-model="form.comments"
        type="textarea"
        maxlength="200"
        show-word-limit
        :autosize="{ minRows: 2, maxRows: 15 }"
        placeholder="请输入数据集简介"
      />
    </el-form-item>

    <el-form-item label="数据集类型" prop="scope">
      <el-radio
        v-for="item of scopeOptions"
        :key="item.value"
        :label="item.value"
        v-model="form.scope"
        >{{ item.label }}</el-radio
      >
    </el-form-item>

    <el-form-item label="样本集类别" prop="scope">
      <el-radio
        :disabled="form.scope === '2'"
        v-for="item of sampleTypeOptions"
        :key="item.value"
        :label="item.value"
        v-model="form.sampleType"
        >{{ item.label }}</el-radio
      >
    </el-form-item>
    <el-form-item label="数据源类型" prop="type">
      <el-select
        popper-class="zindex-calss font-twelve"
        v-model="form.type"
        @change="typeChange"
      >
        <el-option
          v-for="item of typeOptions"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        />
      </el-select>
    </el-form-item>
  </el-form>
</template>

<script>
import { formNameRule } from '@/utils/validate/formRule'

const createForm = () => ({
  name: '',
  comments: '',
  scope: '1',
  type: '1',
  sampleType: '1',
})
export default {
  data() {
    return {
      form: createForm(),
      rules: {
        name: [
          { required: true, message: '请输入数据集名称', trigger: 'blur' },
          ...formNameRule,
        ],
        comments: [
          { required: false, message: '请输入数据集简介', trigger: 'blur' },
        ],
        scope: [
          { required: true, message: '请选择数据集类型', trigger: 'change' },
        ],
        type: [
          { required: true, message: '请选择数据源类型', trigger: 'change' },
        ],
        sampleType: [
          { required: true, message: '请选择样本集类别', trigger: 'change' },
        ],
      },
      typeOptions: [
        { label: 'CSV', value: '1' },
        { label: 'MySql', value: '3' },
      ],
      scopeOptions: [
        { label: '训练数据集', value: '1' },
        { label: '预测数据集', value: '2' },
      ],
      sampleTypeOptions: [
        { label: 'Y样本集', value: '2' },
        { label: 'X样本集', value: '1' },
      ],
    }
  },
  watch: {
    'form.scope'(value) {
      if (value === '2') {
        this.form.sampleType = '1'
      }
    },
    'form.sampleType'(value) {
      this.$emit('sampleTypeChange', value)
    },
  },
  methods: {
    getForm() {
      let formValid
      this.$refs.form.validate(valid => {
        formValid = valid
      })

      if (formValid) {
        return { ...this.form }
      }

      return null
    },
    typeChange(value) {
      this.$emit('typeChange', value)
    },
    cancel() {
      this.form = createForm()
    },
  },
}
</script>
四,step1页面中AddForm组件中的CsvTypeForm
<template>
  <div>
    <el-form :model="form" label-width="100px" ref="form">
      <el-form-item
        label="上传文件"
        prop="path"
        :rules="[{ required: true, message: '请上传文件', trigger: 'change' }]"
      >
        <el-upload
          ref="upload"
          class="upload"
          action=""
          drag
          :on-remove="handleRemove"
          :limit="1"
          :multiple="false"
          :on-exceed="handleExceed"
          :on-change="onFileChange"
          :auto-upload="false"
        >
          <i class="el-icon-receiving mt-3" style="font-size: 67px" />
          <div class="el-upload__text mt-1 mb-1">
            将文件拖到此处,或<em>点击上传</em>
          </div>
          <div
            class="text-center"
            style="font-size: 12px; color: #ccc; line-height: 20px"
          >
            文件仅支持csv格式,最大限制1G
          </div>
          <div
            slot="tip"
            class="text-right"
            style="width: 360px; font-size: 12px"
          >
            <span>下载模板: </span>
            <el-button type="text" @click="download()">样本集示例</el-button>
          </div>
        </el-upload>
        <div class="progress-box" :style="{ opacity: upload.opacity }">
          <!-- <span>上传中:</span> -->
          <el-progress
            :percentage="upload.rate"
            :status="upload.rate === 100 ? 'success' : void 0"
          />
        </div>
      </el-form-item>
    </el-form>
    <!-- <div slot="footer" class="dialog-footer text-right">
      <el-button @click="last">上一步</el-button>
      <el-button type="primary" @click="next" :loading="loading"
        >确定</el-button
      >
    </div> -->
  </div>
</template>

<script>
//  npm 下载 downloadjs  
import downloadjs from 'downloadjs'
import { createMySql } from '@/api/dataSet'

import createAlluxio from '@/utils/createAlluxio'
import isUTF8 from '@/utils/validate/isUTF8'
import checkCsv from '@/utils/checkCsv'

export default {
  props: {
    sampleType: { type: String, required: true },
    setLoading: { type: Function, required: true },
  },
  data() {
    return {
      form: { path: '', type: '1', tablename: '' },
      path: null,
      filename: '',
      upload: {
        opacity: 0,
        rate: 0,
      },
      loading: false,
      cancelUploadFile: null,
    }
  },
  mounted() {
    // createFilePath()
  },
  methods: {
    cancel() {
      this.$refs.form.resetFields()
      this.$refs.upload.clearFiles()
      this.cancelUploadFile && this.cancelUploadFile('取消上传')
      this.upload.opacity = 0
      this.upload.rate = 0
    },
    last() {
      this.cancel()
      this.$emit('last')
    },

    getForm() {
      const { upload } = this

      let formValid
      this.$refs.form.validate(valid => {
        formValid = valid
      })

      if (!formValid) {
        return null
      }

      if (upload.rate !== 100) {
        this.$message.warning('文件上传中,请稍作等待. . .')
        return null
      }

      return { ...this.form }
    },

    next(baseForm) {
      const { form, upload } = this
      if (upload.opacity === 0 || !form.path) {
        this.$message.warning('请选择上传文件')
        return
      }

      if (upload.rate !== 100) {
        this.$message.warning('文件上传中,请稍作等待. . .')
        return
      }

      const params = { ...baseForm, ...form }
      createMySql(params)
        .then(() => {
          this.$message.success('上传成功')
          this.$emit('next')
        })
        .catch(() => {
          this.$message.error('上传失败')
        })
        .finally(() => {
          this.loading = false
          this.path = null
          this.filename = ''
          this.upload.opacity = 0
          this.upload.rate = 0
        })
    },
    handleRemove() {
      this.cancel()
    },
    handleExceed(files, fileList) {
      this.$message.warning(
        `当前限制选择 1 个文件,请先移移除已添加的${fileList[0].name}`,
      )
    },

    async onFileChange(file, fileList) {
      const { name } = file
      const fileType = name.split('.').pop()

      if (fileType !== 'csv') {
        this.$message('上传文件类型只能为 csv 后缀的文件')
        fileList.pop()
        return
      }

      if (file.size === 0) {
        this.$message('不能上传空文件')
        fileList.pop()
        return
      }

      const size = file.size / 1024 / 1024 / 1024

      if (size > 1) {
        this.$message('上传文件最大限制1G')
        fileList.pop()
        return
      }

      this.setLoading(true, '样本检测中...')

      const fileContent = await new Promise(resolve => {
        async function Uint8ArrayToString(u8arr) {
          console.time('up')
          const info = await checkCsv(u8arr)
          console.timeEnd('up')
          console.log(info)

          return info
        }

        const reader = new FileReader()
        reader.onload = async function(e) {
          const data = e.target.result
          const u8arr = new Uint8Array(data)

          const isNoUTF8 = !isUTF8(u8arr)

          const result = { isNoUTF8 }

          if (!isNoUTF8) {
            result.csvInfo = await Uint8ArrayToString(u8arr)
          }

          resolve(result)
        }
        reader.readAsArrayBuffer(file.raw)
      })

      if (fileContent.isNoUTF8) {
        this.setLoading(false)
        this.$message('上传文件编码只能为UTF8格式')
        fileList.pop()
        this.upload.opacity = 0
        return
      }

      const csvInfo = fileContent.csvInfo
      let isCsvError = false

      if (csvInfo.rowError.length !== 0) {
        this.setLoading(false)
        isCsvError = true
        this.$confirm('csv文件第一行为空', '提示', {
          confirmButtonText: '确定',
          showCancelButton: false,
          type: 'warning',
        })
          .then(() => fileList.pop())
          .catch(() => fileList.pop())
      } else if (csvInfo.columnError.length !== 0) {
        this.setLoading(false)
        isCsvError = true
        let text = `csv文件第一行第${csvInfo.columnError.join('、')}列为空`
        if (csvInfo.columnError.length > 200) {
          const arr = csvInfo.columnError.slice(0, 200)
          text = `csv文件第一行总计${
            csvInfo.bodyColumnError.length
          }列为空:第${arr.join('、')}......列`
        }
        this.$confirm(text, '提示', {
          confirmButtonText: '确定',
          showCancelButton: false,
          type: 'warning',
        })
          .then(() => fileList.pop())
          .catch(() => fileList.pop())
      } else if (!csvInfo.bodyStart) {
        this.setLoading(false)
        isCsvError = true
        this.$confirm('csv文件数据为空', '提示', {
          confirmButtonText: '确定',
          showCancelButton: false,
          type: 'warning',
        })
          .then(() => fileList.pop())
          .catch(() => fileList.pop())
      } else if (csvInfo.bodyColumnError.length !== 0) {
        // this.setLoading(false)
        isCsvError = true
        let text = `csv文件第${csvInfo.bodyColumnError.join('、')}行列数不正确`
        if (csvInfo.bodyColumnError.length > 200) {
          const arr = csvInfo.bodyColumnError.slice(0, 200)
          text = `csv文件总计${
            csvInfo.bodyColumnError.length
          }行列数不正确:第${arr.join('、')}......行`
        }
        this.$confirm(text, '提示', {
          confirmButtonText: '确定',
          showCancelButton: false,
          type: 'warning',
        })
          .then(() => fileList.pop())
          .catch(() => fileList.pop())
      }

      this.setLoading(false)
      if (isCsvError) {
        return
      }

      this.upload.opacity = 1

      const alluxio = createAlluxio(
        rate => {
          this.upload.rate = rate
        },
        cancel => {
          this.cancelUploadFile = cancel
        },
      )

      const [err, filePath] = await alluxio.upload(file.raw)

      if (err === null) {
        this.form.path = filePath
        this.form.tablename = name
        this.$refs.form.validate()
        this.$message.success('文件上传成功')
      } else {
        fileList.pop()
        err !== '' && this.$message.error(err)
      }
    },

    download() {
      const name = this.sampleType === '1' ? 'Host' : 'Guest'
      const path = `/excel/${name}.csv`
      downloadjs(path)
    },
  },
}
</script>

<style lang="scss" scoped>
.el-upload__text-limit {
  font-size: 14px;
  font-weight: 400;
  color: rgba(0, 0, 0, 0.45);
  line-height: 22px;
}
.upload {
  line-height: normal;

  /deep/ .el-upload-list__item:first-child {
    margin-top: 0;
  }
}
/deep/ .el-button {
  font-size: 12px;
}
</style>
五,step1页面中AddForm组件中的MySqlTypeForm
<template>
  <div>
    <el-form :model="sqlForm" label-width="100px" :rules="rules" ref="sqlForm">
      <el-form-item label="数据源名称" prop="dataLinkName">
        <el-input
          v-model="sqlForm.dataLinkName"
          placeholder="请输入数据源名称"
        />
      </el-form-item>
      <el-form-item label="数据源地址" prop="link">
        <el-input v-model="sqlForm.link" placeholder="请输入数据源地址" />
      </el-form-item>
      <el-form-item label="数据源端口" prop="dataPort">
        <el-input v-model="sqlForm.dataPort" placeholder="请输入数据源端口" />
      </el-form-item>
      <el-form-item label="数据库名称" prop="dataName">
        <el-input v-model="sqlForm.dataName" placeholder="请输入数据库名称" />
      </el-form-item>
      <el-form-item label="数据库表" prop="tablename">
        <el-input v-model="sqlForm.tablename" placeholder="请输入数据库表名" />
      </el-form-item>
      <el-form-item label="数据库用户名" prop="username">
        <el-input v-model="sqlForm.username" placeholder="请输入数据库用户名" />
      </el-form-item>
      <el-form-item label="数据库密码" prop="password">
        <el-input v-model="sqlForm.password" placeholder="请输入数据库密码" />
      </el-form-item>
      <el-form-item label="ID列" prop="labelId">
        <el-input
          v-model="sqlForm.labelId"
          placeholder="请输入数据集中的ID列"
        />
      </el-form-item>
      <el-form-item label="标签列" prop="labelColumn">
        <el-input
          v-model="sqlForm.labelColumn"
          placeholder="请输入数据集中的标签列"
        />
      </el-form-item>
      <div class="text-right" style="width: 108px">
        <el-button
          type="text"
          :loading="connectLoading"
          @click="connect"
          style="font-size:12px"
          >连通测试</el-button
        >
      </div>
    </el-form>
    <!-- <div slot="footer" class="dialog-footer text-right">
      <el-button @click="last">上一步</el-button>
      <el-button type="primary" @click="next">确定</el-button>
    </div> -->
  </div>
</template>

<script>
import formRule, { formNameRule } from '@/utils/validate/formRule'

const formSqlRule = [
  formRule.noBlank,
  formRule.max,
  {
    pattern: /^[^\u4e00-\u9fa5]*$/,
    message: '不能输入中文字符',
    trigger: ['change', 'blur'],
  },
  {
    pattern: /^[\w-]*$/,
    message: '不能包含 “中划线,下划线” 之外的特殊字符',
    trigger: ['change', 'blur'],
  },
]

import { connectMySql, createMySql } from '@/api/dataSet'
const createForm = () => ({
  dataLinkName: '',
  link: '',
  dataPort: '',
  dataName: '',
  tablename: '',
  username: '',
  passsword: '',
  labelId: '',
  labelColumn: '',
})
export default {
  props: ['form', 'visible'],
  data() {
    return {
      sqlForm: createForm(),
      rules: {
        dataLinkName: [
          { required: true, message: '请输入数据源名称', trigger: 'blur' },
          ...formNameRule,
        ],
        link: [
          { required: true, message: '请输入数据源地址', trigger: 'blur' },
          formRule.noBlank,
          formRule.createMax(200),
        ],
        dataPort: [
          { required: true, message: '请输入数据源端口', trigger: 'change' },
          {
            pattern: /^[0-9]{1,10}$/,
            message: '只能输入10位及10位以内的数字',
            trigger: ['change', 'blur'],
          },
        ],
        dataName: [
          { required: true, message: '请输入数据库名称', trigger: 'change' },
          ...formSqlRule,
        ],
        tablename: [
          { required: true, message: '请输入数据库表名', trigger: 'change' },
          ...formSqlRule,
        ],
        username: [
          { required: true, message: '请输入数据库用户名', trigger: 'change' },
          ...formSqlRule,
        ],
        password: [
          { required: true, message: '请输入数据库密码', trigger: 'change' },
          formRule.noBlank,
          formRule.max,
          {
            pattern: /^[^\u4e00-\u9fa5]*$/,
            message: '不能输入中文字符',
            trigger: ['change', 'blur'],
          },
        ],
        labelId: [
          { required: false, message: '请输入ID列', trigger: 'change' },
          ...formSqlRule,
        ],
        labelColumn: [...formSqlRule],
      },
      typeOptions: [
        { label: 'CSV', value: '1' },
        { label: 'MySql', value: '3' },
      ],
      scopeOptions: [
        { label: '训练数据集', value: '1' },
        { label: '预测数据集', value: '2' },
      ],
      connectLoading: false,
      addLoading: false,
    }
  },
  methods: {
    getForm() {
      let formValid
      this.$refs.sqlForm.validate(valid => {
        formValid = valid
      })

      if (formValid) {
        return { ...this.sqlForm }
      }

      return null
    },
    next(baseForm) {
      this.$refs.sqlForm.validate(valid => {
        if (valid) {
          this.addLoading = true
          const params = { ...baseForm, ...this.sqlForm }
          // params.username = params.userName
          // delete params.userName
          createMySql(params)
            .then(res => {
              console.log(res)
              this.sqlForm = createForm()
              this.$refs.sqlForm.resetFields()
              this.addLoading = false
              this.$message.success('添加成功')
              this.$emit('next')
            })
            .catch(() => {
              this.sqlForm = createForm()
              this.addLoading = false
              this.$message.error('添加失败')
            })
        }
      })
    },
    last() {
      this.sqlForm = createForm()
      this.$refs.sqlForm.resetFields()
      this.connectLoading = false
      this.$emit('last')
    },
    connect() {
      console.log(this.$refs.sqlForm)
      this.$refs.sqlForm.validate(valid => {
        if (valid) {
          this.connectLoading = true
          const { sqlForm } = this
          const {
            link,
            dataPort,
            dataName,
            tablename,
            username,
            password,
            labelId,
            labelColumn,
          } = sqlForm
          const params = {
            address: link + ':' + dataPort,
            idColumnName: labelId,
            labelColumnName: labelColumn,
            tableName: tablename,
            dataName,
            username,
            password,
          }
          connectMySql(params, {
            timeout: 60000 * 60,
          })
            .then(() => {
              if (this.connectLoading) {
                this.$message.success('连接成功')
              } else {
                this.$message.success('连接失败')
              }
            })
            .finally(() => {
              this.connectLoading = false
            })
        }
      })
    },
  },
}
</script>
六,step1页面中AddForm组件中的CsvTypeForm页面中createAlluxio组件
import axios from 'axios'
import { getAlluxioConfig } from '@/api/dataSet'

const CancelToken = axios.CancelToken

/**
 * 接收两个 callback, 并返回 alluiox 实例,上传文件时调用实例上的 upload 方法
 * @param {(rate: number) => undefined} onUploadProgress - 文件上传进度
 * @param {(cancel: Function) => undefined} setCancel - 设置取消上传的回调
 *
 * @return {Alluxio}
 * @typedef {Object} Alluxio
 * @property {string} basePath - alluxio 服务地址
 * @property {string} filePath - alluxio 文件存储路径
 * @property {() => Promise<undefined>} getAlluxioConfig - 获取 alluxio 服务地址及文件存储路径
 * @property {(fileName: string) => Promise<boolean>} isFile - 判断文件是否已存在
 * @property {(fileName: string) => Promise<{fileId: number, fileName: string}>} createFile - 创建文件并返回文件id及文件名
 * @property {(fileId: number, file: File) => Promise<undefined>} writeFile - 写入文件流
 * @property {(fileId: number) => Promise<undefined>} closeFile - 关闭文件流
 * @property {(file: File) => Promise<[?string, (string|undefined)]>} upload - 上传文件
 *
 */
export default function createAlluxio(onUploadProgress, setCancel) {
  const request = axios.create({
    cancelToken: new CancelToken(cancel => {
      setCancel(() => cancel('取消'))
    }),
  })

  const versions = '/api/v1'

  return {
    basePath: '',
    filePath: '',
    async getAlluxioConfig() {
      const {
        data: { BASE_PATH, FILE_PATH },
      } = await getAlluxioConfig(null, {
        cancelToken: null,
      })

      let basePath = BASE_PATH.replace(/\/$/, '') + versions
      let filePath = FILE_PATH.replace(/\/$/, '')

      if (basePath.substring(0, 4) !== 'http') {
        basePath = `http://${basePath}`
      }

      if (filePath.substring(0, 1) !== '/') {
        filePath = `/${filePath}`
      }

      this.basePath = basePath
      this.filePath = filePath
    },

    async isFile(fileName) {
      const url = `${this.basePath}/paths/${this.filePath}/${fileName}/exists`
      const { data } = await request.post(url, {})

      return data
    },

    async createFile() {
      let fileName = generateFileName()

      while (await this.isFile(fileName)) {
        fileName = generateFileName()
      }

      const url = `${this.basePath}/paths/${this.filePath}/${fileName}/create-file`
      const { data } = await request.post(url, {})

      return { fileId: data, fileName }
    },

    async writeFile(fileId, file) {
      const url = `${this.basePath}/streams/${fileId}/write`
      const { data } = await request.post(url, file, {
        headers: { 'Content-Type': 'application/octet-stream' },
        onUploadProgress({ loaded, total }) {
          onUploadProgress(Math.floor((loaded / total) * 99))
        },
      })

      return data
    },

    async closeFile(fileId) {
      const url = `${this.basePath}/streams/${fileId}/close`
      const { data } = await request.post(
        url,
        {},
        {
          onUploadProgress({ loaded, total }) {
            onUploadProgress(Math.floor((loaded / total) * 1 + 99))
          },
        },
      )

      return data
    },

    async upload(file) {
      await this.getAlluxioConfig()

      try {
        const { fileId, fileName } = await this.createFile()

        await this.writeFile(fileId, file)
        await this.closeFile(fileId)

        return [null, `${this.filePath}/${fileName}`]
      } catch (err) {
        console.log(err)
        // 请求超时或者网络有问题
        if (err.message === '取消') {
          // 取消重复请求 返回空字符串
          return ['']
        } else if (err.message.includes('timeout')) {
          return ['请求超时!请检查网络是否正常']
        } else if (err.message.includes('Network Error')) {
          return ['网络连接错误']
        } else {
          return ['请求失败,请检查网络是否已连接']
        }
      }
    },
  }
}

/**
 * 生成guid作为文件名称
 * @return {string}
 */
function generateFileName() {
  let d = new Date().getTime()
  const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(
    c,
  ) {
    var r = (d + Math.random() * 16) % 16 | 0
    d = Math.floor(d / 16)
    return (c == 'x' ? r : (r & 0x7) | 0x8).toString(16)
  })

  return uuid + '.csv'
}
七,step1页面中AddForm组件中的CsvTypeForm页面中isUTF8组件
export default function isUTF8(bytes) {
  // UTF-8 BOM
  if (bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf) {
    return false
  }
  let i = 0
  while (i < bytes.length) {
    if (
      // ASCII
      bytes[i] == 0x09 ||
      bytes[i] == 0x0a ||
      bytes[i] == 0x0d ||
      (0x20 <= bytes[i] && bytes[i] <= 0x7e)
    ) {
      i += 1
      continue
    }

    if (
      // non-overlong 2-byte
      0xc2 <= bytes[i] &&
      bytes[i] <= 0xdf &&
      0x80 <= bytes[i + 1] &&
      bytes[i + 1] <= 0xbf
    ) {
      i += 2
      continue
    }

    if (
      // excluding overlongs
      (bytes[i] == 0xe0 &&
        0xa0 <= bytes[i + 1] &&
        bytes[i + 1] <= 0xbf &&
        0x80 <= bytes[i + 2] &&
        bytes[i + 2] <= 0xbf) || // straight 3-byte
      (((0xe1 <= bytes[i] && bytes[i] <= 0xec) ||
        bytes[i] == 0xee ||
        bytes[i] == 0xef) &&
        0x80 <= bytes[i + 1] &&
        bytes[i + 1] <= 0xbf &&
        0x80 <= bytes[i + 2] &&
        bytes[i + 2] <= 0xbf) || // excluding surrogates
      (bytes[i] == 0xed &&
        0x80 <= bytes[i + 1] &&
        bytes[i + 1] <= 0x9f &&
        0x80 <= bytes[i + 2] &&
        bytes[i + 2] <= 0xbf)
    ) {
      i += 3
      continue
    }

    if (
      // planes 1-3
      (bytes[i] == 0xf0 &&
        0x90 <= bytes[i + 1] &&
        bytes[i + 1] <= 0xbf &&
        0x80 <= bytes[i + 2] &&
        bytes[i + 2] <= 0xbf &&
        0x80 <= bytes[i + 3] &&
        bytes[i + 3] <= 0xbf) || // planes 4-15
      (0xf1 <= bytes[i] &&
        bytes[i] <= 0xf3 &&
        0x80 <= bytes[i + 1] &&
        bytes[i + 1] <= 0xbf &&
        0x80 <= bytes[i + 2] &&
        bytes[i + 2] <= 0xbf &&
        0x80 <= bytes[i + 3] &&
        bytes[i + 3] <= 0xbf) || // plane 16
      (bytes[i] == 0xf4 &&
        0x80 <= bytes[i + 1] &&
        bytes[i + 1] <= 0x8f &&
        0x80 <= bytes[i + 2] &&
        bytes[i + 2] <= 0xbf &&
        0x80 <= bytes[i + 3] &&
        bytes[i + 3] <= 0xbf)
    ) {
      i += 4
      continue
    }
    return false
  }
  return true
}
八,step1页面中AddForm组件中的CsvTypeForm页面中checkCsv组件
const LF_CODE = 10
const CR_CODE = 13
const SPACE_CODE = 32
const QUOTE_CODE = 34
const COMMA_CODE = 44

function checkSpace(start, end, u8arr) {
  if (start === undefined) {
    return true
  }
  for (let i = start; i < end; i++) {
    if (u8arr[i] !== SPACE_CODE) {
      return false
    }
  }
  return true
}

function checkEmptyRow(index, u8arr) {
  if (
    index === 0 ||
    u8arr[index - 1] === LF_CODE ||
    u8arr[index - 1] === CR_CODE
  ) {
    return true
  }
  return false
}

export function getHeadInfo(u8arr) {
  let openQuote = false
  let start = undefined
  let end = undefined

  const info = {
    columnNumber: 0,
    rowError: [],
    columnError: [],
    bodyStart: undefined,
  }

  for (let i = 0; i < u8arr.length; i++) {
    if (!openQuote && start === undefined && u8arr[i] === QUOTE_CODE) {
      openQuote = true
      start = i + 1
    } else if (openQuote) {
      if (u8arr[i] === QUOTE_CODE) {
        if (u8arr[i + 1] === QUOTE_CODE) {
          i++
        } else {
          openQuote = false
          end = i
        }
      } else if (u8arr[i] === LF_CODE || u8arr[i] === CR_CODE) {
        openQuote = false
        i--
      }
    } else if (u8arr[i] === COMMA_CODE) {
      if (end === undefined) {
        end = i
      }

      info.columnNumber += 1

      if (checkSpace(start, end, u8arr)) {
        info.columnError.push(info.columnNumber)
      }

      start = undefined
      end = undefined
    } else if (u8arr[i] === LF_CODE || u8arr[i] === CR_CODE) {
      if (checkEmptyRow(i, u8arr)) {
        info.rowError.push(1)
        return info
      } else {
        if (end === undefined) {
          end = i
        }

        info.columnNumber += 1
        if (checkSpace(start, end, u8arr)) {
          info.columnError.push(info.columnNumber)
        }
      }

      if (u8arr[i] === CR_CODE && u8arr[i + 1] === LF_CODE) {
        info.bodyStart = i + 2
      } else {
        info.bodyStart = i + 1
      }
      return info
    } else if (start === undefined) {
      start = i
    }
  }
  return info
}

const sleep = () => new Promise(res => setTimeout(res, 3000))

/**
 * 检测csv文件是否合规
 * 通过遍历 uint8Array 判断 ascii 码识别行、列
 * ascii code 10=\n 13=\r 34="  44=,
 * csv分行符有 \n, \r, \r\n 三种,escape characte
 * 分列符 英文半角逗号 ','
 * 单元格内容中出现分隔符时,单元格内容会被半角双引号包裹。 例:"1,",2 转换为 ['1,', '2']
 * 单元格内容中包含双引号时,需要使用2个双引号,第一个双引号作为转义符。例:"1"",",2 转换为 ['1",', '2']
 * @param {*} csv
 * @returns
 */
export default async function checkCsv(u8arr) {
  let openQuote = false
  let start = undefined

  let rowNumber = 1
  let columnNumber = 0
  const columnError = []
  const rowError = []

  const headInfo = getHeadInfo(u8arr)
  const isheadInfo =
    headInfo.rowError.length !== 0 || headInfo.columnError.length !== 0
  if (!headInfo.bodyStart || isheadInfo) {
    return headInfo
  }

  const sleep = () => new Promise(res => setTimeout(res, 0))

  const headColumnNumber = headInfo.columnNumber

  for (let i = headInfo.bodyStart; i < u8arr.length; i++) {
    if (i % 1e7 === 0) {
      await sleep()
    }
    if (!openQuote && start === undefined && u8arr[i] === QUOTE_CODE) {
      openQuote = true
      start = i + 1
    } else if (openQuote) {
      if (u8arr[i] === QUOTE_CODE) {
        if (u8arr[i + 1] === QUOTE_CODE) {
          i++
        } else {
          openQuote = false
        }
      } else if (u8arr[i] === LF_CODE || u8arr[i] === CR_CODE) {
        openQuote = false
        i--
      }
    } else if (u8arr[i] === COMMA_CODE) {
      columnNumber += 1

      start = undefined
    } else if (u8arr[i] === LF_CODE || u8arr[i] === CR_CODE) {
      rowNumber += 1
      if (checkEmptyRow(i, u8arr)) {
        rowError.push(rowNumber)
      } else {
        columnNumber += 1

        if (headColumnNumber !== columnNumber) {
          columnError.push(rowNumber)
        }
        columnNumber = 0
      }

      if (u8arr[i] === CR_CODE && u8arr[i + 1] === LF_CODE) {
        i++
      }
    } else if (start === undefined) {
      start = i
    }
  }

  if (
    u8arr[u8arr.length - 1] === LF_CODE ||
    u8arr[u8arr.length - 1] === CR_CODE
  ) {
    rowNumber += 1
    rowError.push(rowNumber)
  } else {
    columnNumber += 1
    if (headColumnNumber !== columnNumber) {
      columnError.push(rowNumber)
    }
  }

  headInfo.bodyRowError = rowError
  headInfo.bodyColumnError = columnError
  return headInfo
}
posted @ 2022-06-20 17:44  xiaoxiao95  阅读(68)  评论(0编辑  收藏  举报