欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

vue3.0 搭建项目总结(详细步骤)

程序员文章站 2023-12-09 18:50:27
1.环境配置 项目中的不同开发环境有很多依赖配置,所以可以根据环境设置不同的配置,以免在不同环境经常修改文件 1 在根目录下创建 `.env.[环境]` 文件,可以在不...

1.环境配置

项目中的不同开发环境有很多依赖配置,所以可以根据环境设置不同的配置,以免在不同环境经常修改文件

1 在根目录下创建 `.env.[环境]` 文件,可以在不同环境设置一些配置变量,如图

vue3.0 搭建项目总结(详细步骤)

vue3.0 搭建项目总结(详细步骤) 

.env.dev 文件

2.eslint 配置

在package.json 文件里面有一个eslintconfig对象,可设置rules: 如图

vue3.0 搭建项目总结(详细步骤)

3.配置svg

在vue.config.js 里面需在module.exports对象里面设置

chainwebpack: config => {
  config.module.rules.delete('svg') // 重点:删除默认配置中处理svg,//const svgrule = config.module.rule('svg') //svgrule.uses.clear()
  config.module
   .rule('svg-sprite-loader')
   .test(/\.svg$/)
   .use('svg-sprite-loader')
   .loader('svg-sprite-loader')
   .options({
    symbolid: 'icon-[name]'
   })
 }

svg component

<template>
 <svg :class="svgclass" aria-hidden="true">
  <use :xlink:href="iconname" rel="external nofollow" />
 </svg>
</template>

<script>
export default {
 name: 'svgicon',
 props: {
  iconclass: {
   type: string,
   required: true
  },
  classname: {
   type: string,
   default: ''
  }
 },
 computed: {
  iconname() {
   return `#icon-${this.iconclass}`
  },
  svgclass() {
   if (this.classname) {
    return 'svg-icon ' + this.classname
   } else {
    return 'svg-icon'
   }
  }
 }
}
</script>

<style scoped>
.svg-icon {
 width: 1em;
 height: 1em;
 vertical-align: -0.15em;
 fill: currentcolor;
 overflow: hidden;
}
</style>

```

使用svg组件

import svgicon from '@/components/svgicon.vue'
// 设置全局组件svgicon
vue.component('svg-icon', svgicon)
const req = require.context('./assets/svg', true, /\.svg$/) // 查询文件加下面的svg文件
const requireall = requirecontext => requirecontext.keys().map(requirecontext)
requireall(req) // 全局导入svg文件

2.通用组件

级联(多选且可以选择全部)组件

安装插件 multi-cascader-base-ele

使用

import multicascader from 'multi-cascader-base-ele'
vue.use(multicascader)

-- 支持选择全部

<template>
 <div>
  <multitestcascader v-model="selectedoptions" class="multi-cascader" :props="customprops" :options="options" multiple filterable select-children :show-all-levels="false" clearable only-out-put-leaf-node @change="cascaderchange" />
 </div>
</template>
<script>
export default {
 props: {
 // 传入级联列表数据
  options: {
   type: array,
   default: () => []
  },
  // 传入选择数据
  list: {
   type: array,
   default: () => []
  },
  // 自定义相关字段
  customprops: {
   type: object,
   default: () => {
    return {
     label: 'label',
     value: 'value',
     children: 'children'
    }
   }
  },
  // 显示全部类型 1 全部二级/全部三级 2 全部二级分类/全部三级分类 3 全省/全市
  type: {
   type: string,
   default: () => '1'
  }
 },
 data() {
  return {
   selectedoptions: this.list,
   liststatus: true
  }
 },
 created() {

 },
 watch: {
  options(newvalue, oldvalue) {
   this.setlistdisabled(newvalue)
   this.addalllabel(newvalue)
  },
  list(newvalue) {
   if (this.liststatus) {
    this.cascaderchange(newvalue)
    this.liststatus = false
   }
  }
 },
 mounted() {
  this.setlistdisabled(this.options)
  this.addalllabel(this.options)
 },
 methods: {
  addalllabel(list) {
   list.foreach(val => {
    if (val[this.customprops.children] && val[this.customprops.children].length > 0 && val[this.customprops.children][0][this.customprops.label] !== (this.type === '1' ? '全部一级' : (this.type === '2' ? '全部一级分类' : (this.type === '3' ? '全省' : '')))) {
     if (val[this.customprops.children].length > 1) {
      val[this.customprops.children].unshift({
       [this.customprops.label]: this.type === '1' ? '全部二级' : (this.type === '2' ? '全部二级分类' : (this.type === '3' ? '全省' : '')),
       [this.customprops.value]: val[this.customprops.value],
       [this.customprops.children]: null
      })
     }
     val[this.customprops.children].foreach(v => {
      if (v[this.customprops.children] && v[this.customprops.children].length > 1 && v[this.customprops.children][0][this.customprops.label] !== (this.type === '1' ? '全部二级' : (this.type === '2' ? '全部二级分类' : (this.type === '3' ? '全省' : '')))) {
       if (v[this.customprops.children].length > 1) {
        v[this.customprops.children].unshift({
         [this.customprops.label]: this.type === '1' ? '全部三级' : (this.type === '2' ? '全部三级分类' : (this.type === '3' ? '全市' : '')),
         [this.customprops.value]: v[this.customprops.value],
         [this.customprops.children]: null
        })
       }
      }
     })
    }
   })
  },
  setlistdisabled(list) {
   const label = this.customprops.label
   const value = this.customprops.value
   const children = this.customprops.children
   list.foreach(val => {
    val.disabled = false
    if (val[children]) this.setlistdisabled(val[children])
   })
  },
  cascaderchange(itemlist) {
   if (!itemlist || itemlist.length === 0) {
    this.selectedoptions = []
   }
   this.setlistdisabled(this.options)
   const label = this.customprops.label
   const value = this.customprops.value
   const children = this.customprops.children
   this.options.foreach((v, l) => {
    this.selectedoptions.foreach(val => {
     if (val[0] === '-1') {
      if (v[value] !== '-1') v.disabled = true
      else v.disabled = false
      if (v[children] && v[children].length > 0) {
       v[children].foreach(c => { c.disabled = true })
      }
     } else {
      if (v[value] === '-1') v.disabled = true
      else v.disabled = false
      if (v[children] && v[children].length > 0) {
       v[children].foreach(c => { c.disabled = false })
      }
     }
     if (val.length === 2 && v[value] === val[0] && v[children]) {
      v[children].foreach((item, num) => {
       item.disabled = false
       if (val[0] === val[1] && item[value] === val[1]) {
        item.disabled = false
       } else {
        if (val[0] === val[1] && num !== 0) {
         item.disabled = true
         if (item[children]) {
          item[children].foreach(i => {
           i.disabled = true
          })
         }
        }
        if (val[0] !== val[1] && num === 0 && v[children].length > 1) item.disabled = true
       }
       // this.options[l][children][0].disabled = true
      })
     }
     if (val.length === 3 && v[value] === val[0] && v[children]) {
      v[children].foreach((item, j) => {
       // let status = false
       if (item[children] && val[1] === item[value]) {
        item.disabled = false
        item[children].foreach((i, index) => {
         i.disabled = false
         if (i[value] === val[2]) status = true
         if (i[value] === val[2] && val[1] === val[2]) {
          i.disabled = false
         } else {
          if (val[1] !== val[2] && index === 0 && v[children].length > 1) i.disabled = true
          if (val[1] === val[2] && index !== 0) i.disabled = true
         }
        })
        // this.options[0].disabled = true
        this.options[l][children][0].disabled = true
        // return status
       }
      })
     }
    })
   })
   this.selectedoptions = this.selectedoptions.map(val => {
    if (val.length === 2 && val[0] === val[1]) return [val[0]]
    if (val.length === 1 && val[0] === '-1') return [val[0]]
    if (val.length === 3 && val[1] === val[2]) return [val[0], val[1]]
    return val
   })
   const item = this.selectedoptions[this.selectedoptions.length - 1]
   const length = this.selectedoptions.length
   let status = -1
   this.selectedoptions.some((val, index) => {
    if ((length - 1) === index) return true
    if (item.length === val.length) {
     if (item.join(',') === val.join(',')) {
      status = 1
      return true
     }
    }
    if (item.length > val.length) {
     if (item.join(',').includes(val.join(','))) {
      status = 2
      return true
     }
    }
    if (val.length > item.length) {
     if (val.join(',').includes(item.join(','))) {
      status = 3
      return true
     }
    }
   })
   if (status !== -1) {
    this.selectedoptions.splice(this.selectedoptions.length - 1, 1)
   }
   this.$emit('update:list', this.selectedoptions)
  }
 }
}
</script>

上传(支持图片/视频/裁剪图片/拖拽)

安装插件

vuedraggable axios vue-cropper

代码

<!-- -->
<template>
 <div class="image-draggable">
  <draggable v-model="draggablelist" @end="onend">
   <!-- <transition-group> -->
   <div v-for="(item, index) in draggablelist" :key="index" class="image-list">
    <template v-if="item.isimg">
     <img :src="item.displayurl" alt="" srcset="" style="width: 148px; height: 148px;">
     <div class="icon">
      <span @click="viewimage(item.displayurl)">
       <svg-icon icon-class="view" class="icon-size" style="margin-right: 10px;"></svg-icon>
      </span>
      <span @click="remove(index)">
       <svg-icon icon-class="delete" class="icon-size"></svg-icon>
      </span>
     </div>
    </template>
    <template v-if="!item.isimg">
     <video :src="item.displayurl" :ref="item.id" :id="item.id" :poster="item.coverurl" style="width: 148px; height: 148px;">
     </video>
     <div class="icon">
      <span v-if="item.isplay" @click="play(item)" class="video-icon">
       <svg-icon icon-class="play" class="icon-size"></svg-icon>
      </span>
      <span v-if="!item.isplay" @click="pause(item)" class="video-icon">
       <svg-icon icon-class="pause" class="icon-size"></svg-icon>
      </span>
      <span @click="fullplay(item)" class="video-icon">
       <svg-icon icon-class="full" class="icon-size"></svg-icon>
      </span>
      <span @click="remove(index)">
       <svg-icon icon-class="delete" class="icon-size"></svg-icon>
      </span>
     </div>
    </template>
   </div>

   <!-- </transition-group> -->
  </draggable>
  <el-upload :id="uploadid" :disabled="isdiabled" :action="uploadurl" class="image-upload" :headers="headers" :accept="accept" list-type="picture-card" :show-file-list="false" :on-preview="handlepicturecardpreview" :on-progress="handleprogress" :on-change="filechange" :auto-upload="!iscropper" :on-remove="handleremove" :on-success="imagesuccess" :before-upload="filebeforeupload">
   <i class="el-icon-plus"></i>
   <el-progress :percentage="percentage" v-if="isupload && isloading" :show-text="false"></el-progress>
  </el-upload>
  <el-dialog :visible.sync="dialogvisible">
   <img width="100%" :src="dialogimageurl" alt="">
  </el-dialog>
  <el-dialog :visible.sync="modifycropper">
   <div :style="{height: (autocropheight + 100) + 'px'}">
    <vuecropper ref="cropper" :img="imgsrc" :outputsize="option.size" :outputtype="option.outputtype" :info="true" :full="option.full" :canmove="option.canmove" :canmovebox="option.canmovebox" :original="option.original" :autocrop="option.autocrop" :autocropheight="autocropheight" :autocropwidth="autocropwidth" :fixedbox="option.fixedbox" @realtime="realtime" @imgload="imgload"></vuecropper>
   </div>
   <span slot="footer" class="dialog-footer">
    <el-button @click="modifycropper = false">取 消</el-button>
    <el-button type="primary" @click="uploadcropperimage">确 定</el-button>
   </span>
  </el-dialog>
 </div>
</template>

<script>
// 拖拽
import draggable from 'vuedraggable'
// 裁剪
import { vuecropper } from 'vue-cropper'
// 上传地址
import { upload } from '@/api'
import { gettoken } from '@/util/auth'
import axios from 'axios'

export default {
 name: '',
 data() {
  return {
   headers: {
    authorization: gettoken()
   },
   uploadurl: upload,
   displayurl: '',
   dialogimageurl: '',
   dialogvisible: false,
   percentage: 0,
   accept: '',
   draggablelist: [],
   isupload: false,
   modifycropper: false,
   isdiabled: false,
   cropperimage: {
   },
   uploadid: 'id' + date.now(),
   imgsrc: '',
   option: {
    size: 0.5,
    full: true, // 输出原图比例截图 props名full
    outputtype: 'png',
    canmove: true,
    original: true,
    canmovebox: false,
    autocrop: true,
    fixedbox: true
   }
  }
 },
 props: {
  // 已存在的文件
  filelist: {
   type: array,
   default() {
    return [
    ]
   }
  },
  // 返回类型 array 数组 object 对象
  returntype: {
   type: string,
   default: 'array'
  },
  // 自定义对象
  customobject: {
   type: object,
   default: () => { }
  },
  // 上传的最大个数
  maxnum: {
   type: number,
   required: true,
   default: 1
  },
  // 单位mb
  maxsize: {
   type: number,
   default: 15
  },
  autocropwidth: {
   type: number,
   default: 180
  },
  autocropheight: {
   type: number,
   default: 180
  },
  // 上传类型 all 图片/视频 image 图片 video视频
  accepttype: {
   type: string,
   default: 'all'
  },
  // 是否裁剪
  iscropper: {
   type: boolean,
   default: false
  },
  // 是否显示加载条
  isloading: {
   type: boolean,
   default: true
  },

  outputsize: {
   type: number,
   default: 1
  },
  outputtype: {
   type: string,
   default: 'jpeg'
  }
 },
 components: {
  draggable,
  vuecropper
 },
 watch: {
  draggablelist(newvalue, oldvalue) {
   this.getelement(this.draggablelist.length)
  },
  filelist(newvalue, oldvalue) {
   this.draggablelist = newvalue
   this.initimage()
  }
 },

 computed: {},

 mounted() {
  if (this.accepttype === 'all') {
   this.accept = 'image/png, image/jpeg, image/gif, image/jpg, .mp4,.qlv,.qsv,.ogg,.flv,.avi,.wmv,.rmvb'
  }
  if (this.accepttype === 'image') {
   this.accept = 'image/png, image/jpeg, image/gif, image/jpg'
  }
  if (this.accepttype === 'video') {
   this.accept = '.mp4,.qlv,.qsv,.ogg,.flv,.avi,.wmv,.rmvb'
  }
  this.initimage()
 },
 methods: {
  // 获取五位数的随机数
  getrandom() {
   return (((math.random() + math.random()) * 10000) + '').substr(0, 5).replace('.', 0)
  },
  initimage() {
   const _this = this
   // console.log('file', this.filelist)
   if (this.filelist.length > 0) {
    this.draggablelist = this.filelist.map(val => {
     let displayurl = ''
     let coverurl = ''
     let isimg = true
     const files = (val.url ? val.url : val).split(',')
     if (files.length === 3) {
      displayurl = files[1]
      coverurl = files[2]
      isimg = false
     } else if (files.length === 1) {
      displayurl = (val.url ? val.url : val)
      isimg = true
     }
     const fileobj = object.assign({}, {
      coverurl: coverurl,
      displayurl: displayurl,
      isimg: isimg,
      isplay: true,
      name: date.now(),
      url: (val.url ? val.url : val),
      id: val.id || date.now() + _this.getrandom()
     })
     return fileobj
    }).filter(val => { return val.url })
   }
  },
  handleremove(file, filelist) {
   this.getelement(filelist.length)
  },
  handlepicturecardpreview(file) {
   this.dialogimageurl = file.url
   this.dialogvisible = true
  },
  handleprogress(event, file, filelist) {
   this.percentage = +file.percentage
  },
  filebeforeupload(file, event) {
   if (this.accepttype === 'image' && !file.type.includes('image/')) {
    this.$warning('请上传图片')
    return false
   }
   if (this.accepttype === 'video' && !file.type.includes('video/')) {
    this.$warning('请上传视频')
    return false
   }
   this.isupload = true
   if (file.type.includes('image/') && (file.size > this.maxsize * 1024 * 1024)) {
    this.$warning(`请上传小于${this.maxsize}m的图片`)
    this.percentage = 0
    this.isloading = false
    return false
   }
   if (file.type.includes('video/')) this.isdiabled = true
   if (this.iscropper) {
    return false
   }
  },
  filechange(file, filelist) {
   if (file.percentage === 0 && this.iscropper) {
    if (file.raw.type.includes('video/')) {
     this.$warning('请上传图片')
     return
    }
    this.imgsrc = file.url
    this.modifycropper = true
    this.cropperimage = {
     coverurl: '',
     isimg: true,
     isplay: true,
     name: file.name
    }
   }
  },
  // 实时预览函数
  realtime(data) {
   this.previews = data
  },
  imgload(data) {
  },
  // 裁剪后上传图片
  uploadcropperimage() {
   const _this = this
   this.$refs.cropper.getcropblob((data) => {
    const config = {
     headers: {
      'authorization': _this.headers.authorization,
      'content-type': 'multipart/form-data'
     }
    }
    const formdata = new formdata()
    formdata.append('file', data)
    // this.uploadurl 上传
    axios.post(this.uploadurl, formdata, config).then(response => {
     _this.cropperimage = object.assign({}, _this.cropperimage, {
      displayurl: response.data.data,
      url: response.data.data,
      id: date.now()
     })
     _this.draggablelist.push(_this.cropperimage)
     _this.$emit('getimagelist', _this.draggablelist.map(val => {
      if (this.returntype === 'array') {
       return val.url
      }
      if (this.returntype === 'object') {
       return {
        url: val.url,
        uploadstatus: true
       }
      }
     }), _this.customobject)
     _this.modifycropper = false
    }).catch(error => {
     console.log('err', error)
    })
   })
  },
  imagesuccess(response, file, filelist) {
   const _this = this
   try {
    this.getelement(filelist.length)
    let displayurl = ''
    let coverurl = ''
    let isimg = true
    const url = file.response.data || file.url
    this.isupload = false
    const files = url.split(',')
    if (files.length === 3) {
     displayurl = files[1]
     coverurl = files[2]
     isimg = false
    } else if (files.length === 1) {
     displayurl = url
     isimg = true
    }
    const id = date.now()
    _this.draggablelist.push({
     name: file.name,
     url: url,
     coverurl: coverurl,
     displayurl: displayurl,
     isimg: isimg,
     isplay: true,
     id: id
    })
    if (isimg) {
     _this.percentage = 0
     _this.$emit('getimagelist', _this.draggablelist.map(val => {
      if (this.returntype === 'array') {
       return val.url
      }
      if (this.returntype === 'object') {
       return {
        url: val.url,
        uploadstatus: true
       }
      }
     }), _this.customobject)
     return
    }
    _this.$emit('getimagelist', _this.draggablelist.map(val => {
     if (this.returntype === 'array') {
      return val.url
     }
     if (this.returntype === 'object') {
      return {
       url: val.url,
       uploadstatus: false
      }
     }
    }), _this.customobject)
    settimeout(() => {
     const keys = object.keys(_this.$refs)
     const video = _this.$refs[`${keys[keys.length - 1]}`][0]
     const removeid = keys[keys.length - 1]
     const interval = setinterval(() => {
      if (video.readystate === 4) {
       const duration = video.duration
       this.isdiabled = false
       if (duration < 3 || duration > 60) {
        _this.$message.success('请上传大于三秒小于六十秒的视频')
        _this.percentage = 0
        // _this.remove(_this.draggablelist.length - 1)
        _this.draggablelist = _this.draggablelist.filter(val => {
         return (val.id + '') !== (removeid + '')
        })
        _this.$emit('getimagelist', _this.draggablelist.map(val => {
         if (this.returntype === 'array') {
          return val.url
         }
         if (this.returntype === 'object') {
          return {
           url: val.url,
           uploadstatus: true
          }
         }
        }), _this.customobject)
        _this.getelement(_this.draggablelist.length)
       }
       _this.percentage = 0
       _this.$emit('getimagelist', _this.draggablelist.map(val => {
        if (this.returntype === 'array') {
         return val.url
        }
        if (this.returntype === 'object') {
         return {
          url: val.url,
          uploadstatus: true
         }
        }
       }), _this.customobject)
       clearinterval(interval)
      }
      video.src = displayurl
      video.poster = coverurl
     }, 1000)
    }, 1000)
   } catch (error) {
    console.log('error', error)
   }
  },
  play(item) {
   const video = document.getelementbyid(item.id)
   video.play()
   item.isplay = !item.isplay
  },
  pause(item) {
   const video = document.getelementbyid(item.id)
   video.pause()
   item.isplay = !item.isplay
  },
  // 全屏播放
  fullplay(item) {
   const video = document.getelementbyid(item.id)
   // w3c
   typeof video.requestfullscreen === 'function' && video.requestfullscreen()
   // webkit(谷歌)
   typeof video.webkitrequestfullscreen === 'function' && video.webkitrequestfullscreen()
   // 火狐
   typeof video.mozrequestfullscreen === 'function' && video.mozrequestfullscreen()
   // ie
   typeof video.msexitfullscreen === 'function' && video.msexitfullscreen()
  },
  viewimage(url) {
   this.dialogimageurl = url
   this.dialogvisible = true
  },
  remove(index) {
   this.draggablelist.splice(index, 1)
   this.$emit('getimagelist', this.draggablelist.map(val => {
    if (this.returntype === 'array') {
     return val.url
    }
    if (this.returntype === 'object') {
     return {
      url: val.url,
      uploadstatus: true
     }
    }
   }), this.customobject)
   this.getelement(this.draggablelist.length)
  },
  onend(event) {
   this.$emit('getimagelist', this.draggablelist.map(val => {
    if (this.returntype === 'array') {
     return val.url
    }
    if (this.returntype === 'object') {
     return {
      url: val.url,
      uploadstatus: true
     }
    }
   }), this.customobject)
  },
  isimg(obj) {
   const item = obj.url
   if (item === '' || item === null || typeof item === 'undefined') {
    return false
   }
   const index = item.lastindexof('.')
   var ext = item.substr(index + 1)
   if (ext.includes('!')) ext = ext.split('!')[0]
   ext = ext.tolowercase()
   var tps = ['jpg', 'jpeg', 'png']
   let ok = false
   for (let i = 0; i < tps.length; i++) {
    if (tps[i] === ext) {
     ok = true
     break
    }
   }
   return ok
  },
  getelement(length) {
   const _this = this
   if (length >= _this.maxnum) {
    document.queryselectorall(`#${_this.uploadid} .el-upload--picture-card`).foreach(val => {
     if (val.firstelementchild.classname === 'el-icon-plus') {
      val.style.display = 'none'
      return true
     }
    })
   } else {
    document.queryselectorall(`#${_this.uploadid} .el-upload--picture-card`).foreach(val => {
     if (val.firstelementchild.classname === 'el-icon-plus') {
      val.style.display = 'inline-block'
      return true
     }
    })
   }
  }
 }
}

</script>
<style lang='scss' scoped>
.image-draggable {
  display: flex;
  flex-wrap: wrap;
  .image-list {
    position: relative;

    display: inline-block;
    overflow: hidden;

    width: 148px;
    height: 148px;
    margin-right: 10px;

    cursor: pointer;
    &:hover {
      .icon {
        height: 20%;

        transition: all .5s;
        .video-icon {
          display: inline-block;

          margin-right: 10px;
        }
      }
    }
    .icon {
      position: absolute;
      bottom: 0;

      display: flex;
      justify-content: center;

      width: 100%;
      height: 0;

      background-color: rgba(215, 215, 215, 1);
      .icon-size {
        width: 2em;
        height: 2em;
      }
      .video-icon {
        display: none;
      }
    }
  }
}
</style>
<style lang="scss">
.image-draggable {
  .el-progress {
    top: -50%;
  }
}
</style>

注册全局事件

创建eventbus.js

vue3.0 搭建项目总结(详细步骤)

使用

import eventbus from './plugins/eventbus'
vue.use(eventbus)

处理缓存

借用mounted, activated 事件处理数据

在某一次打开页面的时候进行数据初始化存储, 放置在vuex中,或者全局变量中,当需要初始化进行一个初始化,采取mixins引入

vue3.0 搭建项目总结(详细步骤)

vue3.0 搭建项目总结(详细步骤)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。