vue2中基于vue-simple-upload實(shí)現(xiàn)文件分片上傳組件功能
本文最主要參考的是這一篇,后端也是用django來(lái)完成。
大文件上傳(秒傳/斷點(diǎn)續(xù)傳)_使用Vue-Simple-Uploader插件 --Vue/Django完整實(shí)現(xiàn) http://www.dbjr.com.cn/article/206178.htm
參考的代碼地址:https://github.com/ClearlightY/FileUpload
vue-simple-upload文檔:https://github.com/simple-uploader/Uploader
<template>
<div id='global-uploader'>
<uploader
ref='uploader'
:options='options'
:autoStart='false'
:file-status-text='statusText'
@file-added='onFileAdded'
@file-success='onFileSuccess'
@file-progress='onFileProgress'
@file-error='onFileError'
@file-removed='onfileRemoved'
class='uploader-app'
>
<uploader-unsupport></uploader-unsupport>
<!-- 文件拖拽上傳區(qū)域-->
<uploader-drop id='global-uploader-drop'>
<uploader-btn id='global-uploader-btn' :attrs='attrs' ref='uploadBtn'>
<div class='hzs-upload-icon'>
<svg viewBox='0 0 1024 1024' data-icon='inbox' width='1em' height='1em' fill='currentColor'
aria-hidden='true'
focusable='false' class=''>
<path
d='M885.2 446.3l-.2-.8-112.2-285.1c-5-16.1-19.9-27.2-36.8-27.2H281.2c-17 0-32.1 11.3-36.9 27.6L139.4 443l-.3.7-.2.8c-1.3 4.9-1.7 9.9-1 14.8-.1 1.6-.2 3.2-.2 4.8V830a60.9 60.9 0 0 0 60.8 60.8h627.2c33.5 0 60.8-27.3 60.9-60.8V464.1c0-1.3 0-2.6-.1-3.7.4-4.9 0-9.6-1.3-14.1zm-295.8-43l-.3 15.7c-.8 44.9-31.8 75.1-77.1 75.1-22.1 0-41.1-7.1-54.8-20.6S436 441.2 435.6 419l-.3-15.7H229.5L309 210h399.2l81.7 193.3H589.4zm-375 76.8h157.3c24.3 57.1 76 90.8 140.4 90.8 33.7 0 65-9.4 90.3-27.2 22.2-15.6 39.5-37.4 50.7-63.6h156.5V814H214.4V480.1z'></path>
</svg>
</div>
<p class='hzs-upload-title'>Click or drag file(.zip) to this area to upload datasets</p>
<p class='hzs-upload-description'>Just support for a single file, multiple uploads will be overwritten by the
latter.</p>
</uploader-btn>
</uploader-drop>
<!--文件列表-->
<uploader-list>
<div class='file-panel' slot-scope='props'>
<ul class='file-list' ref='fileList'>
<li v-for='file in props.fileList' :key='file.id'>
<uploader-file :class="'file_' + file.id" ref='files' :file='file' :list='true'></uploader-file>
<!-- <!– 不重要的–>-->
<!-- <div class='query_check' v-if='queryBtnShow'>-->
<!-- <button @click='queryCheck(uuid)'>校驗(yàn)查詢(xún)</button>-->
<!-- <!– <input type="button" v-on="queryCheck(uuid)" value="校"/> –>-->
<!-- <!– <div v-if="queryCheckStatus">文件正在上傳中</div> –>-->
<!-- <span class='queryCheckShow' v-if='queryCheckShow'>{{ checkStatus }}</span>-->
<!-- </div>-->
<!-- <div class='save-result' v-if='saveBtnShow'>-->
<!-- <button @click='download_check_result(file_path)'>校驗(yàn)保存</button>-->
<!-- </div>-->
</li>
<div class='no-file' v-if='!props.fileList.length'>
Empty
</div>
</ul>
</div>
</uploader-list>
</uploader>
</div>
</template>
<script>
import SparkMD5 from 'spark-md5'
import { ACCEPT_FILE_CONFIG } from '@/config/defaultSettings'
import { checkResult, isFileExist, mergeFile, textCheck } from '@/api/uploadTool'
// import storage from 'store'
// https://github.com/simple-uploader/Uploader#events
export default {
data() {
return {
options: {
target: '/api/upload/upload/', // 目標(biāo)上傳URL
testChunks: true,
chunkSize: '2048000',
// chunkSize: "10240000", // 分塊時(shí)按該值來(lái)分
fileParameterName: 'file', // 上傳文件時(shí)文件的參數(shù)名
maxChunkRetries: 3, // 最大自動(dòng)失敗重試上傳次數(shù)
// 由于我自身業(yè)務(wù)原因, 添加了校驗(yàn)查詢(xún)功能, 查詢(xún)的時(shí)候不能控制
// 對(duì)應(yīng)上傳文件的id, 因此,去掉了多文件上傳
// 這里注釋掉, 就可以實(shí)現(xiàn)多文件上傳了
// singleFile: true, // 單文件上傳
// 上傳分片前, 會(huì)先向后端發(fā)送一個(gè)get請(qǐng)求, 該函數(shù)就是響應(yīng)這個(gè)get請(qǐng)求
checkChunkUploadedByResponse: function(chunk, message) {
console.log('上傳分片前, 會(huì)先向后端發(fā)送一個(gè)get請(qǐng)求, 該函數(shù)就是響應(yīng)這個(gè)get請(qǐng)求')
console.log(chunk)
console.log(message)
let objMessage = JSON.parse(message)
console.log('objMessage:', objMessage)
//---------秒傳說(shuō)明------------
// 此處解開(kāi)注釋, 配合后端傳來(lái)的參數(shù)可以實(shí)現(xiàn)秒傳, 我因?yàn)樯蟼骱筮€需要進(jìn)行校驗(yàn)請(qǐng)求,
// 這里我就砍掉秒傳功能了..
//-----------------------------
// 此處根據(jù)返回值來(lái)判斷是否為秒傳
// if (objMessage.skipUpload === "true") {
// return true;
// }
// 根據(jù)返回的數(shù)組內(nèi)容來(lái)判斷哪些分片不需要重新上傳
return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
},
parseTimeRemaining: function(timeRemaining, parsedTimeRemaining) {
return parsedTimeRemaining
.replace(/\syears?/, 'years')
.replace(/\days?/, 'days')
.replace(/\shours?/, 'hours')
.replace(/\sminutes?/, 'minutes')
.replace(/\sseconds?/, 'seconds')
}
},
attrs: {
accept: ACCEPT_FILE_CONFIG.getAll()
},
statusText: {
success: 'success',
error: 'network error',
uploading: 'uploading',
typeError: 'typeError',
emptyError: 'emptyError',
paused: 'paused',
waiting: 'waiting',
cmd5: 'calculate md5',
merging: 'merging'
},
fileStatusText: (status, response) => {
return this.statusText[status]
},
saveBtnShow: false,
queryBtnShow: true,
queryCheckShow: false,
// queryCheckStatus: false,
checkStatus: 'checking...',
uuid: '',
file_path: ''
}
},
mounted() {
// this.$bus.$on('openUploader', query => {
// this.params = query || {}
// if (this.$refs.uploadBtn) {
// document.querySelector('#global-uploader-btn').click()
// }
// })
},
computed: {
//Uploader實(shí)例
uploader() {
return this.$refs.uploader.uploader
}
},
methods: {
onFileAdded(file, event) {
console.log(file)
console.log(event)
console.log(this.uploader.fileList)
console.log(this.uploader.files)
this.$emit('canGoToNextStep', false)
this.$emit('canGoToPreStep', false)
// console.log(this.uploader.files.length)
//暫停文件上傳
file.pause()
//
// this.panelShow = true;
this.queryCheckShow = false
this.saveBtnShow = false
this.queryBtnShow = false
this.statusSet(file.id, 'md5')
this.computedMD5(file)
// setTimeout(() => {
// this.computedMD5(file)
// }, 5000)
// Bus.$emit("fileAdded");
// console.log(this.uploader.files)
// console.log(this.uploader.files.length)
},
onFileProgress(rootFile, file, chunk) {
console.log(
`uploading ${file.name},chunk:${chunk.startByte /
1024 /
1024} ~ ${chunk.endByte / 1024 / 1024}`
)
},
async onFileSuccess(rootFile, file, response, chunk) {
console.log('onFileSuccess')
console.log(file)
let res = JSON.parse(response)
file.success = true
// 文件完成上傳, 文件合并的標(biāo)志
if (res.needMerge === 'true') {
this.statusSet(file.id, 'merging')
let formData = new FormData()
formData.append('filename', file.name)
formData.append('identifier', arguments[0].uniqueIdentifier)
formData.append('totalSize', file.size)
formData.append('timeStamp', res.timeStamp)
console.log('formData')
try {
// --------------------發(fā)送合并請(qǐng)求 start ---------------------
let res = await mergeFile(formData)
// 合并失敗的處理
// if (res.code !== 200) {
// this.$notification.error({
// message: 'Error',
// description: 'An error occurred during the inspection, please try again'
// })
// file.cancel()
// return
// }
// 文件上傳成功 ui更新
this.statusRemove(file.id)
this.statusSet(file.id, 'success')
// this.queryBtnShow = true
// this.$bus.$emit('fileSuccess')
// --------------------發(fā)送合并請(qǐng)求 end ---------------------
// ---------------------發(fā)送請(qǐng)求 詢(xún)問(wèn)是否校驗(yàn)成功 start---------------
// let formData2 = new FormData()
// formData2.append('fpath', res.filePath)
// formData2.append('fname', res.fileName)
// let res2 = await textCheck(formData2)
// if (res2.code !== 200) {
// this.$notification.error({
// message: 'Error',
// description: 'An error occurred during the inspection, please try again'
// })
// file.cancel()
// }
// this.uuid = res2.uuid
// this.file_path = res2.file_path
// ---------------------發(fā)送請(qǐng)求 詢(xún)問(wèn)是否校驗(yàn)成功 end---------------
// this.statusRemove(file.id)
// this.statusSet(file.id, 'finish checking')
// 收集文件相關(guān)數(shù)據(jù)
console.log(file)
this.$store.commit('ADD_FILE_LIST', file)
// 提示可以到下一步了
this.$notification.success({
message: 'Success',
description: 'Successfully upload, go to next step to finish.'
})
this.$emit('canGoToNextStep', true)
this.$emit('canGoToPreStep', true)
// file.uniqueIdentifier
// this.statusSet(file.id, "checking");
//
// // -------------------定時(shí)發(fā)送請(qǐng)求,看檢驗(yàn)是否完成 start-------------------
// // 定時(shí)發(fā)送請(qǐng)求,看檢驗(yàn)是否完成
// let interval = setInterval(() => {
// setTimeout(async () => {
// let formData3 = new FormData()
// formData3.append('file_uuid', res2.uuid)
//
// let res3 = await checkResult(formData3)
// console.log('校驗(yàn)中,請(qǐng)稍等...')
// if (res3.code == 200) {
// console.log('校驗(yàn)完成')
// clearInterval(interval)
// // this.statusRemove(file.id);
// // this.statusSet(file.id, "checkSuccess");
// this.checkStatus = 'finish checking'
// this.saveBtnShow = true
// }
// }, 0)
// }, 1000)
//
// // -------------------定時(shí)發(fā)送請(qǐng)求,看檢驗(yàn)是否完成 end-------------------
} catch (e) {
this.$notification.error({
message: 'Error',
description: 'An error occurred during the inspection, please try again'
})
file.cancel()
}
}
},
onFileError(rootFile, file, response, chunk) {
this.$notification.error({
message: 'Error',
description: 'Please try again'
})
},
onfileRemoved(file) {
this.$message.info(`${file.name} 's upload had been canceled`)
},
/**
* 計(jì)算上傳文件的md5, 實(shí)現(xiàn)斷點(diǎn)續(xù)傳和秒傳
*/
computedMD5(file) {
let fileReader = new FileReader()
let time = new Date().getTime()
let blobSlice =
File.prototype.slice ||
File.prototype.mozSlice ||
File.prototype.webkitSlice
let currentChunk = 0
const chunkSize = 10 * 1024 * 1000
let chunks = Math.ceil(file.size / chunkSize)
let spark = new SparkMD5.ArrayBuffer()
// this.statusSet(file.id, 'md5')
// $('.uploader-file-action').css('display', 'none');
loadNext()
fileReader.onload = e => {
spark.append(e.target.result)
currentChunk++
if (currentChunk < chunks) {
// console.log(
// `第${currentChunk}分片解析完成, 開(kāi)始第${currentChunk +
// 1} / ${chunks}分片解析`
// )
loadNext()
// 實(shí)時(shí)展示MD5的計(jì)算進(jìn)度
setTimeout(() => {
document.querySelector(`.myStatus_${file.id}`).textContent = 'verify MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%'
// console.log(file.id)
// console.log('校驗(yàn)MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%')
// console.log(document.querySelector(`.myStatus_${file.id}`))
// document.querySelector(`.myStatus_${file.id}`).textContent = '校驗(yàn)MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%'
}, 20)
} else {
let md5 = spark.end()
this.computeMD5Success(md5, file)
spark.destroy() // 釋放緩存
console.log(
`MD5計(jì)算完畢:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
file.size
} 用時(shí):${new Date().getTime() - time} ms`
)
}
}
fileReader.onerror = function() {
this.error(`文件${file.name}讀取出錯(cuò),請(qǐng)檢查該文件`)
file.cancel()
}
function loadNext() {
let start = currentChunk * chunkSize
let end =
start + chunkSize >= file.size ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
},
// 計(jì)算md5成功
async computeMD5Success(md5, file) {
file.uniqueIdentifier = md5 // 將文件md5賦值給文件唯一標(biāo)識(shí)
this.statusRemove(file.id)
// file.resume()
console.log('computeMD5Success')
console.log(file)
// 把md5校驗(yàn)結(jié)果傳到后端 檢查是否存在這個(gè)文件
let that = this
try {
let res = await isFileExist({
md5,
fileName: file.name
})
if (res.status) {
// 文件未在s3存在的情況
if (res['is_exist'] === false) {
this.$confirm({
title: 'Are you sure to upload it?',
content: h => res.message,
okText: 'yes',
onOk() {
// 開(kāi)始文件上傳的一個(gè)動(dòng)作
// 將表單里的是否強(qiáng)制上傳設(shè)置為false
file['is_force_process'] = false
file.resume()
},
onCancel() {
that.$emit('canGoToPreStep', true)
file.cancel()
}
})
} else {
// 是否需要強(qiáng)制上傳
this.$confirm({
title: 'The file had existed, are you sure to recover it?',
content: h => res.message,
okText: 'yes',
onOk() {
// 開(kāi)始文件上傳的一個(gè)動(dòng)作
// 將表單里的是否強(qiáng)制上傳設(shè)置為false
file['is_force_process'] = true
file.resume()
},
onCancel() {
that.$emit('canGoToPreStep', true)
file.cancel()
}
})
}
} else {
file.cancel()
}
} catch (e) {
file.cancel()
}
},
// 展示文件列表
// fileListShow() {
// // let $list = $('#global-uploader .file-list')
// let $list = this.$refs.fileList
//
// if ($list.is(':visible')) {
// $list.slideUp()
// } else {
// $list.slideDown()
// }
// },
close() {
this.uploader.cancel()
},
/**
* 新增的自定義的狀態(tài): 'md5'、'transcoding'、'failed'
* @param id
* @param status
*/
statusSet(id, status) {
let statusMap = {
md5: {
text: 'verify MD5',
bgc: '#fff'
},
merging: {
text: 'merging',
bgc: '#e2eeff'
},
transcoding: {
text: 'transcoding',
bgc: '#e2eeff'
},
failed: {
text: 'fail to upload',
bgc: '#e2eeff'
},
success: {
text: 'successfully upload',
bgc: '#e2eeff'
},
checking: {
text: 'checking',
bgc: '#e2eeff'
},
checkSuccess: {
text: 'check ok',
bgc: '#e2eeff'
}
}
setTimeout(() => {
let p = document.createElement('p')
p.className = `myStatus_${id}`
let father = document.querySelector(`.file_${id} .uploader-file-status`)
p.setAttribute('style', `position: absolute; top:-16px;left: 0;right: 0; bottom: 0;zIndex: 1; backgroundColor: ${statusMap[status].bgc}`)
p.append(statusMap[status].text)
father.appendChild(p)
})
},
statusRemove(id) {
// console.log("statusRemove")
// console.log(id)
this.$nextTick(() => {
let node = document.querySelector(`.myStatus_${id}`)
node.parentNode.removeChild(node)
})
},
error(msg) {
this.$notification.error({
message: 'Error',
description: msg
})
},
// 查詢(xún)每個(gè)區(qū)塊
queryCheck: function(uuid) {
console.log('uuid::', uuid)
console.log('file_path::--------', this.file_path)
// if (uuid === "") {
// this.queryCheckStatus = true;
// return;
// }
let formData = new FormData()
formData.append('file_uuid', uuid)
const instance = this.$http.post({
headers: {
'Content-Type': 'multipart/form-data'
}
})
instance
.post('http://127.0.0.1:8000/fileUpload/check_result', formData)
.then(res => {
// console.log("點(diǎn)擊了查詢(xún)校驗(yàn)狀態(tài)按鈕");
if (res.data.code == '200') {
// this.queryCheckStatus = false;
this.checkStatus = 'check ok'
this.queryCheckShow = true
} else {
this.checkStatus = 'checking...'
this.queryCheckShow = true
}
})
},
// 下載校驗(yàn)結(jié)果
download_check_result() {
let formData = new FormData()
formData.append('file_path', this.file_path)
const instance = this.$axios.create({
headers: {
'Content-Type': 'multipart/form-data'
},
responseType: 'blob'
})
instance
.post('http://127.0.0.1:8000/fileUpload/file_download', formData)
.then(response => {
if (!response) {
return
}
let url = window.URL.createObjectURL(new Blob([response.data]))
let link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', 'check-result.txt')
document.body.appendChild(link)
link.click()
})
}
},
watch: {},
destroyed() {
// this.$bus.$off('openUploader')
}
}
</script>
<style scoped lang='less'>
#global-uploader {
// position: fixed;
z-index: 20;
max-width: 1000px;
margin: 10px auto;
background: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 4);
//padding: 10px;
h2 {
padding: 30px 0;
text-align: center;
font-size: 20px;
}
.uploader-app {
//width: 880px;
//padding: 15px;
//margin: 20px auto 0;
font-size: 14px;
}
ul li {
list-style-type: none;
}
li div {
//left: -19px;
}
}
.file-panel {
background-color: #fff;
border: 1px solid #e2e2e2;
border-radius: 7px 7px 0 0;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
.file-title {
display: flex;
height: 40px;
// line-height: 40px;
padding: 0 15px;
border-bottom: 1px solid #ddd;
.operate {
flex: 1;
text-align: right;
}
}
.file-list {
position: relative;
height: 240px;
overflow-x: hidden;
overflow-y: auto;
background-color: #fff;
//border: 1px solid black;
padding: 0;
> li {
background-color: #fff;
}
}
&.collapse {
.file-title {
background-color: #e7ecf2;
}
}
}
.no-file {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 16px;
}
.uploader-btn {
margin-right: 4px;
color: #fff;
padding: 6px 16px;
}
#global-uploader-btn {
//border: 1px solid #409eff;
//background: #409eff;
border: 0;
width: 100%;
//height: 100px;
padding: 15px;
color: #F5222D;
display: flex;
flex-direction: column;
align-items: center;
p {
padding: 0;
margin: 10px;
}
.hzs-upload-icon {
font-size: 40px;
}
.hzs-upload-title {
font-size: 20px;
}
.hzs-upload-description {
font-size: 16px;
color: rgba(0, 0, 0, 0.45)
}
}
#global-uploader-btn:hover {
background: none;
}
#global-uploader-drop {
//height: 150px;
padding: 0;
}
#global-uploader-drop:hover {
border: 1px dashed #F5222D;
}
#global-uploader-dir-btn {
border: 1px solid #67c23a;
background: #67c23a;
}
.save-result {
position: absolute;
margin-top: -24px;
margin-left: 700px;
z-index: 10;
}
.query_check {
position: absolute;
margin-left: 700px;
margin-top: -50px;
z-index: 10;
}
.queryCheckShow {
margin-left: 10px;
color: red;
font-size: 12px;
}
</style>調(diào)用
<MyUploaderBox
@canGoToNextStep='canGoToNextStep'
@canGoToPreStep='canGoToPreStep'
></MyUploaderBox>
參考
vue實(shí)現(xiàn)分片上傳 https://blog.csdn.net/AIfurture/article/details/103975897
字節(jié)跳動(dòng)面試官:請(qǐng)你實(shí)現(xiàn)一個(gè)大文件上傳和斷點(diǎn)續(xù)傳 https://juejin.cn/post/6844904046436843527
vue-simple-uploader組件的使用感受 https://www.jianshu.com/p/da8ad489095e
大文件上傳(秒傳/斷點(diǎn)續(xù)傳)_使用Vue-Simple-Uploader插件 --Vue/Django完整實(shí)現(xiàn) https://blog.csdn.net/qq_36852780/article/details/107437875
相關(guān)代碼和文檔:
https://github.com/ClearlightY/FileUpload
https://github.com/shady-xia/Blog/tree/master/vue-simple-uploader
https://github.com/simple-uploader/Uploader
到此這篇關(guān)于vue2中基于vue-simple-upload的文件分片上傳組件的文章就介紹到這了,更多相關(guān)vue-simple-upload文件分片上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue如何實(shí)現(xiàn)文件預(yù)覽和下載功能的前端上傳組件
- Vue框架+Element-ui(el-upload組件)使用http-request方法上傳文件并顯示文件上傳進(jìn)度功能
- 利用Vue3+Element-plus實(shí)現(xiàn)大文件分片上傳組件
- vue3.0搭配.net core實(shí)現(xiàn)文件上傳組件
- Vue開(kāi)發(fā)之封裝上傳文件組件與用法示例
- Vue封裝一個(gè)簡(jiǎn)單輕量的上傳文件組件的示例
- vue webuploader 文件上傳組件開(kāi)發(fā)
- Vue 中自定義文件上傳組件的實(shí)現(xiàn)代碼
相關(guān)文章
vue如何解決循環(huán)引用組件報(bào)錯(cuò)的問(wèn)題
這篇文章主要介紹了vue如何解決循環(huán)引用組件報(bào)錯(cuò)的問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
在vue中使用Echarts利用watch做動(dòng)態(tài)數(shù)據(jù)渲染操作
這篇文章主要介紹了在vue中使用Echarts利用watch做動(dòng)態(tài)數(shù)據(jù)渲染操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
Vue如何調(diào)用接口請(qǐng)求頭增加參數(shù)
這篇文章主要介紹了Vue如何調(diào)用接口請(qǐng)求頭增加參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
avue實(shí)現(xiàn)自定義搜索欄及清空搜索事件的實(shí)踐
本文主要介紹了avue實(shí)現(xiàn)自定義搜索欄及清空搜索事件的實(shí)踐,主要包括對(duì)搜索欄進(jìn)行自定義,并通過(guò)按鈕實(shí)現(xiàn)折疊搜索欄效果,具有一定的參考價(jià)值,感興趣的可以了解一下2021-12-12
vscode+vue cli3.0創(chuàng)建項(xiàng)目配置Prettier+eslint方式
這篇文章主要介紹了vscode+vue cli3.0創(chuàng)建項(xiàng)目配置Prettier+eslint方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10
vue3使用vue-cli引入windicss報(bào)錯(cuò)Can‘t resolve windi.css問(wèn)題
這篇文章主要介紹了vue3使用vue-cli引入windicss報(bào)錯(cuò)Can‘t resolve windi.css問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03

