vue3.0 搭建项目总结(详细步骤)
程序员文章站
2023-12-15 21:29:28
1.环境配置
项目中的不同开发环境有很多依赖配置,所以可以根据环境设置不同的配置,以免在不同环境经常修改文件
1 在根目录下创建 `.env.[环境]` 文件,可以在不...
1.环境配置
项目中的不同开发环境有很多依赖配置,所以可以根据环境设置不同的配置,以免在不同环境经常修改文件
1 在根目录下创建 `.env.[环境]` 文件,可以在不同环境设置一些配置变量,如图
.env.dev 文件
2.eslint 配置
在package.json 文件里面有一个eslintconfig对象,可设置rules: 如图
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
使用
import eventbus from './plugins/eventbus' vue.use(eventbus)
处理缓存
借用mounted, activated 事件处理数据
在某一次打开页面的时候进行数据初始化存储, 放置在vuex中,或者全局变量中,当需要初始化进行一个初始化,采取mixins引入
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。