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>

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

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax