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

536 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 运行时下载模型 - 完整实现示例
## ✅ **确认:完全可以实现!**
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. 提供测试建议
---
**总结:完全可以运行时下载!建议先打包测试,后续再实施下载功能。**