vue 常用开发小技巧(vue2.7+ts)

306 阅读2分钟

vue2.7+element-ui

下载文件

/** api */
// 批量导入模板下载
export function userTemplateAPI () {
  return request({
    responseType: 'blob',
    method: 'post',
    url: '/api/sys/user/template',
  })
}

/** 业务页面 */
import { downloadFile } from '@/utils'

//导入模板下载
async function downloadTemplate () {
  //下载文件
  try {
    const res: any = await userTemplateAPI()
    downloadFile(res)
  } catch (error) {
    console.log('error: ', error)
  }
}

/** 下载方法 */
export function downloadFile (
  res: NormalizedResponse<Blob>,
  fileName?: string
): void {
  let name = ''
  const { headers, data } = res as any
  try {
    name = decodeURI(
      fileName || (headers['content-disposition'] || '').split('filename=')[1]
    )
    const blob = new Blob([data])
    saveAs(blob, name)
  } catch (e) {
    console.error(e)
  }
}



注: 发送请求时 responseType: 'blob', 是关键

async function handleExport () {
  if(errorSelected.value.length === 0) return
  const res = await erroExportAPI(errorSelected.value)
  const blob = new Blob([res.data], {
    type: 'application/vnd.ms-excel; charset=uft-8', 
  })
  const a = document.createElement('a')
  const url = window.URL.createObjectURL(blob)
  a.href = url
  a.download = '上传失败原因.xlsx' 
  a.click()
  window.URL.revokeObjectURL(url)

}

blob文件转JSON文件

export function batchUserImportAPI (filePath: string) {
  return request({
    method: 'post',
    responseType: 'blob',        //当接口请求存在blob 时
    params: {fileId: filePath},
    url: _c('/api/sys/user/import', false),
  })
}


function blobToJson (blob: Blob) {
  return new Promise((resolve, reject) =>{
    const reader: any = new FileReader()
    reader.onload = () =>{
      resolve(JSON.parse(reader.result))
    }
    reader.onerror = () =>{
      reject(new Error('Unable to read blob as JSON'))
    }
    reader.readAsText(blob)
  })
}

function upAddBatch () {
  //批量新增
  submitLoading.value = true
  batchUserImportAPI(fileIds.value[0]).then((res) => {
    if(res.data.type == 'application/json'){         //json文件时
      const blob = new Blob([res.data], { type: 'application/json' })
      blobToJson(blob).then((data: any) =>{
        if(data?.code == '00000') {
          closed()
          ElMessageBox.alert(data.data, {
            type: 'success',
            confirmButtonText: '知道了'
          }).finally(() => {
            // emits('upload-success')
          })
          return
        }else {
          ElMessage.error(data.message)
        }
        return
      })

    }else{             //下载接口返回的文件
      ElMessage.error('导入失败')
      downloadFile(res as any)
      return
    }
  }).finally(() => {
    submitLoading.value = false
  })
}

el-dropdown-menu 首次点击无效

@click 改为 @click.native
@click 写在 el-dropdown-item 上

<el-dropdown-menu
  :hide-timeout="0"
  class="dropdown-menu"
>
   <el-dropdown-item @click.native="changePassword">
      <span
        style="display: block"
      >
        修改密码
      </span>
    </el-dropdown-item>
    <el-dropdown-item 
      divided
      @click.native="handleLogout"
    >
      <span
        style="display: block"
      >退出登录</span>
    </el-dropdown-item>
</el-dropdown-menu>

el-form 校验提取

const formRules = {
  oldPass: [
    { required: true, message: '请输入密码', trigger: 'blur' },
  ],
  newPass: [
    { required: true, message: '请输入新密码', trigger: 'blur' },
    { validator: checkNewPass, trigger: ['blur'] }
  ],
  confirmPassword: [
    { required: true, message: '请再次输入密码', trigger: 'blur' },
    { validator: validataConfirmPassword, trigger: ['blur'] }
  ],
   name:[
    { required: true, message: '请输入用户姓名', trigger: 'blur' },
    {
      required: true,
      validator: (rule: unknown, value: string, cb: Function) => {
        const regex = /[^\u4e00-\u9fa5\\^a-zA-Z]/g 
        if (!value) {
          cb(new Error('请输入用户账号'))
        } else if(regex.test(value)) {
          cb(new Error('只能输入汉字和英文'))
        }else {
          cb()
        }
      },
      triggers: ['blur', 'change']
    }
  ],
}
function validataConfirmPassword (rule: any, value: string,cb: any) {
  if(!value){
    cb(new Error('请再次输入密码'))
  } else if(value !== formData.value.newPass) {
    cb(new Error('两次密码不一致'))
  } else {
    cb()
  }
}
function checkNewPass (rule: any, value: string,cb: any) {
  const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#¥%&*(){}])[A-Za-z\d!@#¥%&*(){}]{6,}$/
  if(!value){
    cb(new Error('请输入新密码'))
  }else if(value.length < 6 || value.length > 20){
    cb(new Error('密码长度为6-20位'))
  }
  else if(!regex.test(value)) {
    cb(new Error('密码需包含大小写字母、数字、特殊符号组成  特殊字符包含!@#¥%&*(){}'))
  }
}

时间选择器快捷键

 <el-date-picker
  v-model="filterParam.time"
  type="datetimerange"
  :picker-options="pickerOptions"
  range-separator="至"
  start-placeholder="开始日期"
  end-placeholder="结束日期"
  align="right"
>
</el-date-picker>


const pickerOptions = {
  shortcuts: [
    {
      text: "昨天",
      onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
        const end = new Date()
        const start = new Date()
        start.setTime(new Date(new Date().toLocaleDateString()).getTime() - 24 * 60 * 60 * 1000)
        end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
        picker.$emit("pick", [start, end])
      },
    },
    {
      text: "最近一周",
      onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
        const end = new Date()
        const start = new Date()

        end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
        start.setTime(end.getTime() - 3600 * 1000 * 24 * 7 + 1)
        picker.$emit("pick", [start, end])
      },
    },
    {
      text: "最近一个月",
      onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
        const end = new Date()
        const start = new Date()
        end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
        start.setTime(end.getTime() - 3600 * 1000 * 24 * 31 + 1)
        picker.$emit("pick", [start, end])
      },
    },
    {
      text: "最近三个月",
      onClick(picker: { $emit: (arg0: string, arg1: Date[]) => void }) {
        const end = new Date()
        const start = new Date()
        end.setTime(new Date(new Date().toLocaleDateString()).getTime() - 1)
        start.setTime(end.getTime() - 3600 * 1000 * 24 * 31 * 3 + 1)
        picker.$emit("pick", [start, end])
      },
    },
  ],
}


时间选择器未来日期不可选

<el-date-picker
  v-model="filterParam.time[1]"
  type="date"
  placeholder="结束时间"
  :picker-options="setDisabled"
  @change="handleFilterChange"
  >
</el-date-picker>


const setDisabled = {
  disabledDate(time: any) {
    return time.getTime() > Date.now();  // 可选历史天、可选当前天、不可选未来天
    // return time.getTime() > Date.now() - 8.64e7;  // 可选历史天、不可选当前天、不可选未来天
    // return time.getTime() < Date.now() - 8.64e7;  // 不可选历史天、可选当前天、可选未来天
    // return time.getTime() < Date.now(); // 不可选历史天、不可选当前天、可选未来天
  }
}

时间选择器多重条件禁用

时间选择器未来日期不可选&&结束日期不能早于开始日期&&最长选择范围一年

<el-date-picker
  v-model="filterParam.time[0]"
  type="date"
  placeholder="开始时间"
  :picker-options="setDisabledStart"
  @change="search"
></el-date-picker>
<span>至</span>
<el-date-picker
  v-model="filterParam.time[1]"
  type="date"
  placeholder="结束时间"
  :picker-options="setDisabledEnd"
  @change="search"
></el-date-picker>


const filterParam = ref({
  time: [getWeekTime(),dayjs().format()],
})

const setDisabledStart = {
  disabledDate(time: any) {
    //不能选未来,及结束日期一年期那的日期
    return time.getTime() > Date.now() || time.getTime() < +dayjs(filterParam.value.time[1]) - (365 * 24 * 3600 * 1000)
  }
}
const setDisabledEnd = {
  disabledDate(time: any) {
    //不能选未来,开始日期前的日期
    return time.getTime() > Date.now() || time.getTime() < +dayjs(filterParam.value.time[0])
  }
}

自定义下拉框字体颜色

<el-select
  v-model="filterFields.stat"
  placeholder="Select"
  @change="search"
>
  <el-option
    v-for="item in statAll"
    :key="item.value"
    :label="item.label"
    :value="item.value"
    v-html="'<span style=color:' + item.color + '>'+item.label+'</span>'"
  />
</el-select>


const filterFields = ref({
  stat: statAll[0].value //状态
})

const statAll = [
  {
    label:'全部',
    value:'-1',
    color: '#000'
  },
  {
    label:'运行中',
    value:'0',
    color: '#70e17b'
  },  
  { 
    label:'异常',
    value:'1',
    color: '#e62000'
  },
  {
    label:'空闲',
    value:'2',
    color: '#666666'
  },
]

base64图片的显示(验证码)

<el-image :src="'data:image/png;base64,'+captchaSrc" @click="login" /> 

async function getCaptcha (){ 
    await getVerifyApi()
    .then((res) => {
        captchaSrc.value = res.data //base64数据
    }) 
} 
       
onMounted(() => { 
    getCaptcha() 
})
  

Blob 图片显示(验证码)

重点

{ responseType: 'blob' }  是重点
//获取验证码
export function getVerifyApi () {
  return request.get('/verify', { responseType: 'blob' })
}
<el-image
  :src="captchaSrc"
  @click="getCaptcha"
  class="login-form__captcha"
/>

async function getCaptcha (){
  await getVerifyApi().then((res) => {
    const blob = new Blob([res.data], {type: 'image/jpeg'})
    captchaSrc.value = window.URL.createObjectURL(blob)
  })
}
onMounted(() => {
  getCaptcha()
})

Blob 图片显示(验证码)

 <el-image
  :src="captchaSrc"
  @click.native="toggleCaptcha"
  class="login-form__captcha"
>
  <div
    slot="placeholder"
    class="image-slot"
  >
    加载中<span class="dot">...</span>
  </div>
</el-image>

import throttle from 'lodash/throttle'

const captchaSrc = ref('')
const toggleCaptcha = throttle(() => {
  getCaptchaSrc().then(({ data, axiosRes: { headers } }) => {
    const blob = new Blob([data], {type: headers.contentType ?? ''})
    captchaSrc.value = window.URL.createObjectURL(blob)
  })
}, 2000, { trailing: false })


onMounted(() => {
  toggleCaptcha()
  if (loginForm.value.username === '') {
    usernameRef.value?.focus()
  } else if (loginForm.value.password === '') {
    passwordRef.value?.focus()
  }
})

axios 封装

@/utils/request
待完善

import axios from 'axios'

const service = axios.create({
  baseURL,
  timeout: 0
})

export default service

@/utils/index

/**
 * 在开发环境下,拼接 mock api 的请求地址
 * 若程序运行在生产环境中,将原样返回传入的请求地址,不再进行拼接
 *
 * @param path 相对路径, 从 Yapi 平台直接复制即可
 * @param mock 是否需要给路径增加 mock 所需的前缀
 */
export function _c(path: string, mock = false) {
  if (IS_PRODUCTION) {
    return path
  } else {
    return mock
      ? '/mock' + path
      : path
  }
}

@/api/index

import request from '@/utils/request'
import { _c } from '@/utils'

export function getNodeApi(params: any) {
  return request.get(_c('/node/all', isMock), { params: params })
}

export function updataNodeApi(data: addNodeApiType) {
  return request.post(_c('/node', isMock), data)
}

export function addNodeApi(data: addNodeApiType) {
  return request.put(_c('/node', isMock), data)
}

export function delNodeApi(id: string) {
  return request.delete(_c(`/node/${id}`, isMock))
}