mirror of
https://github.com/timeshiftsauce/CeruMusic.git
synced 2025-11-25 11:29:42 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57de7b49e8 | ||
|
|
42e17e83e7 | ||
|
|
380c273329 |
145
.github/workflows/main.yml
vendored
145
.github/workflows/main.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Build Electron App for macos
|
||||
if: matrix.os == 'macos-latest' # 只在macOS上运行
|
||||
run: |
|
||||
yarn run build:mac
|
||||
yarn run build:mac:universal
|
||||
|
||||
- name: Build Electron App for linux
|
||||
if: matrix.os == 'ubuntu-latest' # 只在Linux上运行
|
||||
@@ -70,146 +70,3 @@ jobs:
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: 'dist/**' # 将dist目录下所有文件添加到release
|
||||
|
||||
# 新增:自动同步到 WebDAV
|
||||
sync-to-webdav:
|
||||
name: Sync to WebDAV
|
||||
needs: release # 等待 release 任务完成
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v') # 只在标签推送时执行
|
||||
steps:
|
||||
- name: Wait for release to be ready
|
||||
run: |
|
||||
echo "等待 Release 准备就绪..."
|
||||
sleep 30 # 等待30秒确保 release 完全创建
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y curl jq
|
||||
|
||||
- name: Get latest release info
|
||||
id: get-release
|
||||
run: |
|
||||
# 获取当前标签对应的 release 信息
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
# 获取 release 详细信息
|
||||
response=$(curl -s \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG_NAME")
|
||||
|
||||
release_id=$(echo "$response" | jq -r '.id')
|
||||
echo "release_id=$release_id" >> $GITHUB_OUTPUT
|
||||
echo "找到 Release ID: $release_id"
|
||||
|
||||
- name: Sync release to WebDAV
|
||||
run: |
|
||||
TAG_NAME="${{ steps.get-release.outputs.tag_name }}"
|
||||
RELEASE_ID="${{ steps.get-release.outputs.release_id }}"
|
||||
|
||||
echo "🚀 开始同步版本 $TAG_NAME 到 WebDAV..."
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
echo "WebDAV 根路径: ${{ secrets.WEBDAV_BASE_URL }}/yd/ceru"
|
||||
|
||||
# 获取该release的所有资源文件
|
||||
assets_json=$(curl -s \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets")
|
||||
|
||||
assets_count=$(echo "$assets_json" | jq '. | length')
|
||||
echo "找到 $assets_count 个资源文件"
|
||||
|
||||
if [ "$assets_count" -eq 0 ]; then
|
||||
echo "⚠️ 该版本没有资源文件,跳过同步"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 先创建版本目录
|
||||
dir_path="/yd/ceru/$TAG_NAME"
|
||||
dir_url="${{ secrets.WEBDAV_BASE_URL }}$dir_path"
|
||||
|
||||
echo "创建版本目录: $dir_path"
|
||||
curl -s -X MKCOL \
|
||||
-u "${{ secrets.WEBDAV_USERNAME }}:${{ secrets.WEBDAV_PASSWORD }}" \
|
||||
"$dir_url" || echo "目录可能已存在"
|
||||
|
||||
# 处理每个asset
|
||||
success_count=0
|
||||
failed_count=0
|
||||
|
||||
for i in $(seq 0 $(($assets_count - 1))); do
|
||||
asset=$(echo "$assets_json" | jq -c ".[$i]")
|
||||
asset_name=$(echo "$asset" | jq -r '.name')
|
||||
asset_url=$(echo "$asset" | jq -r '.url')
|
||||
asset_size=$(echo "$asset" | jq -r '.size')
|
||||
|
||||
echo "📦 处理资源: $asset_name (大小: $asset_size bytes)"
|
||||
|
||||
# 下载资源文件
|
||||
safe_filename="./temp_${TAG_NAME}_$(date +%s)_$i"
|
||||
|
||||
if ! curl -sL -o "$safe_filename" \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/octet-stream" \
|
||||
"$asset_url"; then
|
||||
echo "❌ 下载失败: $asset_name"
|
||||
failed_count=$((failed_count + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -f "$safe_filename" ]; then
|
||||
actual_size=$(wc -c < "$safe_filename")
|
||||
if [ "$actual_size" -ne "$asset_size" ]; then
|
||||
echo "❌ 文件大小不匹配: $asset_name (期望: $asset_size, 实际: $actual_size)"
|
||||
rm -f "$safe_filename"
|
||||
failed_count=$((failed_count + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "⬆️ 上传到 WebDAV: $asset_name"
|
||||
|
||||
# 构建远程路径
|
||||
remote_path="/yd/ceru/$TAG_NAME/$asset_name"
|
||||
full_url="${{ secrets.WEBDAV_BASE_URL }}$remote_path"
|
||||
|
||||
# 使用 WebDAV PUT 方法上传文件
|
||||
if curl -s -f -X PUT \
|
||||
-u "${{ secrets.WEBDAV_USERNAME }}:${{ secrets.WEBDAV_PASSWORD }}" \
|
||||
-T "$safe_filename" \
|
||||
"$full_url"; then
|
||||
|
||||
echo "✅ 上传成功: $asset_name"
|
||||
success_count=$((success_count + 1))
|
||||
|
||||
else
|
||||
echo "❌ 上传失败: $asset_name"
|
||||
failed_count=$((failed_count + 1))
|
||||
fi
|
||||
|
||||
# 清理临时文件
|
||||
rm -f "$safe_filename"
|
||||
echo "----------------------------------------"
|
||||
else
|
||||
echo "❌ 临时文件不存在: $safe_filename"
|
||||
failed_count=$((failed_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
echo "🎉 同步完成!"
|
||||
echo "成功: $success_count 个文件"
|
||||
echo "失败: $failed_count 个文件"
|
||||
echo "总计: $assets_count 个文件"
|
||||
|
||||
- name: Notify completion
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ job.status }}" == "success" ]; then
|
||||
echo "✅ 版本 ${{ steps.get-release.outputs.tag_name }} 已成功同步到 alist"
|
||||
else
|
||||
echo "❌ 版本 ${{ steps.get-release.outputs.tag_name }} 同步失败"
|
||||
fi
|
||||
|
||||
215
.workflow/main copy.yml
Normal file
215
.workflow/main copy.yml
Normal file
@@ -0,0 +1,215 @@
|
||||
name: AutoBuild # 工作流的名称
|
||||
|
||||
permissions:
|
||||
contents: write # 给予写入仓库内容的权限
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # 当推送以v开头的标签时触发此工作流
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: build and release electron app # 任务名称
|
||||
runs-on: ${{ matrix.os }} # 在matrix.os定义的操作系统上运行
|
||||
strategy:
|
||||
fail-fast: false # 如果一个任务失败,其他任务继续运行
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest] # 在Windows和macOS上运行任务
|
||||
|
||||
steps:
|
||||
- name: Check out git repository
|
||||
uses: actions/checkout@v4 # 检出代码仓库
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22 # 安装Node.js 22 (这里node环境是能够运行代码的环境)
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm i -g yarn
|
||||
yarn install # 安装项目依赖
|
||||
|
||||
- name: Build Electron App for windows
|
||||
if: matrix.os == 'windows-latest' # 只在Windows上运行
|
||||
run: yarn run build:win # 构建Windows版应用
|
||||
|
||||
- name: Build Electron App for macos
|
||||
if: matrix.os == 'macos-latest' # 只在macOS上运行
|
||||
run: |
|
||||
yarn run build:mac
|
||||
|
||||
- name: Build Electron App for linux
|
||||
if: matrix.os == 'ubuntu-latest' # 只在Linux上运行
|
||||
run: yarn run build:linux # 构建Linux版应用
|
||||
|
||||
- name: Cleanup Artifacts for Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
npx del-cli "dist/*" "!dist/*.exe" "!dist/*.zip" "!dist/*.yml" # 清理Windows构建产物,只保留特定文件
|
||||
|
||||
- name: Cleanup Artifacts for MacOS
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
npx del-cli "dist/*" "!dist/(*.dmg|*.zip|latest*.yml)" # 清理macOS构建产物,只保留特定文件
|
||||
|
||||
- name: Cleanup Artifacts for Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
npx del-cli "dist/*" "!dist/(*.AppImage|*.deb|*.snap|latest*.yml)" # 清理Linux构建产物,只保留特定文件
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}
|
||||
path: dist # 上传构建产物作为工作流artifact
|
||||
|
||||
- name: release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: 'dist/**' # 将dist目录下所有文件添加到release
|
||||
|
||||
# 新增:自动同步到 WebDAV
|
||||
sync-to-webdav:
|
||||
name: Sync to WebDAV
|
||||
needs: release # 等待 release 任务完成
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v') # 只在标签推送时执行
|
||||
steps:
|
||||
- name: Wait for release to be ready
|
||||
run: |
|
||||
echo "等待 Release 准备就绪..."
|
||||
sleep 30 # 等待30秒确保 release 完全创建
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y curl jq
|
||||
|
||||
- name: Get latest release info
|
||||
id: get-release
|
||||
run: |
|
||||
# 获取当前标签对应的 release 信息
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
# 获取 release 详细信息
|
||||
response=$(curl -s \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG_NAME")
|
||||
|
||||
release_id=$(echo "$response" | jq -r '.id')
|
||||
echo "release_id=$release_id" >> $GITHUB_OUTPUT
|
||||
echo "找到 Release ID: $release_id"
|
||||
|
||||
- name: Sync release to WebDAV
|
||||
run: |
|
||||
TAG_NAME="${{ steps.get-release.outputs.tag_name }}"
|
||||
RELEASE_ID="${{ steps.get-release.outputs.release_id }}"
|
||||
|
||||
echo "🚀 开始同步版本 $TAG_NAME 到 WebDAV..."
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
echo "WebDAV 根路径: ${{ secrets.WEBDAV_BASE_URL }}/yd/ceru"
|
||||
|
||||
# 获取该release的所有资源文件
|
||||
assets_json=$(curl -s \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets")
|
||||
|
||||
assets_count=$(echo "$assets_json" | jq '. | length')
|
||||
echo "找到 $assets_count 个资源文件"
|
||||
|
||||
if [ "$assets_count" -eq 0 ]; then
|
||||
echo "⚠️ 该版本没有资源文件,跳过同步"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 先创建版本目录
|
||||
dir_path="/yd/ceru/$TAG_NAME"
|
||||
dir_url="${{ secrets.WEBDAV_BASE_URL }}$dir_path"
|
||||
|
||||
echo "创建版本目录: $dir_path"
|
||||
curl -s -X MKCOL \
|
||||
-u "${{ secrets.WEBDAV_USERNAME }}:${{ secrets.WEBDAV_PASSWORD }}" \
|
||||
"$dir_url" || echo "目录可能已存在"
|
||||
|
||||
# 处理每个asset
|
||||
success_count=0
|
||||
failed_count=0
|
||||
|
||||
for i in $(seq 0 $(($assets_count - 1))); do
|
||||
asset=$(echo "$assets_json" | jq -c ".[$i]")
|
||||
asset_name=$(echo "$asset" | jq -r '.name')
|
||||
asset_url=$(echo "$asset" | jq -r '.url')
|
||||
asset_size=$(echo "$asset" | jq -r '.size')
|
||||
|
||||
echo "📦 处理资源: $asset_name (大小: $asset_size bytes)"
|
||||
|
||||
# 下载资源文件
|
||||
safe_filename="./temp_${TAG_NAME}_$(date +%s)_$i"
|
||||
|
||||
if ! curl -sL -o "$safe_filename" \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/octet-stream" \
|
||||
"$asset_url"; then
|
||||
echo "❌ 下载失败: $asset_name"
|
||||
failed_count=$((failed_count + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -f "$safe_filename" ]; then
|
||||
actual_size=$(wc -c < "$safe_filename")
|
||||
if [ "$actual_size" -ne "$asset_size" ]; then
|
||||
echo "❌ 文件大小不匹配: $asset_name (期望: $asset_size, 实际: $actual_size)"
|
||||
rm -f "$safe_filename"
|
||||
failed_count=$((failed_count + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "⬆️ 上传到 WebDAV: $asset_name"
|
||||
|
||||
# 构建远程路径
|
||||
remote_path="/yd/ceru/$TAG_NAME/$asset_name"
|
||||
full_url="${{ secrets.WEBDAV_BASE_URL }}$remote_path"
|
||||
|
||||
# 使用 WebDAV PUT 方法上传文件
|
||||
if curl -s -f -X PUT \
|
||||
-u "${{ secrets.WEBDAV_USERNAME }}:${{ secrets.WEBDAV_PASSWORD }}" \
|
||||
-T "$safe_filename" \
|
||||
"$full_url"; then
|
||||
|
||||
echo "✅ 上传成功: $asset_name"
|
||||
success_count=$((success_count + 1))
|
||||
|
||||
else
|
||||
echo "❌ 上传失败: $asset_name"
|
||||
failed_count=$((failed_count + 1))
|
||||
fi
|
||||
|
||||
# 清理临时文件
|
||||
rm -f "$safe_filename"
|
||||
echo "----------------------------------------"
|
||||
else
|
||||
echo "❌ 临时文件不存在: $safe_filename"
|
||||
failed_count=$((failed_count + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
echo "🎉 同步完成!"
|
||||
echo "成功: $success_count 个文件"
|
||||
echo "失败: $failed_count 个文件"
|
||||
echo "总计: $assets_count 个文件"
|
||||
|
||||
- name: Notify completion
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ job.status }}" == "success" ]; then
|
||||
echo "✅ 版本 ${{ steps.get-release.outputs.tag_name }} 已成功同步到 alist"
|
||||
else
|
||||
echo "❌ 版本 ${{ steps.get-release.outputs.tag_name }} 同步失败"
|
||||
fi
|
||||
@@ -41,7 +41,8 @@ export default defineConfig({
|
||||
{ text: '插件类使用', link: '/guide/CeruMusicPluginHost' },
|
||||
{ text: '澜音插件开发文档(重点)', link: '/guide/CeruMusicPluginDev' }
|
||||
]
|
||||
},{
|
||||
},
|
||||
{
|
||||
text: '鸣谢名单',
|
||||
link: '/guide/sponsorship'
|
||||
}
|
||||
|
||||
@@ -138,7 +138,6 @@ module.exports = {
|
||||
> - kg 酷狗音乐 |
|
||||
> - mg 咪咕音乐 |
|
||||
> - kw 酷我音乐
|
||||
>
|
||||
> - 导出
|
||||
>
|
||||
> ```javascript
|
||||
|
||||
@@ -5,4 +5,3 @@
|
||||
| 昵称 | 赞助金额 |
|
||||
| :------------------------: | :------: |
|
||||
| **群友**:可惜情绪落泪零碎 | 6.66 |
|
||||
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
## 日志
|
||||
|
||||
- ###### 2025-9-26 (v1.3.8)
|
||||
|
||||
1. 写入歌曲tag信息
|
||||
2. 歌曲下载 选择音质
|
||||
3. 歌单 头部自动压缩
|
||||
|
||||
- ###### 2025-9-25 (v1.3.7)
|
||||
|
||||
- ###### 2025-9-25 (v1.3.7)
|
||||
1. 歌单
|
||||
- 新增右键移除歌曲
|
||||
- local 页歌单右键操作
|
||||
@@ -17,7 +15,6 @@
|
||||
2. debug:右键菜单二级菜单位置决策
|
||||
|
||||
- ###### 2025-9-22 (v1.3.6)
|
||||
|
||||
1. 歌单列表可以右键操作
|
||||
- 播放
|
||||
- 下载
|
||||
@@ -27,7 +24,6 @@
|
||||
3. 搜索页切换源重新加载
|
||||
|
||||
- ###### 2025-9-22 (v1.3.5)
|
||||
|
||||
1. 软件启动位置 宽高记忆 限制软件最大宽高
|
||||
2. debug: 修复歌曲音质支持短缺问题
|
||||
|
||||
|
||||
@@ -21,18 +21,16 @@ win:
|
||||
arch:
|
||||
- x64
|
||||
- ia32
|
||||
# 简化版本信息设置,避免rcedit错误
|
||||
- target: zip
|
||||
arch:
|
||||
- x64
|
||||
- ia32
|
||||
fileAssociations:
|
||||
- ext: cerumusic
|
||||
name: CeruMusic File
|
||||
description: CeruMusic playlist file
|
||||
# 如果有证书文件,取消注释以下配置
|
||||
# certificateFile: path/to/certificate.p12
|
||||
# certificatePassword: your-password
|
||||
# 或者使用证书存储
|
||||
# certificateSubjectName: "Your Company Name"
|
||||
nsis:
|
||||
artifactName: ${name}-${version}-${arch}-setup.${ext}
|
||||
artifactName: ${name}-${version}-win-${arch}-setup.${ext}
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
@@ -44,26 +42,43 @@ nsis:
|
||||
mac:
|
||||
icon: 'resources/icons/icon.icns'
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
target:
|
||||
- target: dmg
|
||||
arch:
|
||||
- universal
|
||||
- target: zip
|
||||
arch:
|
||||
- universal
|
||||
extendInfo:
|
||||
- NSDocumentsFolderUsageDescription: 需要访问文档文件夹来保存和打开您创建的文件。
|
||||
- NSDownloadsFolderUsageDescription: 需要访问下载文件夹来管理您下载的内容。
|
||||
- NSDownloadsFolderUsageDescription: 需要访问下载文件夹来管理您下载的歌曲。
|
||||
notarize: false
|
||||
dmg:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
artifactName: ${name}-${version}-${arch}.${ext}
|
||||
title: ${productName}
|
||||
linux:
|
||||
icon: 'resources/icons'
|
||||
target:
|
||||
- AppImage
|
||||
- snap
|
||||
- deb
|
||||
- target: AppImage
|
||||
arch:
|
||||
- x64
|
||||
- target: snap
|
||||
arch:
|
||||
- x64
|
||||
- target: deb
|
||||
arch:
|
||||
- x64
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
artifactName: ${name}-${version}-linux-${arch}.${ext}
|
||||
snap:
|
||||
artifactName: ${name}-${version}-linux-${arch}.${ext}
|
||||
deb:
|
||||
artifactName: ${name}-${version}-linux-${arch}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://update.ceru.shiqianjiang.cn
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ceru-music",
|
||||
"version": "1.3.9",
|
||||
"version": "1.3.10",
|
||||
"description": "一款简洁优雅的音乐播放器",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "sqj,wldss,star",
|
||||
@@ -18,9 +18,12 @@
|
||||
"onlybuild": "electron-vite build && electron-builder --win --x64",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "yarn run build && electron-builder --dir",
|
||||
"build:win": "yarn run build && electron-builder --win --config --publish never",
|
||||
"build:win": "yarn run build && electron-builder --win --x64 --config --publish never",
|
||||
"build:win32": "yarn run build && electron-builder --win --ia32 --config --publish never",
|
||||
"build:mac": "yarn run build && electron-builder --mac --config --publish never",
|
||||
"build:mac:intel": "yarn run build && electron-builder --mac --x64 --config --publish never",
|
||||
"build:mac:arm64": "yarn run build && electron-builder --mac --arm64 --config --publish never",
|
||||
"build:mac:universal": "yarn run build && electron-builder --mac --universal --config --publish never",
|
||||
"build:linux": "yarn run build && electron-builder --linux --config --publish never",
|
||||
"build:deps": "electron-builder install-app-deps && yarn run build && electron-builder --win --x64 --config",
|
||||
"buildico": "electron-icon-builder --input=./resources/logo.png --output=resources --flatten",
|
||||
|
||||
2
src/renderer/auto-imports.d.ts
vendored
2
src/renderer/auto-imports.d.ts
vendored
@@ -6,5 +6,5 @@
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
const DialogPlugin: (typeof import('tdesign-vue-next'))['DialogPlugin']
|
||||
}
|
||||
|
||||
2
src/renderer/components.d.ts
vendored
2
src/renderer/components.d.ts
vendored
@@ -23,6 +23,7 @@ declare module 'vue' {
|
||||
PlaylistSettings: typeof import('./src/components/Settings/PlaylistSettings.vue')['default']
|
||||
PlayMusic: typeof import('./src/components/Play/PlayMusic.vue')['default']
|
||||
PluginNoticeDialog: typeof import('./src/components/PluginNoticeDialog.vue')['default']
|
||||
Plugins: typeof import('./src/components/Settings/plugins.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ShaderBackground: typeof import('./src/components/Play/ShaderBackground.vue')['default']
|
||||
@@ -41,7 +42,6 @@ declare module 'vue' {
|
||||
TFormItem: typeof import('tdesign-vue-next')['FormItem']
|
||||
ThemeSelector: typeof import('./src/components/ThemeSelector.vue')['default']
|
||||
TIcon: typeof import('tdesign-vue-next')['Icon']
|
||||
TImage: typeof import('tdesign-vue-next')['Image']
|
||||
TInput: typeof import('tdesign-vue-next')['Input']
|
||||
TitleBarControls: typeof import('./src/components/TitleBarControls.vue')['default']
|
||||
TLayout: typeof import('tdesign-vue-next')['Layout']
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 歌曲信息 -->
|
||||
<div class="col-title" @dblclick="handleAddToPlaylist(song)">
|
||||
<div class="col-title" @click="handleSongClick(song)">
|
||||
<div v-if="song.img" class="song-cover">
|
||||
<img :src="song.img" loading="lazy" alt="封面" />
|
||||
</div>
|
||||
@@ -175,6 +175,11 @@ const scrollTop = ref(0)
|
||||
const visibleStartIndex = ref(0)
|
||||
const visibleEndIndex = ref(0)
|
||||
|
||||
// 点击防抖相关状态
|
||||
let clickTimer: NodeJS.Timeout | null = null
|
||||
let lastClickTime = 0
|
||||
const doubleClickDelay = 300 // 300ms 内的第二次点击视为双击
|
||||
|
||||
// 右键菜单相关状态
|
||||
const contextMenuVisible = ref(false)
|
||||
const contextMenuPosition = ref<ContextMenuPosition>({ x: 0, y: 0 })
|
||||
@@ -209,21 +214,9 @@ const visibleItems = computed(() => {
|
||||
return props.songs.slice(visibleStartIndex.value, visibleEndIndex.value)
|
||||
})
|
||||
|
||||
// 判断是否为当前歌曲
|
||||
const isCurrentSong = (song: Song) => {
|
||||
return (
|
||||
props.currentSong &&
|
||||
(song.id === props.currentSong.id || song.songmid === props.currentSong.songmid)
|
||||
)
|
||||
}
|
||||
|
||||
// 处理播放
|
||||
const handlePlay = (song: Song) => {
|
||||
if (isCurrentSong(song) && props.isPlaying) {
|
||||
emit('pause')
|
||||
} else {
|
||||
emit('play', song)
|
||||
}
|
||||
emit('play', song)
|
||||
}
|
||||
|
||||
// 处理添加到播放列表
|
||||
@@ -231,6 +224,31 @@ const handleAddToPlaylist = (song: Song) => {
|
||||
emit('addToPlaylist', song)
|
||||
}
|
||||
|
||||
// 处理歌曲点击事件
|
||||
const handleSongClick = (song: Song) => {
|
||||
const currentTime = Date.now()
|
||||
const timeDiff = currentTime - lastClickTime
|
||||
|
||||
// 清除之前的定时器
|
||||
if (clickTimer) {
|
||||
clearTimeout(clickTimer)
|
||||
clickTimer = null
|
||||
}
|
||||
|
||||
if (timeDiff < doubleClickDelay && timeDiff > 0) {
|
||||
// 双击:立即执行播放操作
|
||||
handlePlay(song)
|
||||
lastClickTime = 0 // 重置时间,防止三击
|
||||
} else {
|
||||
// 单击:延迟执行添加到播放列表
|
||||
lastClickTime = currentTime
|
||||
clickTimer = setTimeout(() => {
|
||||
handleAddToPlaylist(song)
|
||||
clickTimer = null
|
||||
}, doubleClickDelay)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化时长
|
||||
const formatDuration = (duration: string | number) => {
|
||||
if (!duration) return '--:--'
|
||||
@@ -326,10 +344,10 @@ const contextMenuItems = computed((): ContextMenuItem[] => {
|
||||
}
|
||||
})
|
||||
)
|
||||
// 添加分隔线
|
||||
baseItems.push(createSeparator())
|
||||
// 如果是本地歌单,添加"移出本地歌单"选项
|
||||
if (props.isLocalPlaylist) {
|
||||
// 添加分隔线
|
||||
baseItems.push(createSeparator())
|
||||
baseItems.push(
|
||||
createMenuItem('removeFromLocalPlaylist', '移出当前歌单', {
|
||||
icon: DeleteIcon,
|
||||
|
||||
@@ -394,8 +394,10 @@ const lightMainColor = computed(() => {
|
||||
.fullscreen-btn,
|
||||
.putawayscreen-btn {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
|
||||
-webkit-app-region: no-drag;
|
||||
top: 25px;
|
||||
left: 30px;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
@@ -442,7 +444,7 @@ const lightMainColor = computed(() => {
|
||||
}
|
||||
|
||||
.putawayscreen-btn {
|
||||
left: 100px;
|
||||
left: 90px;
|
||||
}
|
||||
|
||||
.full-play {
|
||||
@@ -468,8 +470,8 @@ const lightMainColor = computed(() => {
|
||||
.top {
|
||||
position: absolute;
|
||||
width: calc(100% - 200px);
|
||||
margin-left: 200px;
|
||||
z-index: 1;
|
||||
right: 0;
|
||||
padding: 30px 30px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -1011,7 +1011,7 @@ watch(showFullPlay, (val) => {
|
||||
/* 进度条样式 */
|
||||
.progress-bar-container {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
height: 4px;
|
||||
position: absolute;
|
||||
// padding-top: 2px;
|
||||
cursor: pointer;
|
||||
@@ -1019,7 +1019,7 @@ watch(showFullPlay, (val) => {
|
||||
|
||||
&:has(.progress-handle.dragging, *:hover) {
|
||||
// margin-bottom: 0;
|
||||
height: 4px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { ref, computed, nextTick, onUnmounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { LocalUserDetailStore } from '@renderer/store/LocalUserDetail'
|
||||
import { MessagePlugin, Popconfirm } from 'tdesign-vue-next'
|
||||
import { LocationIcon, DeleteIcon } from 'tdesign-icons-vue-next'
|
||||
import type { SongList } from '@renderer/types/audio'
|
||||
|
||||
// Props
|
||||
@@ -413,6 +415,33 @@ onUnmounted(() => {
|
||||
stopAutoScroll()
|
||||
})
|
||||
|
||||
// 清空播放列表
|
||||
const handleClearPlaylist = () => {
|
||||
if (list.value.length === 0) {
|
||||
MessagePlugin.warning('播放列表已为空')
|
||||
return
|
||||
}
|
||||
|
||||
localUserStore.clearList()
|
||||
MessagePlugin.success('播放列表已清空')
|
||||
}
|
||||
|
||||
// 定位到当前播放歌曲
|
||||
const handleLocateCurrentSong = () => {
|
||||
if (!props.currentSongId) {
|
||||
MessagePlugin.info('当前没有正在播放的歌曲')
|
||||
return
|
||||
}
|
||||
|
||||
const currentSongExists = list.value.some((song) => song.songmid === props.currentSongId)
|
||||
if (!currentSongExists) {
|
||||
MessagePlugin.warning('当前播放的歌曲不在播放列表中')
|
||||
return
|
||||
}
|
||||
|
||||
scrollToCurrentSong()
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
scrollToCurrentSong
|
||||
@@ -484,6 +513,35 @@ defineExpose({
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<div v-if="list.length > 0" class="playlist-footer">
|
||||
<button
|
||||
class="playlist-action-btn locate-btn"
|
||||
:disabled="!currentSongId"
|
||||
@click="handleLocateCurrentSong"
|
||||
>
|
||||
<LocationIcon size="16" />
|
||||
<span>定位当前播放</span>
|
||||
</button>
|
||||
<Popconfirm
|
||||
content="确定要清空播放列表吗?此操作不可撤销。"
|
||||
:confirm-btn="{ content: '确认清空', theme: 'danger' }"
|
||||
cancel-btn="取消"
|
||||
placement="top"
|
||||
theme="warning"
|
||||
:popup-props="{
|
||||
zIndex: 9999,
|
||||
overlayStyle: { zIndex: 9998 }
|
||||
}"
|
||||
@confirm="handleClearPlaylist"
|
||||
>
|
||||
<button class="playlist-action-btn clear-btn">
|
||||
<DeleteIcon size="16" />
|
||||
<span>清空播放列表</span>
|
||||
</button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
@@ -509,6 +567,7 @@ defineExpose({
|
||||
flex-direction: column;
|
||||
color: #333;
|
||||
transform: translateX(0);
|
||||
overflow: hidden;
|
||||
/* 初始位置 */
|
||||
}
|
||||
|
||||
@@ -539,6 +598,7 @@ defineExpose({
|
||||
|
||||
/* 全屏模式下的滚动条样式 - 只显示滑块 */
|
||||
.playlist-container .playlist-content {
|
||||
scrollbar-arrow-color: transparent;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(91, 91, 91, 0.3) transparent;
|
||||
}
|
||||
@@ -834,6 +894,153 @@ defineExpose({
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
/* 播放列表底部操作按钮 */
|
||||
.playlist-footer {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
flex-shrink: 0;
|
||||
// background: rgba(255, 255, 255, 0.3);
|
||||
// backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.playlist-container.full-screen-mode .playlist-footer {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
// background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.playlist-action-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.locate-btn {
|
||||
background: rgba(35, 115, 206, 0.1);
|
||||
color: #2373ce;
|
||||
border: 1px solid rgba(35, 115, 206, 0.2);
|
||||
}
|
||||
|
||||
.locate-btn:hover:not(:disabled) {
|
||||
background: rgba(35, 115, 206, 0.15);
|
||||
border-color: rgba(35, 115, 206, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.locate-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
color: #999;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
background: rgba(229, 72, 77, 0.1);
|
||||
color: #e5484d;
|
||||
border: 1px solid rgba(229, 72, 77, 0.2);
|
||||
}
|
||||
|
||||
.clear-btn:hover {
|
||||
background: rgba(229, 72, 77, 0.15);
|
||||
border-color: rgba(229, 72, 77, 0.3);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// /* 全屏模式下的按钮样式 */
|
||||
// .playlist-container.full-screen-mode .locate-btn {
|
||||
// background: rgba(255, 255, 255, 0.1);
|
||||
// color: #87ceeb;
|
||||
// border-color: rgba(255, 255, 255, 0.2);
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode .locate-btn:hover:not(:disabled) {
|
||||
// background: rgba(255, 255, 255, 0.15);
|
||||
// border-color: rgba(255, 255, 255, 0.3);
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode .locate-btn:disabled {
|
||||
// color: #666;
|
||||
// background: rgba(255, 255, 255, 0.05);
|
||||
// border-color: rgba(255, 255, 255, 0.1);
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode .clear-btn {
|
||||
// background: rgba(255, 255, 255, 0.1);
|
||||
// color: #ff6b6b;
|
||||
// border-color: rgba(255, 255, 255, 0.2);
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode .clear-btn:hover {
|
||||
// background: rgba(255, 255, 255, 0.15);
|
||||
// border-color: rgba(255, 255, 255, 0.3);
|
||||
// }
|
||||
|
||||
// /* Popconfirm 样式适配 */
|
||||
// .playlist-container :deep(.t-popup__content) {
|
||||
// background: rgba(255, 255, 255, 0.95) !important;
|
||||
// backdrop-filter: blur(20px) !important;
|
||||
// border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
||||
// box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15) !important;
|
||||
// border-radius: 8px !important;
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode :deep(.t-popup__content) {
|
||||
// background: rgba(0, 0, 0, 0.85) !important;
|
||||
// backdrop-filter: blur(20px) !important;
|
||||
// border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||
// box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode :deep(.t-popconfirm__content) {
|
||||
// color: #fff !important;
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode :deep(.t-button--theme-default) {
|
||||
// background: rgba(255, 255, 255, 0.1) !important;
|
||||
// border-color: rgba(255, 255, 255, 0.2) !important;
|
||||
// color: #fff !important;
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode :deep(.t-button--theme-default:hover) {
|
||||
// background: rgba(255, 255, 255, 0.15) !important;
|
||||
// border-color: rgba(255, 255, 255, 0.3) !important;
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode :deep(.t-button--theme-danger) {
|
||||
// background: rgba(229, 72, 77, 0.8) !important;
|
||||
// border-color: rgba(229, 72, 77, 0.9) !important;
|
||||
// }
|
||||
|
||||
// .playlist-container.full-screen-mode :deep(.t-button--theme-danger:hover) {
|
||||
// background: rgba(229, 72, 77, 0.9) !important;
|
||||
// border-color: rgba(229, 72, 77, 1) !important;
|
||||
// }
|
||||
|
||||
// /* 普通模式下的按钮样式优化 */
|
||||
// .playlist-container :deep(.t-button--theme-danger) {
|
||||
// background: rgba(229, 72, 77, 0.1) !important;
|
||||
// border-color: rgba(229, 72, 77, 0.3) !important;
|
||||
// color: #e5484d !important;
|
||||
// }
|
||||
|
||||
// .playlist-container :deep(.t-button--theme-danger:hover) {
|
||||
// background: rgba(229, 72, 77, 0.15) !important;
|
||||
// border-color: rgba(229, 72, 77, 0.4) !important;
|
||||
// }
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.playlist-container {
|
||||
@@ -841,5 +1048,22 @@ defineExpose({
|
||||
right: 0;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.playlist-footer {
|
||||
padding: 10px 12px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.playlist-action-btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// /* 移动端 Popconfirm 适配 */
|
||||
// :deep(.playlist-popconfirm .t-popup__content),
|
||||
// :deep(.playlist-popconfirm-fullscreen .t-popup__content) {
|
||||
// max-width: 280px;
|
||||
// font-size: 14px;
|
||||
// }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<TitleBarControls title="插件管理" :show-back="true" class="header"></TitleBarControls>
|
||||
<!-- <TitleBarControls title="插件管理" :show-back="true" class="header"></TitleBarControls> -->
|
||||
<div class="plugins-container">
|
||||
<h2>插件管理</h2>
|
||||
<div class="plugin-actions-hearder">
|
||||
<h2>插件管理</h2>
|
||||
|
||||
<div class="plugin-actions">
|
||||
<t-button theme="primary" @click="plugTypeDialog = true">
|
||||
<template #icon><t-icon name="add" /></template> 添加插件
|
||||
</t-button>
|
||||
<t-dialog
|
||||
:visible="plugTypeDialog"
|
||||
:close-btn="true"
|
||||
confirm-btn="确定"
|
||||
cancel-btn="取消"
|
||||
:on-confirm="addPlug"
|
||||
:on-close="() => (plugTypeDialog = false)"
|
||||
>
|
||||
<template #header>请选择你的插件类别</template>
|
||||
<template #body>
|
||||
<t-radio-group v-model="type" variant="primary-filled" default-value="cr">
|
||||
<t-radio-button value="cr">澜音插件</t-radio-button>
|
||||
<t-radio-button value="lx">洛雪插件</t-radio-button>
|
||||
</t-radio-group>
|
||||
</template>
|
||||
</t-dialog>
|
||||
<t-button theme="default" @click="refreshPlugins">
|
||||
<template #icon><t-icon name="refresh" /></template> 刷新
|
||||
</t-button>
|
||||
<div class="plugin-actions">
|
||||
<t-button theme="primary" @click="plugTypeDialog = true">
|
||||
<template #icon><t-icon name="add" /></template> 添加插件
|
||||
</t-button>
|
||||
<t-dialog
|
||||
:visible="plugTypeDialog"
|
||||
:close-btn="true"
|
||||
confirm-btn="确定"
|
||||
cancel-btn="取消"
|
||||
:on-confirm="addPlug"
|
||||
:on-close="() => (plugTypeDialog = false)"
|
||||
>
|
||||
<template #header>请选择你的插件类别</template>
|
||||
<template #body>
|
||||
<t-radio-group v-model="type" variant="primary-filled" default-value="cr">
|
||||
<t-radio-button value="cr">澜音插件</t-radio-button>
|
||||
<t-radio-button value="lx">洛雪插件</t-radio-button>
|
||||
</t-radio-group>
|
||||
</template>
|
||||
</t-dialog>
|
||||
<t-button theme="default" @click="refreshPlugins">
|
||||
<template #icon><t-icon name="refresh" /></template> 刷新
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">
|
||||
@@ -179,7 +181,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TitleBarControls from '@renderer/components/TitleBarControls.vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { MessagePlugin, DialogPlugin } from 'tdesign-vue-next'
|
||||
import { LocalUserDetailStore } from '@renderer/store/LocalUserDetail'
|
||||
@@ -514,6 +515,9 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
-webkit-app-region: drag;
|
||||
@@ -540,7 +544,7 @@ onMounted(async () => {
|
||||
|
||||
.plugin-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -613,7 +617,7 @@ onMounted(async () => {
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--color-background-soft, #f8f9fa);
|
||||
background-color: #fefefe;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
@@ -46,12 +46,12 @@ const routes: RouteRecordRaw[] = [
|
||||
transitionOut: 'animate__fadeOut'
|
||||
},
|
||||
component: () => import('@renderer/views/settings/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/plugins',
|
||||
name: 'plugins',
|
||||
component: () => import('@renderer/views/settings/plugins.vue')
|
||||
}
|
||||
// {
|
||||
// path: '/plugins',
|
||||
// name: 'plugins',
|
||||
// component: () => import('@renderer/views/settings/plugins.vue')
|
||||
// }
|
||||
]
|
||||
function setAnimate(routerObj: RouteRecordRaw[]) {
|
||||
for (let i = 0; i < routerObj.length; i++) {
|
||||
|
||||
@@ -923,7 +923,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="page">
|
||||
<div class="local-container">
|
||||
<!-- 页面标题和操作 -->
|
||||
<div class="page-header">
|
||||
@@ -1419,6 +1419,10 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
width: 100%;
|
||||
// height: 100%;
|
||||
}
|
||||
.local-container {
|
||||
padding: 2rem;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
SaveIcon
|
||||
} from 'tdesign-icons-vue-next'
|
||||
import fonts from '@renderer/assets/icon_font/icons'
|
||||
import { useRouter } from 'vue-router'
|
||||
import DirectorySettings from '@renderer/components/Settings/DirectorySettings.vue'
|
||||
import MusicCache from '@renderer/components/Settings/MusicCache.vue'
|
||||
import AIFloatBallSettings from '@renderer/components/Settings/AIFloatBallSettings.vue'
|
||||
@@ -180,9 +179,8 @@ const clearAPIKey = (): void => {
|
||||
console.log('DeepSeek API Key 已清空')
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const goPlugin = () => {
|
||||
router.push('/plugins')
|
||||
switchCategory('plugins')
|
||||
}
|
||||
|
||||
// 音乐源和音质配置相关
|
||||
@@ -497,14 +495,15 @@ const getTagOptionsStatus = () => {
|
||||
|
||||
<!-- 插件管理 -->
|
||||
<div v-else-if="activeCategory === 'plugins'" key="plugins" class="settings-section">
|
||||
<div class="setting-group">
|
||||
<!-- <div class="setting-group">
|
||||
<h3>插件管理</h3>
|
||||
<p>管理和配置应用插件,扩展音乐播放器功能</p>
|
||||
<t-button theme="primary" @click="goPlugin">
|
||||
<TreeRoundDotIcon style="margin-right: 0.5em" />
|
||||
打开插件管理
|
||||
</t-button>
|
||||
</div>
|
||||
</div> -->
|
||||
<plugins />
|
||||
</div>
|
||||
|
||||
<!-- 音乐源配置 -->
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="nav-links">
|
||||
<a href="#features">功能特色</a>
|
||||
<a href="#download">下载</a>
|
||||
<a href="./CeruUse.html" target="_blank">文档</a>
|
||||
<a href="https://ceru.docs.shiqianjiang.cn/" target="_blank">文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -183,6 +183,9 @@ async function downloadApp(platform) {
|
||||
button.disabled = true
|
||||
|
||||
try {
|
||||
// Detect user's architecture for better matching
|
||||
const userArch = detectArchitecture()
|
||||
|
||||
// Try Alist first
|
||||
const versions = await getAlistVersions()
|
||||
|
||||
@@ -190,15 +193,16 @@ async function downloadApp(platform) {
|
||||
const latestVersion = versions[0]
|
||||
const files = await getAlistVersionFiles(latestVersion.name)
|
||||
|
||||
// Find the appropriate file for the platform
|
||||
const fileName = findFileForPlatform(files, platform)
|
||||
// Find the appropriate file for the platform and architecture
|
||||
const fileName = findFileForPlatform(files, platform, userArch)
|
||||
|
||||
if (fileName) {
|
||||
const downloadUrl = await getAlistDownloadUrl(latestVersion.name, fileName)
|
||||
|
||||
// Show success notification
|
||||
// Show success notification with architecture info
|
||||
const archInfo = getArchitectureInfo(fileName)
|
||||
showNotification(
|
||||
`正在下载 ${getPlatformName(platform)} 版本 ${latestVersion.name}...`,
|
||||
`正在下载 ${getPlatformName(platform)} ${archInfo} 版本 ${latestVersion.name}...`,
|
||||
'success'
|
||||
)
|
||||
|
||||
@@ -206,21 +210,19 @@ async function downloadApp(platform) {
|
||||
window.open(downloadUrl, '_blank')
|
||||
|
||||
// Track download
|
||||
trackDownload(platform, latestVersion.name)
|
||||
trackDownload(platform, latestVersion.name, fileName)
|
||||
|
||||
return // Success, exit function
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to GitHub if Alist fails
|
||||
console.log('Alist download failed, trying GitHub fallback...')
|
||||
await downloadFromGitHub(platform)
|
||||
} catch (error) {
|
||||
console.error('Download error:', error)
|
||||
|
||||
// Try GitHub fallback
|
||||
try {
|
||||
console.log('Trying GitHub fallback...')
|
||||
await downloadFromGitHub(platform)
|
||||
} catch (fallbackError) {
|
||||
console.error('GitHub fallback also failed:', fallbackError)
|
||||
@@ -249,20 +251,28 @@ async function downloadFromGitHub(platform) {
|
||||
throw new Error('无法获取最新版本信息')
|
||||
}
|
||||
|
||||
const downloadUrl = findDownloadAsset(release.assets, platform)
|
||||
const userArch = detectArchitecture()
|
||||
const downloadUrl = findDownloadAsset(release.assets, platform, userArch)
|
||||
|
||||
if (!downloadUrl) {
|
||||
throw new Error(`暂无 ${getPlatformName(platform)} 版本下载`)
|
||||
}
|
||||
|
||||
// Find the asset to get architecture info
|
||||
const asset = release.assets.find((a) => a.browser_download_url === downloadUrl)
|
||||
const archInfo = asset ? getArchitectureInfo(asset.name) : ''
|
||||
|
||||
// Show success notification
|
||||
showNotification(`正在下载 ${getPlatformName(platform)} 版本 v${release.tag_name}...`, 'success')
|
||||
showNotification(
|
||||
`正在下载 ${getPlatformName(platform)} ${archInfo} 版本 v${release.tag_name}...`,
|
||||
'success'
|
||||
)
|
||||
|
||||
// Start download
|
||||
window.open(downloadUrl, '_blank')
|
||||
|
||||
// Track download
|
||||
trackDownload(platform, release.tag_name)
|
||||
trackDownload(platform, release.tag_name, asset ? asset.name : '')
|
||||
}
|
||||
|
||||
// Get latest release from GitHub API
|
||||
@@ -294,7 +304,7 @@ async function getLatestRelease() {
|
||||
}
|
||||
|
||||
// Find appropriate file for platform from Alist files
|
||||
function findFileForPlatform(files, platform) {
|
||||
function findFileForPlatform(files, platform, userArch = null) {
|
||||
if (!files || !Array.isArray(files)) {
|
||||
return null
|
||||
}
|
||||
@@ -313,53 +323,102 @@ function findFileForPlatform(files, platform) {
|
||||
)
|
||||
})
|
||||
|
||||
// Define file patterns for each platform (ordered by priority)
|
||||
const patterns = {
|
||||
windows: [
|
||||
/ceru-music.*setup\\.exe$/i,
|
||||
/\\.exe$/i,
|
||||
/windows.*\\.zip$/i,
|
||||
/win32.*\\.zip$/i,
|
||||
/win.*x64.*\\.zip$/i
|
||||
],
|
||||
macos: [
|
||||
/ceru-music.*\\.dmg$/i,
|
||||
/\\.dmg$/i,
|
||||
/darwin.*\\.zip$/i,
|
||||
/macos.*\\.zip$/i,
|
||||
/mac.*\\.zip$/i,
|
||||
/osx.*\\.zip$/i
|
||||
],
|
||||
linux: [
|
||||
/ceru-music.*amd64\\.deb$/i,
|
||||
/\\.deb$/i,
|
||||
/\\.AppImage$/i,
|
||||
/linux.*\\.zip$/i,
|
||||
/linux.*\\.tar\\.gz$/i,
|
||||
/\\.rpm$/i
|
||||
]
|
||||
// If no user architecture provided, detect it
|
||||
if (!userArch) {
|
||||
userArch = detectArchitecture()
|
||||
}
|
||||
|
||||
const platformPatterns = patterns[platform] || []
|
||||
// Define architecture-specific patterns for each platform
|
||||
const archPatterns = {
|
||||
windows: {
|
||||
x64: [
|
||||
/ceru-music.*x64.*setup\.exe$/i,
|
||||
/ceru-music.*win.*x64.*setup\.exe$/i,
|
||||
/ceru-music.*x64.*\.zip$/i,
|
||||
/ceru-music.*win.*x64.*\.zip$/i
|
||||
],
|
||||
ia32: [
|
||||
/ceru-music.*ia32.*setup\.exe$/i,
|
||||
/ceru-music.*win.*ia32.*setup\.exe$/i,
|
||||
/ceru-music.*ia32.*\.zip$/i,
|
||||
/ceru-music.*win.*ia32.*\.zip$/i
|
||||
],
|
||||
fallback: [/ceru-music.*setup\.exe$/i, /\.exe$/i, /windows.*\.zip$/i, /win.*\.zip$/i]
|
||||
},
|
||||
macos: {
|
||||
universal: [/ceru-music.*universal\\.dmg$/i, /ceru-music.*universal\\.zip$/i],
|
||||
arm64: [
|
||||
/ceru-music.*arm64\\.dmg$/i,
|
||||
/ceru-music.*arm64\\.zip$/i,
|
||||
/ceru-music.*universal\\.dmg$/i,
|
||||
/ceru-music.*universal\\.zip$/i
|
||||
],
|
||||
x64: [
|
||||
/ceru-music.*x64\\.dmg$/i,
|
||||
/ceru-music.*x64\\.zip$/i,
|
||||
/ceru-music.*universal\\.dmg$/i,
|
||||
/ceru-music.*universal\\.zip$/i
|
||||
],
|
||||
fallback: [
|
||||
/ceru-music.*\\.dmg$/i,
|
||||
/\\.dmg$/i,
|
||||
/darwin.*\\.zip$/i,
|
||||
/macos.*\\.zip$/i,
|
||||
/mac.*\\.zip$/i
|
||||
]
|
||||
},
|
||||
linux: {
|
||||
x64: [
|
||||
/ceru-music.*linux.*x64\\.AppImage$/i,
|
||||
/ceru-music.*linux.*x64\\.deb$/i,
|
||||
/ceru-music.*x64\\.AppImage$/i,
|
||||
/ceru-music.*x64\\.deb$/i
|
||||
],
|
||||
fallback: [
|
||||
/ceru-music.*\\.AppImage$/i,
|
||||
/ceru-music.*\\.deb$/i,
|
||||
/\\.AppImage$/i,
|
||||
/\\.deb$/i,
|
||||
/linux.*\\.zip$/i
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find exact match
|
||||
for (const pattern of platformPatterns) {
|
||||
const platformArchPatterns = archPatterns[platform]
|
||||
if (!platformArchPatterns) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Try architecture-specific patterns first
|
||||
const archSpecificPatterns = platformArchPatterns[userArch] || []
|
||||
|
||||
for (const pattern of archSpecificPatterns) {
|
||||
const file = filteredFiles.find((file) => pattern.test(file.name))
|
||||
if (file) {
|
||||
return file.name
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: look for any file that might match the platform
|
||||
const fallbackPatterns = {
|
||||
// Try fallback patterns
|
||||
const fallbackPatterns = platformArchPatterns.fallback || []
|
||||
|
||||
for (const pattern of fallbackPatterns) {
|
||||
const file = filteredFiles.find((file) => pattern.test(file.name))
|
||||
if (file) {
|
||||
return file.name
|
||||
}
|
||||
}
|
||||
|
||||
// Final fallback: look for any file that might match the platform
|
||||
const finalFallbackPatterns = {
|
||||
windows: /win|exe/i,
|
||||
macos: /mac|darwin|dmg/i,
|
||||
linux: /linux|appimage|deb|rpm/i
|
||||
}
|
||||
|
||||
const fallbackPattern = fallbackPatterns[platform]
|
||||
if (fallbackPattern) {
|
||||
const file = filteredFiles.find((file) => fallbackPattern.test(file.name))
|
||||
const finalPattern = finalFallbackPatterns[platform]
|
||||
if (finalPattern) {
|
||||
const file = filteredFiles.find((file) => finalPattern.test(file.name))
|
||||
if (file) {
|
||||
return file.name
|
||||
}
|
||||
@@ -391,21 +450,33 @@ function findDownloadAsset(assets, platform) {
|
||||
// Define file patterns for each platform (ordered by priority)
|
||||
const patterns = {
|
||||
windows: [
|
||||
/ceru-music.*win.*x64.*setup\.exe$/i,
|
||||
/ceru-music.*win.*ia32.*setup\.exe$/i,
|
||||
/ceru-music.*setup\.exe$/i,
|
||||
/\.exe$/i,
|
||||
/ceru-music.*win.*x64.*\.zip$/i,
|
||||
/ceru-music.*win.*ia32.*\.zip$/i,
|
||||
/windows.*\.zip$/i,
|
||||
/win32.*\.zip$/i,
|
||||
/win.*x64.*\.zip$/i
|
||||
],
|
||||
macos: [
|
||||
/ceru-music.*universal\.dmg$/i,
|
||||
/ceru-music.*arm64\.dmg$/i,
|
||||
/ceru-music.*x64\.dmg$/i,
|
||||
/ceru-music.*\.dmg$/i,
|
||||
/\.dmg$/i,
|
||||
/ceru-music.*universal\.zip$/i,
|
||||
/ceru-music.*arm64\.zip$/i,
|
||||
/ceru-music.*x64\.zip$/i,
|
||||
/darwin.*\.zip$/i,
|
||||
/macos.*\.zip$/i,
|
||||
/mac.*\.zip$/i,
|
||||
/osx.*\.zip$/i
|
||||
],
|
||||
linux: [
|
||||
/ceru-music.*linux.*x64\.deb$/i,
|
||||
/ceru-music.*linux.*x64\.AppImage$/i,
|
||||
/ceru-music.*amd64\.deb$/i,
|
||||
/\.deb$/i,
|
||||
/\.AppImage$/i,
|
||||
@@ -604,7 +675,7 @@ function setupAnimations() {
|
||||
})
|
||||
}
|
||||
|
||||
// Auto-detect user's operating system
|
||||
// Auto-detect user's operating system and architecture
|
||||
function detectOS() {
|
||||
const userAgent = navigator.userAgent.toLowerCase()
|
||||
if (userAgent.includes('win')) return 'windows'
|
||||
@@ -613,9 +684,52 @@ function detectOS() {
|
||||
return 'windows' // default
|
||||
}
|
||||
|
||||
// Detect user's architecture
|
||||
function detectArchitecture() {
|
||||
const userAgent = navigator.userAgent.toLowerCase()
|
||||
const platform = navigator.platform.toLowerCase()
|
||||
|
||||
// For macOS, detect Apple Silicon vs Intel
|
||||
if (userAgent.includes('mac')) {
|
||||
// Check for Apple Silicon indicators
|
||||
if (userAgent.includes('arm') || platform.includes('arm')) {
|
||||
return 'arm64'
|
||||
}
|
||||
// Default to universal for macOS (works on both Intel and Apple Silicon)
|
||||
return 'universal'
|
||||
}
|
||||
|
||||
// For Windows, detect 32-bit vs 64-bit
|
||||
if (userAgent.includes('win')) {
|
||||
if (userAgent.includes('wow64') || userAgent.includes('win64') || userAgent.includes('x64')) {
|
||||
return 'x64'
|
||||
}
|
||||
return 'ia32'
|
||||
}
|
||||
|
||||
// For Linux, assume 64-bit
|
||||
if (userAgent.includes('linux')) {
|
||||
return 'x64'
|
||||
}
|
||||
|
||||
return 'x64' // default
|
||||
}
|
||||
|
||||
// Get architecture display name
|
||||
function getArchitectureName(arch) {
|
||||
const names = {
|
||||
x64: '64位',
|
||||
ia32: '32位',
|
||||
arm64: 'Apple Silicon',
|
||||
universal: 'Universal (Intel + Apple Silicon)'
|
||||
}
|
||||
return names[arch] || arch
|
||||
}
|
||||
|
||||
// Highlight user's OS download option
|
||||
function highlightUserOS() {
|
||||
const userOS = detectOS()
|
||||
const userArch = detectArchitecture()
|
||||
const downloadCards = document.querySelectorAll('.download-card')
|
||||
|
||||
downloadCards.forEach((card, index) => {
|
||||
@@ -624,10 +738,11 @@ function highlightUserOS() {
|
||||
card.style.border = '2px solid var(--primary-color)'
|
||||
card.style.transform = 'scale(1.02)'
|
||||
|
||||
// Add "推荐" badge
|
||||
// Add "推荐" badge with architecture info
|
||||
const badge = document.createElement('div')
|
||||
badge.className = 'recommended-badge'
|
||||
badge.textContent = '推荐'
|
||||
const archName = getArchitectureName(userArch)
|
||||
badge.textContent = `推荐 (${archName})`
|
||||
badge.style.cssText = `
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
@@ -638,9 +753,22 @@ function highlightUserOS() {
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
`
|
||||
card.style.position = 'relative'
|
||||
card.appendChild(badge)
|
||||
|
||||
// Add architecture info to the card description
|
||||
const description = card.querySelector('p')
|
||||
if (description && userOS === 'macos') {
|
||||
if (userArch === 'arm64') {
|
||||
description.innerHTML +=
|
||||
'<br><small style="color: var(--text-muted);">检测到 Apple Silicon Mac,推荐 Universal 版本</small>'
|
||||
} else if (userArch === 'universal') {
|
||||
description.innerHTML +=
|
||||
'<br><small style="color: var(--text-muted);">Universal 版本兼容 Intel 和 Apple Silicon Mac</small>'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -757,9 +885,14 @@ async function updateVersionInfo() {
|
||||
const modifyDate = new Date(latestVersion.modified)
|
||||
const formattedDate = modifyDate.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long'
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
versionInfoElement.innerHTML = `当前版本: <span class="version">${latestVersion.name}</span> | 更新时间: ${formattedDate}`
|
||||
const formattedTime = modifyDate.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
versionInfoElement.innerHTML = `当前版本: <span class="version">${latestVersion.name}</span> | 更新时间: ${formattedDate} ${formattedTime}`
|
||||
}
|
||||
|
||||
// Update download button text with file info from Alist
|
||||
@@ -770,7 +903,6 @@ async function updateVersionInfo() {
|
||||
}
|
||||
|
||||
// Fallback to GitHub if Alist fails
|
||||
console.log('Alist version info failed, trying GitHub fallback...')
|
||||
const release = await getLatestRelease()
|
||||
if (release) {
|
||||
const versionElement = document.querySelector('.version')
|
||||
@@ -784,9 +916,14 @@ async function updateVersionInfo() {
|
||||
const publishDate = new Date(release.published_at)
|
||||
const formattedDate = publishDate.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long'
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
versionInfoElement.innerHTML = `当前版本: <span class="version">${release.tag_name}</span> | 更新时间: ${formattedDate}`
|
||||
const formattedTime = publishDate.toLocaleTimeString('zh-CN', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
versionInfoElement.innerHTML = `当前版本: <span class="version">${release.tag_name}</span> | 更新时间: ${formattedDate} ${formattedTime}`
|
||||
}
|
||||
|
||||
// Update download button text with file sizes if available
|
||||
@@ -848,18 +985,98 @@ function updateDownloadButtonsWithAssets(assets) {
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to find asset for platform
|
||||
function findAssetForPlatform(assets, platform) {
|
||||
const patterns = {
|
||||
windows: [/\.exe$/i, /windows.*\.zip$/i, /win32.*\.zip$/i],
|
||||
macos: [/\.dmg$/i, /darwin.*\.zip$/i, /macos.*\.zip$/i],
|
||||
linux: [/\.AppImage$/i, /linux.*\.zip$/i, /\.deb$/i]
|
||||
// Updated function name to match usage
|
||||
function findDownloadAsset(assets, platform, userArch = null) {
|
||||
if (!userArch) {
|
||||
userArch = detectArchitecture()
|
||||
}
|
||||
|
||||
const platformPatterns = patterns[platform] || []
|
||||
// Filter out unwanted files
|
||||
const filteredAssets = assets.filter((asset) => {
|
||||
const name = asset.name.toLowerCase()
|
||||
return (
|
||||
!name.endsWith('.yml') &&
|
||||
!name.endsWith('.yaml') &&
|
||||
!name.endsWith('.txt') &&
|
||||
!name.endsWith('.md') &&
|
||||
!name.endsWith('.json') &&
|
||||
!name.includes('latest') &&
|
||||
!name.includes('blockmap')
|
||||
)
|
||||
})
|
||||
|
||||
for (const pattern of platformPatterns) {
|
||||
const asset = assets.find((asset) => pattern.test(asset.name))
|
||||
// Define architecture-specific patterns for each platform
|
||||
const archPatterns = {
|
||||
windows: {
|
||||
x64: [
|
||||
/ceru-music.*x64.*setup\.exe$/i,
|
||||
/ceru-music.*win.*x64.*setup\.exe$/i,
|
||||
/ceru-music.*x64.*\.zip$/i,
|
||||
/ceru-music.*win.*x64.*\.zip$/i
|
||||
],
|
||||
ia32: [
|
||||
/ceru-music.*ia32.*setup\.exe$/i,
|
||||
/ceru-music.*win.*ia32.*setup\.exe$/i,
|
||||
/ceru-music.*ia32.*\.zip$/i,
|
||||
/ceru-music.*win.*ia32.*\.zip$/i
|
||||
],
|
||||
fallback: [/ceru-music.*setup\.exe$/i, /\.exe$/i, /windows.*\.zip$/i, /win.*\.zip$/i]
|
||||
},
|
||||
macos: {
|
||||
universal: [/ceru-music.*universal\.dmg$/i, /ceru-music.*universal\.zip$/i],
|
||||
arm64: [
|
||||
/ceru-music.*arm64\.dmg$/i,
|
||||
/ceru-music.*arm64\.zip$/i,
|
||||
/ceru-music.*universal\.dmg$/i,
|
||||
/ceru-music.*universal\.zip$/i
|
||||
],
|
||||
x64: [
|
||||
/ceru-music.*x64\.dmg$/i,
|
||||
/ceru-music.*x64\.zip$/i,
|
||||
/ceru-music.*universal\.dmg$/i,
|
||||
/ceru-music.*universal\.zip$/i
|
||||
],
|
||||
fallback: [
|
||||
/ceru-music.*\.dmg$/i,
|
||||
/\.dmg$/i,
|
||||
/darwin.*\.zip$/i,
|
||||
/macos.*\.zip$/i,
|
||||
/mac.*\.zip$/i
|
||||
]
|
||||
},
|
||||
linux: {
|
||||
x64: [
|
||||
/ceru-music.*linux.*x64\.AppImage$/i,
|
||||
/ceru-music.*linux.*x64\.deb$/i,
|
||||
/ceru-music.*x64\.AppImage$/i,
|
||||
/ceru-music.*x64\.deb$/i
|
||||
],
|
||||
fallback: [
|
||||
/ceru-music.*\.AppImage$/i,
|
||||
/ceru-music.*\.deb$/i,
|
||||
/\.AppImage$/i,
|
||||
/\.deb$/i,
|
||||
/linux.*\.zip$/i
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const platformArchPatterns = archPatterns[platform]
|
||||
if (!platformArchPatterns) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Try architecture-specific patterns first
|
||||
const archSpecificPatterns = platformArchPatterns[userArch] || []
|
||||
for (const pattern of archSpecificPatterns) {
|
||||
const asset = filteredAssets.find((asset) => pattern.test(asset.name))
|
||||
if (asset) return asset
|
||||
}
|
||||
|
||||
// Try fallback patterns
|
||||
const fallbackPatterns = platformArchPatterns.fallback || []
|
||||
for (const pattern of fallbackPatterns) {
|
||||
const asset = filteredAssets.find((asset) => pattern.test(asset.name))
|
||||
if (asset) return asset
|
||||
}
|
||||
|
||||
@@ -882,15 +1099,32 @@ function formatFileSize(bytes) {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
// Get architecture information from filename
|
||||
function getArchitectureInfo(filename) {
|
||||
if (!filename) return ''
|
||||
|
||||
const name = filename.toLowerCase()
|
||||
|
||||
if (name.includes('universal')) return '(Universal)'
|
||||
if (name.includes('arm64')) return '(Apple Silicon)'
|
||||
if (name.includes('x64')) return '(64位)'
|
||||
if (name.includes('ia32')) return '(32位)'
|
||||
if (name.includes('win') && name.includes('x64')) return '(64位)'
|
||||
if (name.includes('win') && name.includes('ia32')) return '(32位)'
|
||||
if (name.includes('linux') && name.includes('x64')) return '(64位)'
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
// Analytics tracking (placeholder)
|
||||
function trackDownload(platform, version) {
|
||||
function trackDownload(platform, version, filename = '') {
|
||||
// Add your analytics tracking code here
|
||||
console.log(`Download tracked: ${platform} v${version}`)
|
||||
const archInfo = getArchitectureInfo(filename)
|
||||
|
||||
// Example: Google Analytics
|
||||
// gtag('event', 'download', {
|
||||
// 'event_category': 'software',
|
||||
// 'event_label': platform,
|
||||
// 'event_label': `${platform}_${archInfo}`,
|
||||
// 'value': version
|
||||
// });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user