uniapp实现多文件下载,保存到本地

概览

uniapp实现多文件下载,保存到本地,因为使用的是uni.downloadFile 实现文件的下载,每次只能下载一个,需要下载多个文件,并保存到本地,并把保存的地址存储到对应的数据组中,并实现进度条显示。

需求分析

1、文件下载并保存到本地 直接使用uni.downloadFile 和uni.saveFile 两个方法既可以实现。

2、如有多个文件下载,需要异步一个一个去下载,并把保存到本地的路径赋值给传过来的数据。

3、如果使用for循环,会存存储数据混乱的情况,或者说直接保存的路径只保存到了最后一个数组对象中,无法实现保存对应的存储路径到数组。

4、进度条显示,根据下载的方法,进行监听进度返回到页面进行显示进度条的变化。

具体实现

download.js 代码

javascript 复制代码
// +----------------------------------------------------------------------
// | 下载工具类
// +----------------------------------------------------------------------
import {
	HTTP_REQUEST_URL
} from '@/config/app';
export const hostUrlUpLoad = HTTP_REQUEST_URL + '/upload/';
export const hostUrl = HTTP_REQUEST_URL;
import {
	getStudyData,
	getTestData
} from '@/api/api.js';

/**
 * 下载分类相关数据
 * 图片加载到本地
 * @returns boolean
 */
export async function downloadClass(data, inta) {
	if (data.length > 0 && inta < data.length) {
		try {
			const downloadTask = await uni.downloadFile({
				url: data[inta].icon_path.includes('upload') ? hostUrl + data[inta].icon_path :
					hostUrlUpLoad + data[inta].icon_path,
				success: res => {
					if (res.statusCode === 200) {
						uni.saveFile({
							tempFilePath: res.tempFilePath, // 下载文件的临时路径
							success: saveRes => {
								data[inta].icon_path = saveRes.savedFilePath
								if (inta < data.length - 1) {
									inta = inta + 1
									downloadClass(data, inta)
								} else {
									uni.setStorageSync('classData', data)
								}
							},
							fail: err => {
								if (inta < data.length - 1) {
									inta = inta + 1
									downloadClass(data, inta)
								} else {
									uni.setStorageSync('classData', data)
								}
							}
						});
					} else {
						if (inta < data.length - 1) {
							inta = inta + 1
							downloadClass(data, inta)
						} else {
							uni.setStorageSync('classData', data)
						}
					}
				},
				fail: err => {
					if (inta < data.length - 1) {
						inta = inta + 1
						downloadClass(data, inta)
					} else {
						uni.setStorageSync('classData', data)
					}
				}
			});
			downloadTask.onProgressUpdate(resda => {})
		} catch (e) {
			if (inta < data.length - 1) {
				inta = inta + 1
				downloadClass(data, inta)
			} else {
				uni.setStorageSync('classData', data)
			}
			//TODO handle the exception
		}
	}
}

/**
 * 下载科目数据
 * 图片加载到本地
 * @returns boolean
 */
export async function downloadSubject(data, inta, callBack) {
	var callBackFun = callBack;
	if (data.length > 0 && inta < data.length) {
		try {
			const downloadTask = await uni.downloadFile({
				url: data[inta].icon_path.includes('upload') ? hostUrl + data[inta].icon_path :
					hostUrlUpLoad + data[inta].icon_path,
				success: res => {
					if (res.statusCode === 200) {
						uni.saveFile({
							tempFilePath: res.tempFilePath, // 下载文件的临时路径
							success: saveRes => {
								data[inta].icon_path = saveRes.savedFilePath
								if (inta < data.length - 1) {
									inta = inta + 1
									downloadSubject(data, inta, callBackFun)
								} else {
									uni.setStorageSync('SubjectData', data, callBackFun)
									requestSubjectClass(data, 0, callBackFun)
								}
							},
							fail: err => {
								if (inta < data.length - 1) {
									inta = inta + 1
									downloadSubject(data, inta, callBackFun)
								} else {
									uni.setStorageSync('SubjectData', data)
									requestSubjectClass(data, 0, callBackFun)
								}
							}
						});
					} else {
						if (inta < data.length - 1) {
							inta = inta + 1
							downloadSubject(data, inta, callBackFun)
						} else {
							uni.setStorageSync('SubjectData', data)
							requestSubjectClass(data, 0, callBackFun)
						}
					}
				},
				fail: err => {
					if (inta < data.length - 1) {
						inta = inta + 1
						downloadSubject(data, inta, callBackFun)
					} else {
						uni.setStorageSync('SubjectData', data)
						requestSubjectClass(data, 0, callBackFun)
					}
				}
			});
			downloadTask.onProgressUpdate(resda => {
				callBackFun(resda.progress, false)
			})
		} catch (e) {
			//TODO handle the exception
			if (inta < data.length - 1) {
				inta = inta + 1
				downloadSubject(data, inta, callBackFun)
			} else {
				uni.setStorageSync('SubjectData', data)
				requestSubjectClass(data, 0, callBackFun)
			}
		}
	}
}


/**
 * 根据科目列表赋值分类数据
 * @param {Object} data
 * @param {Object} inta
 */
export async function requestSubjectClass(data, inta, callBack) {
	const callBackFun = callBack;
	if (data.length > 0 && inta < data.length) {
		try {
			await getStudyData(data[inta].id).then(res => {
				if (res.data.code == 200 && res.data.status == 'success' && res.data.data.count > 0) {
					data[inta].categoryData = res.data.data.list
					if (inta < data.length - 1) {
						inta = inta + 1
						requestSubjectClass(data, inta, callBackFun)
					} else {
						uni.setStorageSync('SubjectData', data)
						downloadSubjectFile(data, 0, 0, callBackFun)
					}
				} else {
					if (inta < data.length - 1) {
						inta = inta + 1
						requestSubjectClass(data, inta, callBackFun)
					} else {
						uni.setStorageSync('SubjectData', data)
						downloadSubjectFile(data, 0, 0, callBackFun)
					}
				}
			})
		} catch (e) {
			//TODO handle the exception
			if (inta < data.length - 1) {
				inta = inta + 1
				requestSubjectClass(data, inta, callBackFun)
			} else {
				uni.setStorageSync('SubjectData', data)
				downloadSubjectFile(data, 0, 0, callBackFun)
			}
		}
	}
}

/**
 * 下载文件到本地
 * @returns boolean
 */
export async function downloadSubjectFile(data, inta, intaClass, callBack) {
	const callBackFun = callBack;
	if (data.length > 0 && inta < data.length) {
		if (data[inta].categoryData.length > 0 && intaClass < data[inta].categoryData.length) {
			try {
				var filePath = ''
				if (data[inta].categoryData[intaClass].file_path.includes('upload')) {
					if (data[inta].categoryData[intaClass].file_path.charAt(0) == '/') {
						filePath = hostUrl + data[inta].categoryData[intaClass].file_path
					} else {
						filePath = "http://" + data[inta].categoryData[intaClass].file_path
					}
				} else {
					filePath = hostUrlUpLoad + data[inta].categoryData[intaClass].file_path
				}
				const downloadTask = await uni.downloadFile({
					url: filePath,
					success: res => {
						if (res.statusCode === 200) {
							uni.saveFile({
								tempFilePath: res.tempFilePath, // 下载文件的临时路径
								success: saveRes => {
									data[inta].categoryData[intaClass].file_path = saveRes.savedFilePath
									if (intaClass < data[inta].categoryData.length - 1) {
										intaClass = intaClass + 1
										downloadSubjectFile(data, inta, intaClass,callBackFun)
									} else {
										if (inta < data.length - 1) {
											inta = inta + 1
											downloadSubjectFile(data, inta, 0, callBackFun)
										} else {
											uni.setStorageSync('SubjectData', data)
											callBackFun(100, true)
										}
									}
								},
								fail: err => {
									console.log("保存文件失败  ")
									if (intaClass < data[inta].categoryData.length - 1) {
										intaClass = intaClass + 1
										downloadSubjectFile(data, inta, intaClass,
											callBackFun)
									} else {
										if (inta < data.length - 1) {
											inta = inta + 1
											downloadSubjectFile(data, inta, 0, callBackFun)
										} else {
											uni.setStorageSync('SubjectData', data)
											callBackFun(100, true)
										}
									}
								}
							});
						} else {
							if (intaClass < data[inta].categoryData.length - 1) {
								intaClass = intaClass + 1
								downloadSubjectFile(data, inta, intaClass, callBackFun)
							} else {
								if (inta < data.length - 1) {
									inta = inta + 1
									downloadSubjectFile(data, inta, 0, callBackFun)
								} else {
									uni.setStorageSync('SubjectData', data)
									callBackFun(100, true)
								}
							}
						}
					},
					fail: err => {
						console.log("下载失败  fail  ")
						if (intaClass < data[inta].categoryData.length - 1) {
							intaClass = intaClass + 1
							downloadSubjectFile(data, inta, intaClass, callBackFun)
						} else {
							if (inta < data.length - 1) {
								inta = inta + 1
								downloadSubjectFile(data, inta, 0, callBackFun)
							} else {
								uni.setStorageSync('SubjectData', data)
								callBackFun(100, true)
							}
						}
					}
				});
				downloadTask.onProgressUpdate(resda => {
					callBackFun(resda.progress, false)
				})
			} catch (e) {
				//TODO handle the exception
				console.log("downloadSubjectFile  catch  " + JSON.stringify(e))
				if (intaClass < data[inta].categoryData.length - 1) {
					intaClass = intaClass + 1
					downloadSubjectFile(data, inta, intaClass, callBackFun)
				} else {
					if (inta < data.length - 1) {
						inta = inta + 1
						downloadSubjectFile(data, inta, 0, callBackFun)
					} else {
						console.log("打印保存的科目数据  downloadSubjectFile    " + JSON.stringify(data))
						uni.setStorageSync('SubjectData', data)
						callBackFun(100, true)
					}
				}
			}
		} else {
			if (intaClass < data[inta].categoryData.length - 1) {
				intaClass = intaClass + 1
				downloadSubjectFile(data, inta, intaClass, callBackFun)
			} else {
				if (inta < data.length - 1) {
					inta = inta + 1
					downloadSubjectFile(data, inta, 0, callBackFun)
				} else {
					console.log("打印保存的科目数据  downloadSubjectFile    " + JSON.stringify(data))
					uni.setStorageSync('SubjectData', data)
					callBackFun(100, true)
				}
			}
		}

	}
}



/**
 * 根据科目列表获取题库
 * @param {Object} subjectData
 */
export async function requestSubjectTestData(subjectData) {
	try {
		await getTestData(subjectData.id).then(res => {
			if (res.data.code == 200 && res.data.status == 'success') {
				uni.removeStorageSync('CNLLIST' + subjectData.id)
				uni.removeStorageSync('CNLLISTCOUNT' + subjectData.id)
				uni.removeStorageSync('ENLLIST' + subjectData.id)
				uni.removeStorageSync('ENLLISTCOUNT' + subjectData.id)
				uni.setStorageSync('CNLLIST' + subjectData.id, res.data.data.list)
				uni.setStorageSync('CNLLISTCOUNT' + subjectData.id, res.data.data.count)
				uni.setStorageSync('ENLLIST' + subjectData.id, res.data.data.english_list)
				uni.setStorageSync('ENLLISTCOUNT' + subjectData.id, res.data.data.english_count)
			} else {
				uni.removeStorageSync('CNLLIST' + subjectData.id)
				uni.removeStorageSync('CNLLISTCOUNT' + subjectData.id)
				uni.removeStorageSync('ENLLIST' + subjectData.id)
				uni.removeStorageSync('ENLLISTCOUNT' + subjectData.id)
			}
		})
	} catch (e) {
		console.log('报错信息  ' + JSON.stringify(subjectData.id))
		//TODO handle the exception
	}
}

/**
 * 根据科目列表赋值分类数据
 * @param {Object} data
 * @param {Object} inta
 */
export async function requestSubjectClassSize(data, inta, callBack) {
	if (data.length > 0 && inta < data.length) {
		try {
			await getStudyData(data[inta].id).then(res => {
				if (res.data.code == 200 && res.data.status == 'success' && res.data.data.count > 0) {
					data[inta].categoryData = res.data.data.list
					if (inta < data.length - 1) {
						inta = inta + 1
						requestSubjectClassSize(data, inta, callBack)
					} else {
						requestDownloadFileSize(data, 0, 0,0, callBack)
					}
				} else {
					if (inta < data.length - 1) {
						inta = inta + 1
						requestSubjectClassSize(data, inta, callBack)
					} else {
						requestDownloadFileSize(data, 0, 0,0, callBack)
					}
				}
			})
		} catch (e) {
			//TODO handle the exception
			if (inta < data.length - 1) {
				inta = inta + 1
				requestSubjectClassSize(data, inta, callBack)
			} else {
				requestDownloadFileSize(data, 0, 0,0, callBack)
			}
		}
	}
}


/**
 * 下载文件总大小
 * @returns boolean
 */
export async function requestDownloadFileSize(data, inta, intaClass, contentLength, callBack) {
	if (data.length > 0 && inta < data.length) {
		if (data[inta].categoryData.length > 0 && intaClass < data[inta].categoryData.length) {
			try {
				var filePath = ''
				if (data[inta].categoryData[intaClass].file_path.includes('upload')) {
					if (data[inta].categoryData[intaClass].file_path.charAt(0) == '/') {
						filePath = hostUrl + data[inta].categoryData[intaClass].file_path
					} else {
						filePath = "http://" + data[inta].categoryData[intaClass].file_path
					}
				} else {
					filePath = hostUrlUpLoad + data[inta].categoryData[intaClass].file_path
				}
				await uni.request({
					url:  filePath,
					method: 'head',
					// responseType: 'arraybuffer', // 或者其他如'text'、'blob'等,取决于你需要的内容类型
					// header: {
					// 	'Accept-Encoding': 'gzip, deflate'
					// },
					success(res) {
						
						if (res.statusCode === 200) {
							if (intaClass < data[inta].categoryData.length - 1) {
								intaClass = intaClass + 1
								if(res.header['Content-Length'] || res.header['content-length']){
									console.log("filePath  " + filePath + '      ' + JSON.stringify(res.header['Content-Length']))
									contentLength = contentLength + parseInt(res.header['Content-Length']?res.header['Content-Length']:res.header['content-length'],10); // 获取Content-Length字段的值
								}
								requestDownloadFileSize(data, inta, intaClass,contentLength,callBack)
							} else {
								if (inta < data.length - 1) {
									inta = inta + 1
									if(res.header['Content-Length'] || res.header['content-length']){
										contentLength = contentLength + parseInt(res.header['Content-Length']?res.header['Content-Length']:res.header['content-length'],10); // 获取Content-Length字段的值
									}
									requestDownloadFileSize(data, inta, 0,contentLength, callBack)
								} else {
									callBack(contentLength)
								}
							}
						} else {
							if (intaClass < data[inta].categoryData.length - 1) {
								intaClass = intaClass + 1
								requestDownloadFileSize(data, inta, intaClass,contentLength, callBack)
							} else {
								if (inta < data.length - 1) {
									inta = inta + 1
									requestDownloadFileSize(data, inta, 0,contentLength, callBack)
								} else {
									callBack(contentLength)
								}
							}
						}
					},
					error(err) {
						console.error('请求错误:', err);
						if (intaClass < data[inta].categoryData.length - 1) {
							intaClass = intaClass + 1
							requestDownloadFileSize(data, inta, intaClass,contentLength, callBack)
						} else {
							if (inta < data.length - 1) {
								inta = inta + 1
								requestDownloadFileSize(data, inta, 0,contentLength, callBack)
							} else {
								callBack(contentLength)
							}
						}
					},
					networkTimeout: 5000, // 设置超时时间
					timeout: 5000 // 设置整体请求超时时间
				})
			} catch (e) {
				//TODO handle the exception
				console.log("requestDownloadFileSize  catch  " + JSON.stringify(e))
				if (intaClass < data[inta].categoryData.length - 1) {
					intaClass = intaClass + 1
					requestDownloadFileSize(data, inta, intaClass,contentLength, callBack)
				} else {
					if (inta < data.length - 1) {
						inta = inta + 1
						requestDownloadFileSize(data, inta, 0,contentLength, callBack)
					} else {
						callBack(contentLength)
					}
				}
			}
		} else {
			if (intaClass < data[inta].categoryData.length - 1) {
				intaClass = intaClass + 1
				requestDownloadFileSize(data, inta, intaClass,contentLength, callBack)
			} else {
				if (inta < data.length - 1) {
					inta = inta + 1
					requestDownloadFileSize(data, inta, 0,contentLength, callBack)
				} else {
					callBack(contentLength)
				}
			}
		}

	}
}



/**
 * @param {Object} data
 * @param {Object} inta
 * @param {Object} contentLength
 * @param {Object} callBack
 */
export async function requestFileSize(data, inta, contentLength, callBack) {
	if (data.length > 0 && inta < data.length) {
		try {
			uni.request({
				url: downloadUrl,
				method: 'GET',
				responseType: 'arraybuffer', // 或者其他如'text'、'blob'等,取决于你需要的内容类型
				header: {
					'Accept-Encoding': 'gzip, deflate'
				},
				success(res) {
					if (res.statusCode === 200 && res.data) {
						if (inta < data.length - 1) {
							inta = inta + 1
							if(res.header['Content-Length'] || res.header['content-length']){
								contentLength = contentLength + parseInt(res.header['Content-Length']?res.header['Content-Length']:res.header['content-length'],10); // 获取Content-Length字段的值
							}
							console.log(contentLength + '   字节');
							requestFileSize(data, inta, contentLength, callBack)
						} else {
							callBack(contentLength)
						}
					} else {
						if (inta < data.length - 1) {
							inta = inta + 1
							requestFileSize(data, inta, contentLength, callBack)
						} else {
							callBack(contentLength)
						}
					}
				},
				error(err) {
					console.error('请求错误:', err);
					if (inta < data.length - 1) {
						inta = inta + 1
						requestFileSize(data, inta, contentLength, callBack)
					} else {
						callBack(contentLength)
					}
				},
				networkTimeout: 5000, // 设置超时时间
				timeout: 5000 // 设置整体请求超时时间
			});
		} catch (e) {
			//TODO handle the exception
			if (inta < data.length - 1) {
				inta = inta + 1
				requestFileSize(data, inta, contentLength, callBack)
			} else {
				callBack(contentLength)
			}
		}
	}
}


export default {
	downloadClass,
	downloadSubject,
	requestSubjectTestData,
	requestDownloadFileSize,
	requestSubjectClassSize
};

实现的逻辑是,下载完成一个文件,然后把保存到本地的路径存储到传过来对应的数据中,把原来线上的文件地址替换为本地路径,然后在进行下一个。

进度条显示

javascript 复制代码
<template>
	<view style="width: 100%;height: 100%;display: flex;align-items: center;justify-content: center;position: relative;">
		<view style="position: absolute;top: 80rpx;display: flex;flex-direction: column;align-items: center;justify-content: center;">
			<image src="../../static/kaoshilogo.png" style="width: 80rpx;" mode="widthFix"></image>
			<view style="margin-top: 20rpx;font-family: Source Han Sans-Bold;font-size: 18rpx;">{{language.AppName}}</view>
		</view>
		<view style="position: absolute;bottom: 30rpx;width: 100%;display: flex;align-items: center;justify-content: center;flex-direction: column;">
			<view style="width: 60%;font-size: 8rpx;text-align: center;margin-bottom: 3rpx;">{{language.zhengzaigengxinziliao}}</view>
			<view class="progress-bar">
				<view class="progress-bar__fill" :style="{width: progress}"></view>
			</view>
		</view>
		
	</view>
</template>

<script>
	import {
		getSubjectData,
		getClassData,
		getList,
		uploadStudyTime,
		uploadTestData,
		uploadOpen
	} from '@/api/api.js'
	export default {
		data() {
			return {
				fileManager: null,
				openData: [],
				progress: '',
				language: getApp().globalData.language,
			}
		},
		onLoad() {
			this.uploadOpen()
			//获取总分类数据
			if (!uni.getStorageSync("classData")) {
				this.getClassDataPage()
			}
			//获取科目数据
			if (!uni.getStorageSync("SubjectData")) {
				this.getSubjectDataPage()
			}else{
				if(uni.getStorageSync('userInfo')){
					uni.switchTab({
						url: '/pages/home/home'
					})
				}else{
					uni.navigateTo({
						url: '/pages/login/login'
					})
				}
			}
			if(uni.getStorageSync('userInfo') && uni.getStorageSync('userInfo').userName != 'Guest'){
				if(uni.getStorageSync('uploadTime')){
					this.uploadSyudyTime()
				}
				if(uni.getStorageSync('uploadTestTime')){
					this.uploadTestTime()
				}
			}
		},
		methods: {
			async getClassDataPage(){
				var that = this
				await getClassData().then(res => {
					if (res.data.code == 200 && res.data.status == 'success') {
						that.$download.downloadClass(res.data.data.list, 0)
					} else if (res.statusCode == '404' || res.status == 1) {
						console.log("网路请求失败")
					} else {
						console.log(res)
					}
				})
			},
			async getSubjectDataPage(){
				var that = this
				await getSubjectData().then(res => {
					if (res.data.code == 200 && res.data.status == 'success') {
						if(res.data && res.data.data && res.data.data.list && res.data.data.list.length > 0){
							for (var i = 0; i < res.data.data.list.length; i++) {
								this.$download.requestSubjectTestData(res.data.data.list[i]);
							}
							this.$download.downloadSubject(res.data.data.list, 0,function(progress,boobleTo){
								that.progress = progress + "%"
								if(boobleTo){
									getApp().globalData.version = '1.0.' + that.$utils.formatDateTime()
									if(uni.getStorageSync('userInfo')){
										uni.switchTab({
											url: '/pages/home/home'
										})
									}else{
										uni.navigateTo({
											url: '/pages/login/login'
										})
									}
								}
							});
						}else{
							if(uni.getStorageSync('userInfo')){
								uni.switchTab({
									url: '/pages/home/home'
								})
							}else{
								uni.navigateTo({
									url: '/pages/login/login'
								})
							}
						}
					} else if (res.statusCode == '404' || res.status == 1) {
						console.log("网路请求失败")
						uni.showToast({
							title: that.language.startToastnetworkMsg,
							icon: 'none',
							duration: 1500
						})
						if(uni.getStorageSync('userInfo')){
							uni.switchTab({
								url: '/pages/home/home'
							})
						}else{
							uni.navigateTo({
								url: '/pages/login/login'
							})
						}
					} else {
						uni.showToast({
							title: that.language.startToastnetworkMsg,
							icon: 'none',
							duration: 1500
						})
						if(uni.getStorageSync('userInfo')){
							uni.switchTab({
								url: '/pages/home/home'
							})
						}else{
							uni.navigateTo({
								url: '/pages/login/login'
							})
						}
					}
				})
			},
			async uploadSyudyTime(){
				await uploadStudyTime({"data": uni.getStorageSync('uploadTime')}).then(res=>{
					if(res.data.code == 200 && res.data.status == 'success'){
						uni.removeStorageSync('uploadTime')
					}
				})
			},
			async uploadTestTime(){
				await uploadTestData({"data": uni.getStorageSync('uploadTestTime')}).then(res=>{
					if(res.data.code == 200 && res.data.status == 'success'){
						uni.removeStorageSync('uploadTestTime')
					}
				})
			},
			async uploadOpen(){
				this.openData = [];
				if(uni.getStorageSync('uploadOpen')){
					this.openData = uni.getStorageSync('uploadOpen')
					this.openData.push({"date": this.$utils.formatDateTime()})
				}else{
					this.openData[0] = {"date": this.$utils.formatDateTime()}
				}
				await uploadOpen({"data": this.openData}).then(res=>{
					if(res.data.code == 200 && res.data.status == 'success'){
						uni.removeStorageSync('uploadOpen')
					}else{
						uni.setStorageSync('uploadOpen',this.openData)
					}
				})
			}
		}
	}
</script>

<style>
	page{
		width: 100%;
		height: 100vh;
	}
	
	.progress-bar {
	  background-color: #cccccc; /* 进度条背景颜色 */
	  height: 10rpx;
	  width: 60%;
	  border-radius: 5rpx;
	}
	
	.progress-bar__fill {
	  height: 100%;
	  background-color: green; /* 完成部分的颜色 */
	  transition: width 0.1s ease-in-out; /* 平滑过渡效果 */
	  border-radius: 5rpx;
	}
</style>

有部分不需要的代码,可以自己删除掉即可。

相关推荐
灰天7681 小时前
健身房项目 Uniapp+若依Vue3版搭建!!
uni-app
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架