guoyu/_已清理文件备份_周六 22512/md/运行时下载模型完整示例.md

536 lines
10 KiB
Markdown
Raw Normal View History

2025-12-06 20:11:36 +08:00
# 运行时下载模型 - 完整实现示例
## ✅ **确认:完全可以实现!**
uni-app 的 `uni.downloadFile` API 完全支持运行时下载大文件。
---
## 📁 **文件清单**
已创建:
-`utils/modelDownloader.js` - 模型下载管理器(已创建)
需要修改:
- ⚠️ `pages/speech/speech.vue` - 语音识别页面
---
## 🎯 **在语音页面中使用**
### **完整代码示例**
```vue
<template>
<view class="container">
<!-- 模型下载提示 -->
<view v-if="!modelReady" class="download-container">
<view class="download-header">
<text class="title">语音识别模型初始化</text>
</view>
<!-- 未开始下载 -->
<view v-if="downloadStatus === 'idle'" class="idle-state">
<text class="tip">首次使用需要下载语音识别模型</text>
<text class="size">文件大小41.87 MB</text>
<text class="wifi-tip">建议在WiFi环境下下载</text>
<button
type="primary"
@click="startDownload"
class="download-btn">
开始下载
</button>
</view>
<!-- 下载中 -->
<view v-if="downloadStatus === 'downloading'" class="downloading-state">
<text class="progress-text">下载进度:{{ downloadProgress }}%</text>
<progress
:percent="downloadProgress"
:show-info="true"
:stroke-width="10"
activeColor="#07c160" />
<text class="speed-text">速度:{{ downloadSpeed }}</text>
<text class="size-text">{{ downloadedSize }} / {{ totalSize }}</text>
<button
type="warn"
@click="cancelDownload"
class="cancel-btn"
size="mini">
取消下载
</button>
</view>
<!-- 下载失败 -->
<view v-if="downloadStatus === 'error'" class="error-state">
<text class="error-text">下载失败:{{ errorMessage }}</text>
<button
type="primary"
@click="retryDownload"
class="retry-btn">
重试
</button>
</view>
</view>
<!-- 语音识别界面 -->
<view v-else class="speech-container">
<text class="ready-text">✅ 模型已就绪</text>
<!-- 原来的语音识别界面 -->
<button @click="startSpeech" type="primary">开始识别</button>
<!-- 设置:重新下载模型 -->
<view class="settings">
<button @click="redownloadModel" size="mini">重新下载模型</button>
</view>
</view>
</view>
</template>
<script>
import modelDownloader from '@/utils/modelDownloader.js';
export default {
data() {
return {
modelReady: false,
downloadStatus: 'idle', // idle, downloading, success, error
downloadProgress: 0,
downloadSpeed: '',
downloadedSize: '',
totalSize: '',
errorMessage: '',
downloadTask: null,
modelPath: ''
}
},
onLoad() {
// 页面加载时检查模型
this.checkModel();
},
methods: {
/**
* 检查模型是否存在
*/
async checkModel() {
try {
uni.showLoading({
title: '检查模型...',
mask: true
});
const result = await modelDownloader.checkModelExists();
uni.hideLoading();
if (result.exists) {
console.log('模型已存在,路径:', result.path);
this.modelPath = result.path;
this.modelReady = true;
this.downloadStatus = 'success';
} else {
console.log('模型不存在,需要下载');
this.modelReady = false;
this.downloadStatus = 'idle';
}
} catch (e) {
console.error('检查模型失败:', e);
uni.hideLoading();
this.downloadStatus = 'idle';
}
},
/**
* 开始下载
*/
startDownload() {
// 检查网络类型
uni.getNetworkType({
success: (res) => {
const networkType = res.networkType;
if (networkType === '2g' || networkType === '3g') {
// 移动网络提示
uni.showModal({
title: '提示',
content: '当前使用移动网络下载将消耗约42MB流量是否继续',
success: (modalRes) => {
if (modalRes.confirm) {
this.doDownload();
}
}
});
} else {
// WiFi或其他网络直接下载
this.doDownload();
}
},
fail: () => {
// 无法获取网络类型,直接下载
this.doDownload();
}
});
},
/**
* 执行下载
*/
doDownload() {
this.downloadStatus = 'downloading';
this.downloadProgress = 0;
this.downloadTask = modelDownloader.downloadModel(
// 进度回调
(progressData) => {
this.downloadProgress = Math.floor(progressData.progress);
this.downloadSpeed = modelDownloader.formatSpeed(progressData.speed);
this.downloadedSize = modelDownloader.formatSize(progressData.loaded);
this.totalSize = modelDownloader.formatSize(progressData.total);
},
// 成功回调
(filePath) => {
console.log('下载成功:', filePath);
this.modelPath = filePath;
this.modelReady = true;
this.downloadStatus = 'success';
uni.showToast({
title: '模型下载完成',
icon: 'success',
duration: 2000
});
},
// 失败回调
(error) => {
console.error('下载失败:', error);
this.downloadStatus = 'error';
this.errorMessage = error.message || '未知错误';
uni.showToast({
title: '下载失败',
icon: 'none',
duration: 2000
});
}
);
},
/**
* 取消下载
*/
cancelDownload() {
if (this.downloadTask) {
this.downloadTask.abort();
this.downloadTask = null;
}
this.downloadStatus = 'idle';
this.downloadProgress = 0;
uni.showToast({
title: '已取消下载',
icon: 'none'
});
},
/**
* 重试下载
*/
retryDownload() {
this.doDownload();
},
/**
* 重新下载模型
*/
redownloadModel() {
uni.showModal({
title: '确认',
content: '确定要重新下载模型吗?',
success: async (res) => {
if (res.confirm) {
try {
// 删除旧模型
await modelDownloader.deleteModel();
// 重置状态
this.modelReady = false;
this.downloadStatus = 'idle';
uni.showToast({
title: '请重新下载',
icon: 'none'
});
} catch (e) {
console.error('删除模型失败:', e);
}
}
}
});
},
/**
* 开始语音识别
*/
startSpeech() {
if (!this.modelReady) {
uni.showToast({
title: '模型未就绪',
icon: 'none'
});
return;
}
// 调用语音识别插件
// 传入模型路径this.modelPath
console.log('使用模型路径:', this.modelPath);
// 原来的语音识别代码
// ...
}
}
}
</script>
<style scoped>
.container {
padding: 30rpx;
}
.download-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx;
}
.download-header {
margin-bottom: 30rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.idle-state {
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.tip {
font-size: 28rpx;
color: #666;
margin-top: 20rpx;
}
.size {
font-size: 32rpx;
color: #07c160;
font-weight: bold;
}
.wifi-tip {
font-size: 24rpx;
color: #999;
}
.download-btn {
margin-top: 40rpx;
width: 300rpx;
}
.downloading-state {
width: 100%;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.progress-text {
font-size: 28rpx;
color: #333;
text-align: center;
}
.speed-text,
.size-text {
font-size: 24rpx;
color: #666;
text-align: center;
}
.cancel-btn {
margin-top: 20rpx;
}
.error-state {
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.error-text {
font-size: 28rpx;
color: #ff0000;
}
.retry-btn {
width: 300rpx;
}
.speech-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 30rpx;
}
.ready-text {
font-size: 32rpx;
color: #07c160;
font-weight: bold;
}
.settings {
margin-top: 50rpx;
}
</style>
```
---
## ✅ **优势说明**
### **1. 用户体验好**
- ✅ 首次下载有进度显示
- ✅ 显示下载速度和剩余大小
- ✅ 移动网络下有流量提醒
- ✅ 可以取消和重试
### **2. 技术可靠**
- ✅ uni-app 官方 API 支持
- ✅ 支持大文件下载GB级别
- ✅ 自动处理文件保存
- ✅ 支持断点续传(需服务器配合)
### **3. 易于维护**
- ✅ 模型可以随时更新
- ✅ 不需要重新打包APP
- ✅ 可以支持多个模型版本
- ✅ 用户可以手动重新下载
---
## 📊 **效果对比**
| 方案 | APK大小 | 首次启动 | 更新模型 | 推荐度 |
|------|---------|----------|----------|--------|
| 打包方式 | ~100MB | 即用 | 需重新打包 | ⭐⭐ |
| 运行时下载 | ~58MB | 需下载1分钟 | 直接更新 | ⭐⭐⭐⭐⭐ |
| 自定义基座 | ~100MB | 即用 | 需重新打包 | ⭐⭐⭐ |
---
## 🚀 **实施步骤**
### **1. 模型文件已移除** ✅
```
vosk-model-small-cn-0.22.zip 已移动到桌面
```
### **2. 下载管理器已创建** ✅
```
utils/modelDownloader.js
```
### **3. 上传模型到服务器**
```
将 vosk-model-small-cn-0.22.zip 上传到:
https://app.liuyingyong.cn/static/vosk-model-small-cn-0.22.zip
```
### **4. 修改语音页面**
```
参考上面的示例代码
修改 pages/speech/speech.vue
```
### **5. 测试**
```
1. 打包APK现在可以打包了大小约58MB
2. 安装到手机
3. 打开语音页面
4. 点击"开始下载"
5. 等待下载完成
6. 测试语音识别功能
```
---
## ⚠️ **注意事项**
### **服务器要求**
1. **支持大文件下载**
- 确保服务器允许下载42MB文件
- 设置合适的超时时间
2. **HTTPS支持**
- iOS必须使用HTTPS
- Android建议使用HTTPS
3. **CDN加速可选**
- 使用CDN可以提升下载速度
- 减轻服务器压力
### **APP权限**
确保manifest.json中有存储权限
```json
{
"permissions": {
"WRITE_EXTERNAL_STORAGE": {},
"READ_EXTERNAL_STORAGE": {}
}
}
```
---
## 🎯 **现在可以做什么?**
### **方案A先打包测试推荐** ⭐⭐⭐⭐⭐
1. ✅ 模型已移除,现在可以打包
2. ✅ 打包后测试除语音外的功能
3. ⏰ 后续实施运行时下载
### **方案B立即实施运行时下载**
1. 上传模型到服务器
2. 按照示例修改speech.vue
3. 打包测试完整功能
---
## 📞 **需要帮助?**
如果您选择方案B我可以
1. 帮您修改 `pages/speech/speech.vue`
2. 检查服务器配置
3. 提供测试建议
---
**总结:完全可以运行时下载!建议先打包测试,后续再实施下载功能。** ✅