Browse Source
- 新增 build_channels.sh 脚本,支持华为、小米、OPPO、vivo、荣耀和谷歌渠道的交互式选择打包 - 脚本提供构建前清理、日志记录、错误处理和构建结果汇总功能 - 删除已过时的 AUDIO_PLAYBACK_FIX.md 文档,该文档记录的音频播放问题已修复 - 在 .gitignore 中添加 .claude/ 目录,避免 Claude Code 生成的文件被提交newdev_shunjiawei
3 changed files with 522 additions and 166 deletions
@ -1,166 +0,0 @@ |
|||
# 音频播放Bug修复报告 |
|||
|
|||
## 问题描述 |
|||
在会议详情页面(meeting detail page)中,点击播放按钮(▶️)时没有任何反应,音频无法播放。 |
|||
影响两种类型的音频: |
|||
1. 录音的音频(recorded audio) |
|||
2. 导入的音频(imported audio) |
|||
|
|||
## 根本原因分析 |
|||
|
|||
### 1. 缺少播放器准备状态检查 |
|||
**问题:** `MeetingDetailsController` 中的 `_loadPlayerController()` 方法没有错误处理和状态跟踪。 |
|||
- 播放器初始化失败时没有任何提示 |
|||
- UI层无法知道播放器是否准备好 |
|||
- 文件路径错误或文件不存在时静默失败 |
|||
|
|||
### 2. UI层缺少状态验证 |
|||
**问题:** `meeting_details_view.dart` 中的播放按钮点击事件没有检查播放器状态。 |
|||
- 直接调用 `startPlayer()` 而不验证播放器是否已初始化 |
|||
- 没有异常捕获机制 |
|||
- 用户点击后没有任何反馈 |
|||
|
|||
### 3. 缺少日志记录 |
|||
**问题:** 没有足够的日志来追踪播放器初始化和播放过程。 |
|||
- 无法诊断失败原因 |
|||
- 难以定位问题发生的具体环节 |
|||
|
|||
## 修复方案 |
|||
|
|||
### 修改1: MeetingDetailsController - 添加播放器准备状态 |
|||
**文件:** `lib/modules/meeting/controllers/meeting_details_controller.dart` |
|||
|
|||
**变更:** |
|||
1. 添加 `isPlayerReady` 状态变量来跟踪播放器是否准备好 |
|||
2. 重写 `_loadPlayerController()` 方法,添加完整的错误处理 |
|||
3. 添加文件路径验证和文件存在性检查 |
|||
4. 添加详细的日志记录 |
|||
5. 添加用户友好的错误提示 |
|||
|
|||
**关键代码:** |
|||
```dart |
|||
// 新增状态变量 |
|||
RxBool isPlayerReady = false.obs; |
|||
|
|||
// 改进的播放器加载方法 |
|||
Future<void> _loadPlayerController() async { |
|||
try { |
|||
final filepath = meetingData.value.filepath; |
|||
|
|||
// 验证文件路径 |
|||
if (filepath.isEmpty) { |
|||
Logger.error('音频文件路径为空,无法加载播放器'); |
|||
isPlayerReady.value = false; |
|||
return; |
|||
} |
|||
|
|||
// 验证文件是否存在 |
|||
final file = File(filepath); |
|||
if (!await file.exists()) { |
|||
Logger.error('音频文件不存在: $filepath'); |
|||
isPlayerReady.value = false; |
|||
Get.snackbar('error'.tr, '音频文件不存在', ...); |
|||
return; |
|||
} |
|||
|
|||
Logger.d('MeetingDetails', '开始加载音频播放器: $filepath'); |
|||
|
|||
// 准备播放器 |
|||
await playerController.preparePlayer(...); |
|||
|
|||
// 标记播放器已准备好 |
|||
isPlayerReady.value = true; |
|||
|
|||
} catch (e, stackTrace) { |
|||
Logger.error('加载音频播放器失败: $e'); |
|||
isPlayerReady.value = false; |
|||
Get.snackbar('error'.tr, '音频播放器加载失败', ...); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### 修改2: MeetingDetailsView - 添加播放前验证 |
|||
**文件:** `lib/modules/meeting/views/meeting_details_view.dart` |
|||
|
|||
**变更:** |
|||
1. 在播放按钮点击事件中添加播放器准备状态检查 |
|||
2. 添加异常捕获和用户提示 |
|||
3. 使用 async/await 确保异步操作正确执行 |
|||
|
|||
**关键代码:** |
|||
```dart |
|||
GestureDetector( |
|||
onTap: () async { |
|||
if (controller.meetingData.value.filepath.isEmpty) { |
|||
controller.downloadAudio(); |
|||
} else { |
|||
// 检查播放器是否已准备好 |
|||
if (!controller.isPlayerReady.value) { |
|||
Get.snackbar('error'.tr, '音频播放器未准备好,请稍候', ...); |
|||
return; |
|||
} |
|||
|
|||
try { |
|||
if (controller.isPlay.value) { |
|||
await controller.playerController.pausePlayer(); |
|||
} else { |
|||
await controller.playerController.startPlayer(); |
|||
} |
|||
} catch (e) { |
|||
Get.snackbar('error'.tr, '播放失败: ${e.toString()}', ...); |
|||
} |
|||
} |
|||
}, |
|||
... |
|||
) |
|||
``` |
|||
|
|||
## 修复效果 |
|||
|
|||
### 修复前 |
|||
- ❌ 点击播放按钮无响应 |
|||
- ❌ 没有错误提示 |
|||
- ❌ 无法诊断问题 |
|||
- ❌ 用户体验差 |
|||
|
|||
### 修复后 |
|||
- ✅ 播放器初始化失败时有明确提示 |
|||
- ✅ 文件不存在时提示用户 |
|||
- ✅ 播放器未准备好时阻止播放并提示 |
|||
- ✅ 播放失败时显示具体错误信息 |
|||
- ✅ 完整的日志记录便于调试 |
|||
- ✅ 录音音频和导入音频都能正常播放 |
|||
|
|||
## 测试建议 |
|||
|
|||
1. **测试录音音频播放** |
|||
- 创建新的录音 |
|||
- 保存录音 |
|||
- 进入会议详情页面 |
|||
- 点击播放按钮验证播放功能 |
|||
|
|||
2. **测试导入音频播放** |
|||
- 导入外部音频文件 |
|||
- 进入会议详情页面 |
|||
- 点击播放按钮验证播放功能 |
|||
|
|||
3. **测试错误场景** |
|||
- 删除音频文件后尝试播放(应提示文件不存在) |
|||
- 在播放器未准备好时点击播放(应提示等待) |
|||
|
|||
## 技术细节 |
|||
|
|||
### 使用的技术 |
|||
- **audio_waveforms**: Flutter音频播放库 |
|||
- **GetX**: 状态管理和依赖注入 |
|||
- **Logger**: 日志记录工具 |
|||
|
|||
### 关键改进点 |
|||
1. **状态管理**: 使用 `RxBool isPlayerReady` 响应式状态 |
|||
2. **错误处理**: try-catch 包裹所有异步操作 |
|||
3. **用户反馈**: Get.snackbar 提供即时反馈 |
|||
4. **日志记录**: Logger 记录关键操作和错误 |
|||
|
|||
## 总结 |
|||
此次修复通过添加完善的状态检查、错误处理和用户反馈机制,彻底解决了音频播放按钮无响应的问题。修复后的代码更加健壮,用户体验显著提升。 |
|||
|
|||
@ -0,0 +1,519 @@ |
|||
#!/bin/bash |
|||
# ================================================================= |
|||
# Voitrans 多渠道 Android 打包脚本 |
|||
# 用法: |
|||
# ./build_channels.sh # 打包所有渠道 |
|||
# ./build_channels.sh huawei google # 只打包指定渠道 |
|||
# ./build_channels.sh --clean # 清理后再打包所有渠道 |
|||
# ./build_channels.sh --list # 列出所有支持的渠道 |
|||
# ================================================================= |
|||
|
|||
set -euo pipefail |
|||
|
|||
# ---------- 常量定义 ---------- |
|||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
|||
ENV_FILE="${SCRIPT_DIR}/.env" |
|||
PUBSPEC_FILE="${SCRIPT_DIR}/pubspec.yaml" |
|||
OUTPUT_DIR="/Users/liuxiamin/work/build_outputs" |
|||
APP_NAME="Voitrans" |
|||
BUILD_DATE=$(date +"%m%d") |
|||
|
|||
# ---------- 渠道配置 ---------- |
|||
# 渠道名列表(保证顺序) |
|||
CHANNEL_NAMES="huawei xiaomi oppo vivo honor google" |
|||
|
|||
# 根据渠道名获取 .env 中的 Channel 值 |
|||
get_channel_env_value() { |
|||
case "$1" in |
|||
huawei) echo "CHINA_HUAWEI" ;; |
|||
xiaomi) echo "CHINA_XIAOMI" ;; |
|||
oppo) echo "CHINA_OPPO" ;; |
|||
vivo) echo "CHINA_VIVO" ;; |
|||
honor) echo "CHINA_HONOR" ;; |
|||
google) echo "GLOBAL_GOOGLE" ;; |
|||
*) echo "" ;; |
|||
esac |
|||
} |
|||
|
|||
# 根据渠道名获取构建类型 |
|||
get_build_type() { |
|||
case "$1" in |
|||
google) echo "aab" ;; |
|||
*) echo "apk" ;; |
|||
esac |
|||
} |
|||
|
|||
# 校验渠道名是否有效 |
|||
is_valid_channel() { |
|||
case "$1" in |
|||
huawei|xiaomi|oppo|vivo|honor|google) return 0 ;; |
|||
*) return 1 ;; |
|||
esac |
|||
} |
|||
|
|||
# 构建结果追踪 |
|||
SUCCESS_CHANNELS="" |
|||
FAILED_CHANNELS="" |
|||
|
|||
# ---------- 颜色输出 ---------- |
|||
RED='\033[0;31m' |
|||
GREEN='\033[0;32m' |
|||
YELLOW='\033[1;33m' |
|||
BLUE='\033[0;34m' |
|||
NC='\033[0m' |
|||
|
|||
# ---------- 辅助函数 ---------- |
|||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } |
|||
log_success() { echo -e "${GREEN}[OK]${NC} $1"; } |
|||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } |
|||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; } |
|||
|
|||
# 提取版本号(从 pubspec.yaml 的 version 字段,去掉构建号) |
|||
get_version() { |
|||
grep '^version:' "$PUBSPEC_FILE" | sed 's/version: *//' | cut -d'+' -f1 |
|||
} |
|||
|
|||
# 备份 .env |
|||
backup_env() { |
|||
cp "$ENV_FILE" "${ENV_FILE}.bak" |
|||
log_info "已备份 .env -> .env.bak" |
|||
} |
|||
|
|||
# 恢复 .env |
|||
restore_env() { |
|||
if [ -f "${ENV_FILE}.bak" ]; then |
|||
mv "${ENV_FILE}.bak" "$ENV_FILE" |
|||
log_info "已恢复 .env 文件" |
|||
fi |
|||
} |
|||
|
|||
# 修改 .env 中的 Channel 值(兼容 macOS sed) |
|||
set_channel() { |
|||
local channel_value="$1" |
|||
if [ "$(uname)" = "Darwin" ]; then |
|||
sed -i '' "s/^Channel=.*/Channel=${channel_value}/" "$ENV_FILE" |
|||
else |
|||
sed -i "s/^Channel=.*/Channel=${channel_value}/" "$ENV_FILE" |
|||
fi |
|||
} |
|||
|
|||
# 列出所有渠道 |
|||
list_channels() { |
|||
echo "" |
|||
echo "支持的渠道列表:" |
|||
echo " 序号 渠道名 .env值 构建格式" |
|||
echo " ---- -------- ---------------- --------" |
|||
local idx=1 |
|||
for ch in $CHANNEL_NAMES; do |
|||
local env_val |
|||
env_val=$(get_channel_env_value "$ch") |
|||
local build_type |
|||
build_type=$(get_build_type "$ch") |
|||
printf " [%d] %-10s %-18s %s\n" "$idx" "$ch" "$env_val" "$build_type" |
|||
idx=$((idx + 1)) |
|||
done |
|||
echo "" |
|||
} |
|||
|
|||
# 交互式选择渠道(上下键移动,空格勾选,Enter确认) |
|||
interactive_select() { |
|||
# 构建菜单项数组 |
|||
local items="全部打包" |
|||
for ch in $CHANNEL_NAMES; do |
|||
local bt |
|||
bt=$(get_build_type "$ch") |
|||
items="${items}|${ch} (${bt})" |
|||
done |
|||
|
|||
local total_items=7 # 全部打包 + 6个渠道 |
|||
local cursor=0 # 当前光标位置 |
|||
# 选中状态:0=未选 1=已选(用空格分隔的列表,索引0-6) |
|||
local selected="0 0 0 0 0 0 0" |
|||
|
|||
# 获取第n项的选中状态 |
|||
get_selected() { |
|||
echo "$selected" | cut -d' ' -f$(($1 + 1)) |
|||
} |
|||
|
|||
# 切换第n项的选中状态 |
|||
toggle_selected() { |
|||
local idx=$1 |
|||
local new_selected="" |
|||
local i=0 |
|||
for val in $selected; do |
|||
if [ $i -eq $idx ]; then |
|||
if [ "$val" = "0" ]; then |
|||
# 如果选中"全部打包",取消其他所有 |
|||
if [ $idx -eq 0 ]; then |
|||
selected="1 0 0 0 0 0 0" |
|||
return |
|||
fi |
|||
val="1" |
|||
else |
|||
val="0" |
|||
fi |
|||
fi |
|||
# 选中单个渠道时,取消"全部打包" |
|||
if [ $idx -ne 0 ] && [ $i -eq 0 ]; then |
|||
val="0" |
|||
fi |
|||
if [ -z "$new_selected" ]; then |
|||
new_selected="$val" |
|||
else |
|||
new_selected="$new_selected $val" |
|||
fi |
|||
i=$((i + 1)) |
|||
done |
|||
selected="$new_selected" |
|||
} |
|||
|
|||
# 绘制菜单 |
|||
draw_menu() { |
|||
# 移动光标到菜单起始位置 |
|||
local i=0 |
|||
printf "\033[${total_items}A" # 向上移动 |
|||
|
|||
local item_idx=0 |
|||
# 保存IFS并用|分割 |
|||
local OLD_IFS="$IFS" |
|||
IFS='|' |
|||
for item in $items; do |
|||
local marker=" " |
|||
local sel |
|||
sel=$(get_selected $item_idx) |
|||
if [ "$sel" = "1" ]; then |
|||
marker="✓" |
|||
fi |
|||
|
|||
if [ $item_idx -eq $cursor ]; then |
|||
# 高亮当前行 |
|||
printf "\033[2K ${GREEN}▸ [${marker}] ${item}${NC}\n" |
|||
else |
|||
printf "\033[2K [${marker}] ${item}\n" |
|||
fi |
|||
item_idx=$((item_idx + 1)) |
|||
done |
|||
IFS="$OLD_IFS" |
|||
} |
|||
|
|||
# 显示标题 |
|||
echo "" |
|||
echo "╔══════════════════════════════════════════╗" |
|||
echo "║ Voitrans 多渠道打包工具 ║" |
|||
echo "╚══════════════════════════════════════════╝" |
|||
echo "" |
|||
echo " ↑/↓ 移动 空格 勾选 Enter 确认 q 退出" |
|||
echo "" |
|||
|
|||
# 首次绘制菜单(先输出占位行) |
|||
local i=0 |
|||
while [ $i -lt $total_items ]; do |
|||
echo "" |
|||
i=$((i + 1)) |
|||
done |
|||
draw_menu |
|||
|
|||
# 读取键盘输入 |
|||
while true; do |
|||
# 读取单个字符(静默模式) |
|||
IFS= read -rsn1 key |
|||
|
|||
case "$key" in |
|||
$'\x1b') |
|||
# 读取转义序列的剩余部分 |
|||
read -rsn2 seq |
|||
case "$seq" in |
|||
'[A') # 上箭头 |
|||
if [ $cursor -gt 0 ]; then |
|||
cursor=$((cursor - 1)) |
|||
else |
|||
cursor=$((total_items - 1)) |
|||
fi |
|||
;; |
|||
'[B') # 下箭头 |
|||
if [ $cursor -lt $((total_items - 1)) ]; then |
|||
cursor=$((cursor + 1)) |
|||
else |
|||
cursor=0 |
|||
fi |
|||
;; |
|||
esac |
|||
;; |
|||
' ') # 空格:切换选中 |
|||
toggle_selected $cursor |
|||
;; |
|||
'') # Enter:确认 |
|||
break |
|||
;; |
|||
'q'|'Q') # 退出 |
|||
echo "" |
|||
echo "已取消" |
|||
exit 0 |
|||
;; |
|||
esac |
|||
draw_menu |
|||
done |
|||
|
|||
# 根据选中状态构建渠道列表 |
|||
if [ "$(get_selected 0)" = "1" ]; then |
|||
SELECTED_CHANNELS="$CHANNEL_NAMES" |
|||
else |
|||
SELECTED_CHANNELS="" |
|||
local idx=1 |
|||
for ch in $CHANNEL_NAMES; do |
|||
if [ "$(get_selected $idx)" = "1" ]; then |
|||
SELECTED_CHANNELS="${SELECTED_CHANNELS} ${ch}" |
|||
fi |
|||
idx=$((idx + 1)) |
|||
done |
|||
SELECTED_CHANNELS=$(echo "$SELECTED_CHANNELS" | sed 's/^ *//') |
|||
fi |
|||
|
|||
if [ -z "$SELECTED_CHANNELS" ]; then |
|||
log_error "未选择任何渠道" |
|||
exit 1 |
|||
fi |
|||
|
|||
echo "" |
|||
log_info "已选择: ${SELECTED_CHANNELS}" |
|||
} |
|||
|
|||
# ---------- 核心构建函数 ---------- |
|||
build_channel() { |
|||
local channel_name="$1" |
|||
local env_value |
|||
env_value=$(get_channel_env_value "$channel_name") |
|||
local build_type |
|||
build_type=$(get_build_type "$channel_name") |
|||
local version |
|||
version=$(get_version) |
|||
|
|||
local output_filename="${APP_NAME}_${BUILD_DATE}_${version}_${channel_name}.${build_type}" |
|||
|
|||
echo "" |
|||
echo "========================================" |
|||
log_info "开始构建渠道: ${channel_name} (${env_value})" |
|||
log_info "构建类型: ${build_type}" |
|||
log_info "输出文件: ${output_filename}" |
|||
echo "========================================" |
|||
|
|||
# 1. 修改 .env 中的 Channel 值 |
|||
set_channel "$env_value" |
|||
log_info "已设置 Channel=${env_value}" |
|||
|
|||
# 2. 执行 flutter build |
|||
local build_start |
|||
build_start=$(date +%s) |
|||
local build_result=0 |
|||
local source_file="" |
|||
|
|||
if [ "$build_type" = "apk" ]; then |
|||
flutter build apk \ |
|||
--obfuscate \ |
|||
--split-debug-info=./build/app/outputs/symbols \ |
|||
2>&1 | tee "${OUTPUT_DIR}/build_${channel_name}.log" || build_result=$? |
|||
source_file="${SCRIPT_DIR}/build/app/outputs/flutter-apk/app-release.apk" |
|||
else |
|||
flutter build aab \ |
|||
--obfuscate \ |
|||
--split-debug-info=./build/app/outputs/symbols \ |
|||
2>&1 | tee "${OUTPUT_DIR}/build_${channel_name}.log" || build_result=$? |
|||
source_file="${SCRIPT_DIR}/build/app/outputs/bundle/release/app-release.aab" |
|||
fi |
|||
|
|||
local build_end |
|||
build_end=$(date +%s) |
|||
local build_duration=$((build_end - build_start)) |
|||
|
|||
# 3. 检查构建结果 |
|||
if [ $build_result -ne 0 ]; then |
|||
log_error "渠道 ${channel_name} 构建失败!(耗时 ${build_duration}秒)" |
|||
FAILED_CHANNELS="${FAILED_CHANNELS} ${channel_name}" |
|||
return 1 |
|||
fi |
|||
|
|||
# 4. 复制并重命名输出文件 |
|||
if [ -f "$source_file" ]; then |
|||
cp "$source_file" "${OUTPUT_DIR}/${output_filename}" |
|||
local file_size |
|||
file_size=$(du -h "${OUTPUT_DIR}/${output_filename}" | cut -f1) |
|||
# 构建成功,删除日志文件 |
|||
rm -f "${OUTPUT_DIR}/build_${channel_name}.log" |
|||
log_success "渠道 ${channel_name} 构建成功!(耗时 ${build_duration}秒, 大小 ${file_size})" |
|||
log_success "输出: ${OUTPUT_DIR}/${output_filename}" |
|||
SUCCESS_CHANNELS="${SUCCESS_CHANNELS} ${channel_name}:${build_duration}" |
|||
else |
|||
log_error "构建产物未找到: ${source_file}" |
|||
FAILED_CHANNELS="${FAILED_CHANNELS} ${channel_name}" |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
# ---------- 主流程 ---------- |
|||
main() { |
|||
local total_start |
|||
total_start=$(date +%s) |
|||
|
|||
# 参数解析 |
|||
local do_clean=false |
|||
local channels_to_build="" |
|||
|
|||
for arg in "$@"; do |
|||
case "$arg" in |
|||
--list|-l) |
|||
list_channels |
|||
exit 0 |
|||
;; |
|||
--clean|-c) |
|||
do_clean=true |
|||
;; |
|||
--help|-h) |
|||
echo "用法: $0 [选项] [渠道名...]" |
|||
echo "" |
|||
echo "选项:" |
|||
echo " --list, -l 列出所有支持的渠道" |
|||
echo " --clean, -c 构建前先执行 flutter clean" |
|||
echo " --help, -h 显示帮助信息" |
|||
echo "" |
|||
echo "示例:" |
|||
echo " $0 # 打包所有渠道" |
|||
echo " $0 huawei google # 只打包华为和谷歌" |
|||
echo " $0 --clean # 清理后打包所有渠道" |
|||
exit 0 |
|||
;; |
|||
*) |
|||
if ! is_valid_channel "$arg"; then |
|||
log_error "未知渠道: $arg" |
|||
list_channels |
|||
exit 1 |
|||
fi |
|||
channels_to_build="${channels_to_build} ${arg}" |
|||
;; |
|||
esac |
|||
done |
|||
|
|||
# 如果没有指定渠道,进入交互选择模式 |
|||
if [ -z "$channels_to_build" ]; then |
|||
interactive_select |
|||
channels_to_build="$SELECTED_CHANNELS" |
|||
fi |
|||
# 去掉前导空格 |
|||
channels_to_build=$(echo "$channels_to_build" | sed 's/^ *//') |
|||
|
|||
local version |
|||
version=$(get_version) |
|||
|
|||
# 计算渠道总数 |
|||
local total=0 |
|||
for _ in $channels_to_build; do |
|||
total=$((total + 1)) |
|||
done |
|||
|
|||
# 显示构建计划 |
|||
echo "" |
|||
echo "╔══════════════════════════════════════════╗" |
|||
echo "║ Voitrans 多渠道打包工具 ║" |
|||
echo "╚══════════════════════════════════════════╝" |
|||
echo "" |
|||
log_info "构建渠道: ${channels_to_build}" |
|||
log_info "版本号: ${version}" |
|||
log_info "构建日期: ${BUILD_DATE}" |
|||
log_info "输出目录: ${OUTPUT_DIR}" |
|||
log_info "共 ${total} 个渠道" |
|||
|
|||
# 前置检查 |
|||
if ! command -v flutter &> /dev/null; then |
|||
log_error "flutter 命令未找到,请确保 Flutter SDK 已安装并配置 PATH" |
|||
exit 1 |
|||
fi |
|||
|
|||
if [ ! -f "$ENV_FILE" ]; then |
|||
log_error ".env 文件不存在: $ENV_FILE" |
|||
exit 1 |
|||
fi |
|||
|
|||
# 创建输出目录 |
|||
mkdir -p "$OUTPUT_DIR" |
|||
|
|||
# 可选:flutter clean |
|||
if [ "$do_clean" = true ]; then |
|||
log_info "执行 flutter clean..." |
|||
flutter clean |
|||
log_info "执行 flutter pub get..." |
|||
flutter pub get |
|||
fi |
|||
|
|||
# 备份 .env 并注册 trap(确保异常退出也能恢复) |
|||
backup_env |
|||
trap restore_env EXIT |
|||
|
|||
# 遍历构建 |
|||
local current=0 |
|||
|
|||
for channel in $channels_to_build; do |
|||
current=$((current + 1)) |
|||
echo "" |
|||
echo "████████████████████████████████████████" |
|||
echo " 进度: [${current}/${total}] 渠道: ${channel}" |
|||
echo "████████████████████████████████████████" |
|||
|
|||
build_channel "$channel" || true |
|||
done |
|||
|
|||
# ---------- 构建结果汇总 ---------- |
|||
local total_end |
|||
total_end=$(date +%s) |
|||
local total_duration=$((total_end - total_start)) |
|||
local total_min=$((total_duration / 60)) |
|||
local total_sec=$((total_duration % 60)) |
|||
|
|||
# 统计成功/失败数量 |
|||
local success_count=0 |
|||
local fail_count=0 |
|||
for _ in $SUCCESS_CHANNELS; do |
|||
success_count=$((success_count + 1)) |
|||
done |
|||
for _ in $FAILED_CHANNELS; do |
|||
fail_count=$((fail_count + 1)) |
|||
done |
|||
|
|||
echo "" |
|||
echo "╔══════════════════════════════════════════╗" |
|||
echo "║ 构 建 结 果 汇 总 ║" |
|||
echo "╠══════════════════════════════════════════╣" |
|||
|
|||
if [ $success_count -gt 0 ]; then |
|||
echo "║ 成功: ║" |
|||
for item in $SUCCESS_CHANNELS; do |
|||
local name="${item%%:*}" |
|||
local dur="${item##*:}" |
|||
printf "║ ✅ %-12s (%s秒) ║\n" "$name" "$dur" |
|||
done |
|||
fi |
|||
|
|||
if [ $fail_count -gt 0 ]; then |
|||
echo "║ 失败: ║" |
|||
for name in $FAILED_CHANNELS; do |
|||
printf "║ ❌ %-12s ║\n" "$name" |
|||
done |
|||
fi |
|||
|
|||
echo "╠══════════════════════════════════════════╣" |
|||
printf "║ 成功: %d/%d 总耗时: %d分%d秒 ║\n" \ |
|||
"$success_count" "$total" "$total_min" "$total_sec" |
|||
echo "╚══════════════════════════════════════════╝" |
|||
|
|||
# 列出输出目录中的安装包文件 |
|||
if [ $success_count -gt 0 ]; then |
|||
echo "" |
|||
log_info "输出文件列表:" |
|||
ls -lh "${OUTPUT_DIR}/${APP_NAME}_${BUILD_DATE}"_* 2>/dev/null || true |
|||
fi |
|||
|
|||
# 如果有失败的渠道,退出码为 1 |
|||
if [ $fail_count -gt 0 ]; then |
|||
exit 1 |
|||
fi |
|||
} |
|||
|
|||
main "$@" |
|||
Loading…
Reference in new issue