Compare commits

..

No commits in common. "6faaa7984bbac901f7214132b899c4554a54c31b" and "bacbe1871c9b5df04a3fa1ad9db6068cd2a25501" have entirely different histories.

144 changed files with 3690 additions and 10550 deletions

View File

@ -0,0 +1,112 @@
# 知识卡导出索引文件
schema_version: 1
locale: zh-CN
branch: Q0DeR-MaG1C-BrAnCh-Fo2-NoN-GiT-Or-No-C0MmIt
exported_at: "2026-06-12T08:09:05Z"
modules:
"":
dir_name: 网盘管理跨端应用
title: 网盘管理跨端应用
scope:
- vue\.gitignore
- vue\.vscode\extensions.json
- vue\APK_BUILD_OPTIONS.md
- vue\APP_GUIDE.md
- vue\PACKAGING_SUMMARY.md
- vue\README.md
- vue\android\.gitignore
- vue\android\app\.gitignore
- vue\android\app\build.gradle
- vue\android\app\capacitor.build.gradle
- vue\android\app\proguard-rules.pro
- vue\android\app\src\androidTest\java\com\getcapacitor\myapp\ExampleInstrumentedTest.java
- vue\android\app\src\main\AndroidManifest.xml
- vue\android\app\src\main\assets\capacitor.config.json
- vue\android\app\src\main\assets\capacitor.plugins.json
- vue\android\app\src\main\assets\public\assets\index-DrjxTYTT.css
- vue\android\app\src\main\assets\public\assets\index-xOoYtVTp.js
- vue\android\app\src\main\assets\public\cordova.js
- vue\android\app\src\main\assets\public\cordova_plugins.js
- vue\android\app\src\main\assets\public\index.html
- vue\android\app\src\main\assets\public\logo.png
- vue\android\app\src\main\assets\public\vite.svg
- vue\android\app\src\main\java\com\wangpan\app\MainActivity.java
- vue\android\app\src\main\res\drawable-land-hdpi\splash.png
- vue\android\app\src\main\res\drawable-land-mdpi\splash.png
- vue\android\app\src\main\res\drawable-land-xhdpi\splash.png
- vue\android\app\src\main\res\drawable-land-xxhdpi\splash.png
- vue\android\app\src\main\res\drawable-land-xxxhdpi\splash.png
- vue\android\app\src\main\res\drawable-port-hdpi\splash.png
- vue\android\app\src\main\res\drawable-port-mdpi\splash.png
- vue\android\app\src\main\res\drawable-port-xhdpi\splash.png
- vue\android\app\src\main\res\drawable-port-xxhdpi\splash.png
- vue\android\app\src\main\res\drawable-port-xxxhdpi\splash.png
- vue\android\app\src\main\res\drawable-v24\ic_launcher_foreground.xml
- vue\android\app\src\main\res\drawable\ic_launcher_background.xml
- vue\android\app\src\main\res\drawable\splash.png
- vue\android\app\src\main\res\layout\activity_main.xml
- vue\android\app\src\main\res\mipmap-anydpi-v26\ic_launcher.xml
- vue\android\app\src\main\res\mipmap-anydpi-v26\ic_launcher_round.xml
- vue\android\app\src\main\res\mipmap-hdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-hdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-hdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-mdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-mdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-mdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-xhdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-xhdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-xhdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-xxhdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-xxhdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-xxhdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-xxxhdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-xxxhdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-xxxhdpi\ic_launcher_round.png
- vue\android\app\src\main\res\values\ic_launcher_background.xml
- vue\android\app\src\main\res\values\strings.xml
- vue\android\app\src\main\res\values\styles.xml
- vue\android\app\src\main\res\xml\config.xml
- vue\android\app\src\main\res\xml\file_paths.xml
- vue\android\app\src\test\java\com\getcapacitor\myapp\ExampleUnitTest.java
- vue\android\build.gradle
- vue\android\capacitor-cordova-android-plugins\build.gradle
- vue\android\capacitor-cordova-android-plugins\cordova.variables.gradle
- vue\android\capacitor-cordova-android-plugins\src\main\AndroidManifest.xml
- vue\android\capacitor-cordova-android-plugins\src\main\java\.gitkeep
- vue\android\capacitor-cordova-android-plugins\src\main\res\.gitkeep
- vue\android\capacitor.settings.gradle
- vue\android\gradle.properties
- vue\android\gradle\wrapper\gradle-wrapper.jar
- vue\android\gradle\wrapper\gradle-wrapper.properties
- vue\android\gradlew
- vue\android\gradlew.bat
- vue\android\settings.gradle
- vue\android\variables.gradle
- vue\build-apk.bat
- vue\capacitor.config.json
- vue\index.html
- vue\package-lock.json
- vue\package.json
- vue\public\logo.png
- vue\public\vite.svg
- vue\src\App.vue
- vue\src\api\index.js
- vue\src\assets\vue.svg
- vue\src\components\DriveManageDialog.vue
- vue\src\components\FolderTree.vue
- vue\src\components\LoginView.vue
- vue\src\components\PosterCard.vue
- vue\src\components\RecordDialog.vue
- vue\src\components\SimpleDialog.vue
- vue\src\components\TreeNode.vue
- vue\src\components\UserManageDialog.vue
- vue\src\main.js
- vue\src\style.css
- vue\src\utils\captcha.js
- vue\src\utils\data.js
- vue\src\utils\storage.js
- vue\vite.config.js
source_files: []
children: []
depends_on: []
related_to: []

View File

@ -0,0 +1,105 @@
schema_version: 1
module_path: ""
title: 网盘管理跨端应用
scope:
- vue\.gitignore
- vue\.vscode\extensions.json
- vue\APK_BUILD_OPTIONS.md
- vue\APP_GUIDE.md
- vue\PACKAGING_SUMMARY.md
- vue\README.md
- vue\android\.gitignore
- vue\android\app\.gitignore
- vue\android\app\build.gradle
- vue\android\app\capacitor.build.gradle
- vue\android\app\proguard-rules.pro
- vue\android\app\src\androidTest\java\com\getcapacitor\myapp\ExampleInstrumentedTest.java
- vue\android\app\src\main\AndroidManifest.xml
- vue\android\app\src\main\assets\capacitor.config.json
- vue\android\app\src\main\assets\capacitor.plugins.json
- vue\android\app\src\main\assets\public\assets\index-DrjxTYTT.css
- vue\android\app\src\main\assets\public\assets\index-xOoYtVTp.js
- vue\android\app\src\main\assets\public\cordova.js
- vue\android\app\src\main\assets\public\cordova_plugins.js
- vue\android\app\src\main\assets\public\index.html
- vue\android\app\src\main\assets\public\logo.png
- vue\android\app\src\main\assets\public\vite.svg
- vue\android\app\src\main\java\com\wangpan\app\MainActivity.java
- vue\android\app\src\main\res\drawable-land-hdpi\splash.png
- vue\android\app\src\main\res\drawable-land-mdpi\splash.png
- vue\android\app\src\main\res\drawable-land-xhdpi\splash.png
- vue\android\app\src\main\res\drawable-land-xxhdpi\splash.png
- vue\android\app\src\main\res\drawable-land-xxxhdpi\splash.png
- vue\android\app\src\main\res\drawable-port-hdpi\splash.png
- vue\android\app\src\main\res\drawable-port-mdpi\splash.png
- vue\android\app\src\main\res\drawable-port-xhdpi\splash.png
- vue\android\app\src\main\res\drawable-port-xxhdpi\splash.png
- vue\android\app\src\main\res\drawable-port-xxxhdpi\splash.png
- vue\android\app\src\main\res\drawable-v24\ic_launcher_foreground.xml
- vue\android\app\src\main\res\drawable\ic_launcher_background.xml
- vue\android\app\src\main\res\drawable\splash.png
- vue\android\app\src\main\res\layout\activity_main.xml
- vue\android\app\src\main\res\mipmap-anydpi-v26\ic_launcher.xml
- vue\android\app\src\main\res\mipmap-anydpi-v26\ic_launcher_round.xml
- vue\android\app\src\main\res\mipmap-hdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-hdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-hdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-mdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-mdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-mdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-xhdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-xhdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-xhdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-xxhdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-xxhdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-xxhdpi\ic_launcher_round.png
- vue\android\app\src\main\res\mipmap-xxxhdpi\ic_launcher.png
- vue\android\app\src\main\res\mipmap-xxxhdpi\ic_launcher_foreground.png
- vue\android\app\src\main\res\mipmap-xxxhdpi\ic_launcher_round.png
- vue\android\app\src\main\res\values\ic_launcher_background.xml
- vue\android\app\src\main\res\values\strings.xml
- vue\android\app\src\main\res\values\styles.xml
- vue\android\app\src\main\res\xml\config.xml
- vue\android\app\src\main\res\xml\file_paths.xml
- vue\android\app\src\test\java\com\getcapacitor\myapp\ExampleUnitTest.java
- vue\android\build.gradle
- vue\android\capacitor-cordova-android-plugins\build.gradle
- vue\android\capacitor-cordova-android-plugins\cordova.variables.gradle
- vue\android\capacitor-cordova-android-plugins\src\main\AndroidManifest.xml
- vue\android\capacitor-cordova-android-plugins\src\main\java\.gitkeep
- vue\android\capacitor-cordova-android-plugins\src\main\res\.gitkeep
- vue\android\capacitor.settings.gradle
- vue\android\gradle.properties
- vue\android\gradle\wrapper\gradle-wrapper.jar
- vue\android\gradle\wrapper\gradle-wrapper.properties
- vue\android\gradlew
- vue\android\gradlew.bat
- vue\android\settings.gradle
- vue\android\variables.gradle
- vue\build-apk.bat
- vue\capacitor.config.json
- vue\index.html
- vue\package-lock.json
- vue\package.json
- vue\public\logo.png
- vue\public\vite.svg
- vue\src\App.vue
- vue\src\api\index.js
- vue\src\assets\vue.svg
- vue\src\components\DriveManageDialog.vue
- vue\src\components\FolderTree.vue
- vue\src\components\LoginView.vue
- vue\src\components\PosterCard.vue
- vue\src\components\RecordDialog.vue
- vue\src\components\SimpleDialog.vue
- vue\src\components\TreeNode.vue
- vue\src\components\UserManageDialog.vue
- vue\src\main.js
- vue\src\style.css
- vue\src\utils\captcha.js
- vue\src\utils\data.js
- vue\src\utils\storage.js
- vue\vite.config.js
source_files: []
depends_on: []
related_to: []

View File

@ -0,0 +1 @@
Vue 3.5, Vite 7, Capacitor 8, Android Gradle Plugin

View File

@ -0,0 +1,4 @@
- **前端架构**:采用 Vite + Vue 3 (Composition API) 技术栈,通过 `src/api/index.js` 封装 RESTful 请求并处理 Token 鉴权。
- **跨端桥接**:利用 Capacitor 8 将 Web 应用(`dist` 目录)嵌入原生 Android Shell (`BridgeActivity`),实现从 Web 到 Native 的无缝转换。
- **配置管理**:通过 `capacitor.config.json` 定义应用 ID、启动屏及状态栏样式并通过 `vite.config.js` 代理后端 API 请求。
- **构建流程**遵循“Vite 构建 Web -> Cap sync 同步资源 -> Gradle 编译 APK”的标准化流水线`build-apk.bat` 脚本自动化执行。

View File

@ -0,0 +1 @@
基于 Vue 3 与 Capacitor 构建的网盘资源管理与海报展示跨端应用,支持 Android APK 打包。

View File

@ -0,0 +1 @@
- **APK 打包**:运行根目录下的 `build-apk.bat` 脚本,自动完成 Web 资源构建、Capacitor 同步并唤起 Android Studio 进行最终编译。

View File

@ -0,0 +1,2 @@
- API 模块统一在 `src/api/index.js` 中导出,使用自定义 `request` 包装器处理全局认证头与 401/403 状态分发。
- 组件采用 `<script setup>` 语法糖编写,逻辑复用通过 `src/utils` 下的纯函数(如 `data.js`, `storage.js`)实现。

Binary file not shown.

View File

@ -1,20 +0,0 @@
const fs = require('fs');
const path = require('path');
const iconsDir = path.join(__dirname, 'public', 'icons');
if (!fs.existsSync(iconsDir)) fs.mkdirSync(iconsDir, { recursive: true });
const icons = {
home: { path: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z M9 22v-10l6 6v-10', label: 'Home' },
search: { d: 'circle cx=11 cy=11 r=8 line x1=21 y1=21 x2=16.65 y2=16.65', label: 'Search' },
user: { d: 'path d=M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2 circle cx=12 cy=7 r=4', label: 'User' },
folder: { d: 'path d=M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z', fill: true, label: 'Folder' },
file: { d: 'path d=M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z polyline points=14 2 14 8 20 8', label: 'File' },
settings: { d: 'circle cx=12 cy=12 r=3 path d=M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0', label: 'Settings' },
close: { d: 'line x1=18 y1=6 x2=6 y2=18 line x1=6 y1=6 x2=18 y2=18', label: 'Close' },
check: { d: 'polyline points=20 6 9 17 4 12', color: '#22c55e', label: 'Check' },
delete: { d: 'polyline points=3 6 5 6 21 6 path d=M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2', color: '#ef4444', label: 'Delete' },
add: { d: 'circle cx=12 cy=12 r=10 line x1=12 y1=8 x2=12 y2=16 line x1=8 y1=12 x2=16 y2=12', label: 'Add' },
download: { d: 'path d=M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4 polyline points=7 10 12 15 17 10 line x1=12 y1=15 x2=12 y2=3', label: 'Download' },
upload: { d: 'path d=M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4 polyline points=17 8 12 3 7 8 line x1=12 y1=3 x2=12 y2=15', label: 'Upload' },
share: { d: 'circle cx=18 cy=5 r=3 circle cx=6 cy=12 r=3 circle cx=18 cy=19 r=3 line x1=8.59 y1=13.51 x2=15.42 y2=17.49 line x1=15.41 y1=6.51 x2=8.59 y2=10.49', label: 'Share' },
favorite: { d: 'path d=M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z', fill: true, label: 'Favorite' },
cloud: { d: 'path d=M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z', label: 'Cloud' },

View File

@ -1,175 +0,0 @@
# Icon Generator Script
import os
icons_dir = 'public/icons'
os.makedirs(icons_dir, exist_ok=True)
# Color scheme
PRIMARY_COLOR = '#6366f1'
SUCCESS_COLOR = '#22c55e'
DANGER_COLOR = '#ef4444'
def create_svg(filename, elements, color=PRIMARY_COLOR, fill=False):
"""Create an SVG icon file"""
fill_attr = 'fill "{}"'.format(color) if fill else 'fill="none"'
svg_content = '''<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {} stroke="{}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
{}
</svg>'''.format(fill_attr, color, '\n'.join(elements))
filepath = os.path.join(icons_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(svg_content)
print(f'Created: {filename}')
# Home icon
create_svg('home.svg', [
'<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
'<polyline points="9 22 9 12 15 12 15 22"/>'
])
# Search icon
create_svg('search.svg', [
'<circle cx="11" cy="11" r="8"/>',
'<line x1="21" y1="21" x2="16.65" y2="16.65"/>'
])
# Settings icon
create_svg('settings.svg', [
'<circle cx="12" cy="12" r="3"/>',
'<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>'
])
# User icon
create_svg('user.svg', [
'<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>',
'<circle cx="12" cy="7" r="4"/>'
])
# Folder icon (filled)
create_svg('folder.svg', [
'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>'
], fill=True)
# File icon
create_svg('file.svg', [
'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>',
'<polyline points="14 2 14 8 20 8"/>'
])
# Download icon
create_svg('download.svg', [
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>',
'<polyline points="7 10 12 15 17 10"/>',
'<line x1="12" y1="15" x2="12" y2="3"/>'
])
# Upload icon
create_svg('upload.svg', [
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>',
'<polyline points="17 8 12 3 7 8"/>',
'<line x1="12" y1="3" x2="12" y2="15"/>'
])
# Share icon
create_svg('share.svg', [
'<circle cx="18" cy="5" r="3"/>',
'<circle cx="6" cy="12" r="3"/>',
'<circle cx="18" cy="19" r="3"/>',
'<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>',
'<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>'
])
# Favorite/Like icon (filled)
create_svg('favorite.svg', [
'<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>'
], fill=True)
# Delete icon
create_svg('delete.svg', [
'<polyline points="3 6 5 6 21 6"/>',
'<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>'
], color=DANGER_COLOR)
# Edit icon
create_svg('edit.svg', [
'<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>',
'<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>'
])
# Add icon
create_svg('add.svg', [
'<circle cx="12" cy="12" r="10"/>',
'<line x1="12" y1="8" x2="12" y2="16"/>',
'<line x1="8" y1="12" x2="16" y2="12"/>'
])
# Back/Arrow Left icon
create_svg('back.svg', [
'<line x1="19" y1="12" x2="5" y2="12"/>',
'<polyline points="12 19 5 12 12 5"/>'
])
# More/Options icon (three dots)
create_svg('more.svg', [
'<circle cx="12" cy="12" r="1"/>',
'<circle cx="19" cy="12" r="1"/>',
'<circle cx="5" cy="12" r="1"/>'
], fill=True)
# Cloud icon
create_svg('cloud.svg', [
'<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/>'
])
# Drive/Server icon
create_svg('drive.svg', [
'<rect x="2" y="6" width="20" height="12" rx="2"/>',
'<circle cx="12" cy="12" r="2"/>',
'<path d="M6 12h.01 M18 12h.01"/>'
])
# Log/File Text icon
create_svg('log.svg', [
'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>',
'<polyline points="14 2 14 8 20 8"/>',
'<line x1="16" y1="13" x2="8" y2="13"/>',
'<line x1="16" y1="17" x2="8" y2="17"/>',
'<polyline points="10 9 9 9 8 9"/>'
])
# Close icon
create_svg('close.svg', [
'<line x1="18" y1="6" x2="6" y2="18"/>',
'<line x1="6" y1="6" x2="18" y2="18"/>'
])
# Check/Verify icon
create_svg('check.svg', [
'<polyline points="20 6 9 17 4 12"/>'
], color=SUCCESS_COLOR)
# Play icon
create_svg('play.svg', [
'<polygon points="5 3 19 12 5 21 5 3"/>'
], fill=True)
# Pause icon
create_svg('pause.svg', [
'<rect x="6" y="4" width="4" height="16"/>',
'<rect x="14" y="4" width="4" height="16"/>'
])
# Eye icon
create_svg('eye.svg', [
'<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>',
'<circle cx="12" cy="12" r="3"/>'
])
# Eye Off icon
create_svg('eye-off.svg', [
'<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>',
'<line x1="1" y1="1" x2="23" y2="23"/>'
])
print(f'\nTotal icons created: {len(os.listdir(icons_dir)) - 1}') # Exclude test.txt
print('Icon generation complete!')

View File

@ -1,8 +0,0 @@
<!-- 添加文件图标:文档+加号 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<!-- 加号(青绿色) -->
<line x1="12" y1="13" x2="12" y2="19" stroke="#2dd4bf" stroke-width="2"/>
<line x1="9" y1="16" x2="15" y2="16" stroke="#2dd4bf" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 525 B

View File

@ -1,7 +0,0 @@
<!-- 添加文件夹图标:文件夹+加号 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<!-- 加号(青绿色) -->
<line x1="12" y1="11" x2="12" y2="17" stroke="#2dd4bf" stroke-width="2"/>
<line x1="9" y1="14" x2="15" y2="14" stroke="#2dd4bf" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 510 B

View File

@ -1,10 +0,0 @@
<!-- 批量删除图标:垃圾桶+列表 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<!-- 列表线(青绿色) -->
<line x1="10" y1="11" x2="10" y2="17" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="14" y1="11" x2="14" y2="17" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="9" y1="9" x2="15" y2="9" stroke="#2dd4bf" stroke-width="1"/>
</svg>

Before

Width:  |  Height:  |  Size: 628 B

View File

@ -1,13 +0,0 @@
<!-- 批量刮削图标:电影+列表 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="6" width="18" height="14" rx="1"/>
<path d="M3 10h18"/>
<path d="M7 6V3"/>
<path d="M12 6V3"/>
<path d="M17 6V3"/>
<!-- 场记板条纹(青绿色) -->
<line x1="5" y1="8" x2="7" y2="8" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="9" y1="8" x2="11" y2="8" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="13" y1="8" x2="15" y2="8" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="17" y1="8" x2="19" y2="8" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 713 B

View File

@ -1,8 +0,0 @@
<!-- 主演图标:面具/戏剧 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12c0-5 4-9 10-9s10 4 10 9"/>
<path d="M2 12c0 3 2 5 4 5s3-2 4-5"/>
<path d="M14 12c1 3 2 5 4 5s4-2 4-5"/>
<!-- 面具高亮(青绿色) -->
<path d="M2 12c0 3 2 5 4 5s3-2 4-5" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.3" opacity="0.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 492 B

View File

@ -1,5 +0,0 @@
<!-- 多选图标:复选框+对勾 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="3"/>
<path d="M7 12l3.5 3.5L17 9" stroke="#2dd4bf" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 346 B

View File

@ -1,7 +0,0 @@
<!-- 关闭图标X -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
<!-- 关闭线高亮(青绿色) -->
<line x1="18" y1="6" x2="6" y2="18" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 410 B

View File

@ -1,10 +0,0 @@
<!-- 删除图标:垃圾桶 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
<!-- 桶身高亮(青绿色) -->
<path d="M9 6v12a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V6" stroke="#2dd4bf" stroke-width="0.8" opacity="0.3"/>
</svg>

Before

Width:  |  Height:  |  Size: 577 B

View File

@ -1,7 +0,0 @@
<!-- 简介图标:打开的书 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
<!-- 书页高亮(青绿色) -->
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.3" opacity="0.15"/>
</svg>

Before

Width:  |  Height:  |  Size: 495 B

View File

@ -1,10 +0,0 @@
<!-- 详情图标:文档+放大镜 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="8" y1="13" x2="16" y2="13"/>
<line x1="8" y1="17" x2="12" y2="17"/>
<!-- 放大镜(青绿色) -->
<circle cx="17" cy="17" r="3" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="19.5" y1="19.5" x2="22" y2="22" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 608 B

View File

@ -1,7 +0,0 @@
<!-- 编辑图标:铅笔 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
<!-- 铅笔尖高亮(青绿色) -->
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4z" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.3" opacity="0.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 529 B

View File

@ -1,8 +0,0 @@
<!-- 导出图标:上传箭头 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
<!-- 箭头高亮(青绿色) -->
<polyline points="17 8 12 3 7 8" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 469 B

View File

@ -1,7 +0,0 @@
<!-- 隐藏图标:眼睛+斜线 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
<line x1="1" y1="1" x2="23" y2="23"/>
<!-- 斜线高亮(青绿色) -->
<line x1="1" y1="1" x2="23" y2="23" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 574 B

View File

@ -1,7 +0,0 @@
<!-- 显示图标:眼睛 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
<!-- 眼睛高亮(青绿色) -->
<circle cx="12" cy="12" r="1.5" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 436 B

View File

@ -1,9 +0,0 @@
<!-- 失败图标:叉号圆圈 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<line x1="9" y1="9" x2="15" y2="15"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<!-- 叉号高亮(青绿色) -->
<line x1="9" y1="9" x2="15" y2="15" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="15" y1="9" x2="9" y2="15" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 528 B

View File

@ -1,9 +0,0 @@
<!-- 文件图标:文档 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="8" y1="13" x2="16" y2="13"/>
<line x1="8" y1="17" x2="13" y2="17"/>
<!-- 文件折角高亮(青绿色) -->
<polyline points="14 2 14 8 20 8" stroke="#2dd4bf" stroke-width="1" opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 541 B

View File

@ -1,7 +0,0 @@
<!-- 打开的文件夹图标 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<path d="M2 10h20"/>
<!-- 文件夹内部高亮(青绿色) -->
<path d="M4 10h16v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-7z" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.3" opacity="0.15"/>
</svg>

Before

Width:  |  Height:  |  Size: 506 B

View File

@ -1,7 +0,0 @@
<!-- 刷新文件夹图标:文件夹+刷新箭头 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<!-- 刷新箭头(青绿色) -->
<path d="M14 13a3 3 0 1 1-1-2.2L14 12" stroke="#2dd4bf" stroke-width="1.5"/>
<polyline points="12 10 14 12 16 10" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 527 B

View File

@ -1,6 +0,0 @@
<!-- 文件夹图标:关闭状态 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<!-- 文件夹标签高亮(青绿色) -->
<path d="M4 5h5l2 3h9" stroke="#2dd4bf" stroke-width="1" opacity="0.4"/>
</svg>

Before

Width:  |  Height:  |  Size: 439 B

View File

@ -1,8 +0,0 @@
<!-- 网络图标:地球 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="2" y1="12" x2="22" y2="12"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
<!-- 地球高亮(青绿色) -->
<path d="M12 2a15.3 15.3 0 0 1 4 10" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 514 B

View File

@ -1,8 +0,0 @@
<!-- 信息图标圆圈i -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="16" x2="12" y2="12"/>
<line x1="12" y1="8" x2="12.01" y2="8"/>
<!-- 信息符号高亮(青绿色) -->
<line x1="12" y1="16" x2="12" y2="12" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 461 B

View File

@ -1,7 +0,0 @@
<!-- 链接图标:链条 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
<!-- 链条连接处高亮(青绿色) -->
<path d="M10 13a5 5 0 0 0 2 1.5" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 487 B

View File

@ -1,10 +0,0 @@
<!-- 日志图标:剪贴板 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
<line x1="8" y1="10" x2="16" y2="10"/>
<line x1="8" y1="14" x2="16" y2="14"/>
<line x1="8" y1="18" x2="12" y2="18"/>
<!-- 日志条目高亮(青绿色) -->
<line x1="8" y1="10" x2="11" y2="10" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 609 B

View File

@ -1,8 +0,0 @@
<!-- 退出图标:门+箭头 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
<!-- 箭头高亮(青绿色) -->
<polyline points="16 17 21 12 16 7" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 471 B

View File

@ -1,11 +0,0 @@
<!-- 电影图标:场记板 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="6" width="18" height="14" rx="1"/>
<path d="M3 10h18"/>
<path d="M7 6V3"/>
<path d="M12 6V3"/>
<path d="M17 6V3"/>
<!-- 场记板条纹高亮(青绿色) -->
<line x1="5" y1="8" x2="7" y2="8" stroke="#2dd4bf" stroke-width="1.5"/>
<line x1="9" y1="8" x2="11" y2="8" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

View File

@ -1,10 +0,0 @@
<!-- 内外网切换图标:双向箭头 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="4" y1="8" x2="20" y2="8"/>
<polyline points="16 4 20 8 16 12"/>
<line x1="20" y1="16" x2="4" y2="16"/>
<polyline points="8 12 4 16 8 20"/>
<!-- 箭头高亮(青绿色) -->
<polyline points="16 4 20 8 16 12" stroke="#2dd4bf" stroke-width="1.2"/>
<polyline points="8 12 4 16 8 20" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 577 B

View File

@ -1,154 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>图标预览 - 网盘管理系统</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f5f7fa;
padding: 32px;
color: #374151;
}
h1 { text-align: center; font-size: 22px; font-weight: 700; margin-bottom: 8px; }
.subtitle { text-align: center; font-size: 13px; color: #94a3b8; margin-bottom: 32px; }
.section-title {
font-size: 15px; font-weight: 600; color: #64748b; margin: 28px 0 12px;
padding-bottom: 8px; border-bottom: 1px solid #e2e8f0;
}
.icon-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 14px;
max-width: 960px;
margin: 0 auto;
}
.icon-card {
display: flex; flex-direction: column; align-items: center; gap: 8px;
padding: 16px 8px 12px; background: #fff; border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06); transition: all 0.2s;
}
.icon-card:hover { box-shadow: 0 4px 16px rgba(45,212,191,0.15); transform: translateY(-2px); }
.icon-card img { width: 40px; height: 40px; object-fit: contain; }
.icon-card span { font-size: 11px; color: #64748b; text-align: center; white-space: nowrap; }
.style-info {
max-width: 960px; margin: 32px auto 0; padding: 20px 24px;
background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);
font-size: 13px; line-height: 1.8; color: #64748b;
}
.style-info strong { color: #374151; }
.color-dot { display: inline-block; width: 12px; height: 12px; border-radius: 3px; vertical-align: middle; margin-right: 4px; }
</style>
</head>
<body>
<h1>网盘管理系统 - 统一风格图标集</h1>
<p class="subtitle">深色描边 + 青绿色点缀 · 简约立体风格 · 根据代码实际使用场景生成</p>
<div class="section-title">顶部导航栏</div>
<div class="icon-grid" id="grid-topbar"></div>
<div class="section-title">用户菜单</div>
<div class="icon-grid" id="grid-user"></div>
<div class="section-title">网盘与文件管理</div>
<div class="icon-grid" id="grid-drive"></div>
<div class="section-title">刮削与日志</div>
<div class="icon-grid" id="grid-scrape"></div>
<div class="section-title">海报详情</div>
<div class="icon-grid" id="grid-poster"></div>
<div class="section-title">设置与绑定</div>
<div class="icon-grid" id="grid-settings"></div>
<div class="section-title">状态与提示</div>
<div class="icon-grid" id="grid-status"></div>
<div class="style-info">
<strong>设计规范:</strong><br/>
• 主色(描边):<span class="color-dot" style="background:#374151"></span>#374151 深灰色<br/>
• 点缀色(高亮):<span class="color-dot" style="background:#2dd4bf"></span>#2dd4bf 青绿色<br/>
• 尺寸viewBox 24×24显示 48×48px<br/>
• 风格:线条描边 + 局部色块填充,简约立体感<br/>
• 格式SVG 矢量图,可无损缩放<br/>
• 用途:替换代码中现有的 emoji 图标,实现视觉统一
</div>
<script>
const iconGroups = {
'grid-topbar': [
{ file: 'search.svg', name: '搜索' },
{ file: 'settings.svg', name: '设置' },
{ file: 'globe.svg', name: '外网' },
{ file: 'network.svg', name: '内外网切换' },
{ file: 'user.svg', name: '用户' },
{ file: 'switch-view.svg', name: '切换视图' },
],
'grid-user': [
{ file: 'movie.svg', name: '海报展示' },
{ file: 'log.svg', name: '控制台' },
{ file: 'settings.svg', name: 'TMDB配置' },
{ file: 'tools.svg', name: '设置' },
{ file: 'export.svg', name: '导出数据' },
{ file: 'refresh.svg', name: '恢复数据' },
{ file: 'logout.svg', name: '退出登录' },
],
'grid-drive': [
{ file: 'folder.svg', name: '文件夹' },
{ file: 'folder-open.svg', name: '打开文件夹' },
{ file: 'file.svg', name: '文件' },
{ file: 'add-folder.svg', name: '添加文件夹' },
{ file: 'add-file.svg', name: '添加文件' },
{ file: 'folder-refresh.svg', name: '刷新文件夹' },
{ file: 'checkbox.svg', name: '多选' },
{ file: 'batch-delete.svg', name: '批量删除' },
{ file: 'delete.svg', name: '删除' },
{ file: 'edit.svg', name: '编辑' },
],
'grid-scrape': [
{ file: 'scrape.svg', name: '刮削' },
{ file: 'batch-scrape.svg', name: '批量刮削' },
{ file: 'sync.svg', name: '同步' },
{ file: 'log.svg', name: '刮削日志' },
],
'grid-poster': [
{ file: 'movie.svg', name: '电影' },
{ file: 'cast.svg', name: '主演' },
{ file: 'tag.svg', name: '标签' },
{ file: 'detail.svg', name: '查看详情' },
{ file: 'description.svg', name: '简介' },
],
'grid-settings': [
{ file: 'link.svg', name: '绑定链接' },
{ file: 'globe.svg', name: 'OpenList地址' },
{ file: 'user.svg', name: '账号' },
{ file: 'eye.svg', name: '显示密码' },
{ file: 'eye-off.svg', name: '隐藏密码' },
],
'grid-status': [
{ file: 'success.svg', name: '成功' },
{ file: 'fail.svg', name: '失败' },
{ file: 'warning.svg', name: '警告' },
{ file: 'info.svg', name: '信息' },
{ file: 'sparkle.svg', name: '提示' },
{ file: 'save.svg', name: '保存' },
{ file: 'close.svg', name: '关闭' },
],
};
Object.entries(iconGroups).forEach(([gridId, icons]) => {
const grid = document.getElementById(gridId);
if (!grid) return;
icons.forEach(icon => {
const card = document.createElement('div');
card.className = 'icon-card';
card.innerHTML = `<img src="${icon.file}" alt="${icon.name}" /><span>${icon.name}</span>`;
grid.appendChild(card);
});
});
</script>
</body>
</html>

View File

@ -1,9 +0,0 @@
<!-- 刷新图标:循环箭头 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 2v6h-6"/>
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"/>
<path d="M3 22v-6h6"/>
<path d="M21 12a9 9 0 0 1-15 6.7L3 16"/>
<!-- 箭头高亮(青绿色) -->
<path d="M21 2v6h-6" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 458 B

View File

@ -1,8 +0,0 @@
<!-- 保存图标:软盘 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17 21 17 13 7 13 7 21"/>
<polyline points="7 3 7 8 15 8"/>
<!-- 软盘标签高亮(青绿色) -->
<rect x="9" y="15" width="6" height="4" rx="0.5" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 525 B

View File

@ -1,11 +0,0 @@
<!-- 刮削图标:电影+刷新 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="6" width="18" height="14" rx="1"/>
<path d="M3 10h18"/>
<path d="M7 6V3"/>
<path d="M12 6V3"/>
<path d="M17 6V3"/>
<!-- 刷新箭头(青绿色) -->
<path d="M14 13a3 3 0 1 1-1-2.2L14 12" stroke="#2dd4bf" stroke-width="1.5"/>
<polyline points="12 10 14 12 16 10" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 559 B

View File

@ -1,7 +0,0 @@
<!-- 搜索图标:放大镜 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="10.5" cy="10.5" r="6.5"/>
<line x1="15.5" y1="15.5" x2="21" y2="21"/>
<!-- 镜片反光(青绿色) -->
<path d="M7 7.5a5 5 0 0 1 3-1" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 416 B

View File

@ -1,7 +0,0 @@
<!-- 设置图标:齿轮 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
<!-- 齿轮中心高亮(青绿色) -->
<circle cx="12" cy="12" r="1.5" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 1012 B

View File

@ -1,6 +0,0 @@
<!-- 提示图标:星星/闪光 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8z"/>
<!-- 星星高亮(青绿色) -->
<path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22z" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.3" opacity="0.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 450 B

View File

@ -1,5 +0,0 @@
<!-- 成功图标:对勾圆圈 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<path d="M8 12l3 3 5-6" stroke="#2dd4bf" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 319 B

View File

@ -1,10 +0,0 @@
<!-- 切换视图图标:⇄ 双向切换 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="17 1 21 5 17 9"/>
<path d="M3 11V9a4 4 0 0 1 4-4h14"/>
<polyline points="7 23 3 19 7 15"/>
<path d="M21 13v2a4 4 0 0 1-4 4H3"/>
<!-- 箭头高亮(青绿色) -->
<polyline points="17 1 21 5 17 9" stroke="#2dd4bf" stroke-width="1.2"/>
<polyline points="7 23 3 19 7 15" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 574 B

View File

@ -1,7 +0,0 @@
<!-- 同步图标:云端同步 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
<polyline points="21 3 21 9 15 9"/>
<!-- 同步箭头高亮(青绿色) -->
<polyline points="21 3 21 9 15 9" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 422 B

View File

@ -1,7 +0,0 @@
<!-- 标签图标 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
<line x1="7" y1="7" x2="7.01" y2="7"/>
<!-- 标签孔高亮(青绿色) -->
<circle cx="7" cy="7" r="1" fill="#2dd4bf" stroke="#2dd4bf" stroke-width="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 467 B

View File

@ -1,6 +0,0 @@
<!-- 工具图标:扳手 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
<!-- 扳手头部高亮(青绿色) -->
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 513 B

View File

@ -1,7 +0,0 @@
<!-- 用户图标:人物轮廓 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
<!-- 头像高亮(青绿色) -->
<path d="M10 5.5a3 3 0 0 1 2-1" stroke="#2dd4bf" stroke-width="1.2"/>
</svg>

Before

Width:  |  Height:  |  Size: 423 B

View File

@ -1,8 +0,0 @@
<!-- 警告图标:三角感叹号 -->
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#374151" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
<!-- 感叹号高亮(青绿色) -->
<line x1="12" y1="9" x2="12" y2="13" stroke="#2dd4bf" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

View File

@ -1,363 +0,0 @@
<!--
统一图标组件 - 支持白天/黑夜两种主题
用法: <AppIcon name="search" size="20" />
主题颜色通过 CSS 变量自动适配:
- 白天模式: 描边 #374151, 点缀 #0f766e (品牌青)
- 黑夜模式: 描边 #94a3b8, 点缀 #38bdf8 (品牌蓝)
-->
<template>
<span class="app-icon" :style="{ width: size + 'px', height: size + 'px' }">
<!-- 搜索 -->
<svg v-if="name === 'search'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="10.5" cy="10.5" r="6.5"/>
<line x1="15.5" y1="15.5" x2="21" y2="21"/>
<path d="M7 7.5a5 5 0 0 1 3-1" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 设置/齿轮 -->
<svg v-else-if="name === 'settings'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
<circle cx="12" cy="12" r="1.5" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.5"/>
</svg>
<!-- 文件夹(关闭) -->
<svg v-else-if="name === 'folder'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<path d="M4 5h5l2 3h9" stroke="var(--icon-accent)" stroke-width="1" opacity="0.4"/>
</svg>
<!-- 文件夹(打开) -->
<svg v-else-if="name === 'folder-open'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<path d="M2 10h20"/>
<path d="M4 10h16v7a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-7z" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.3" opacity="0.15"/>
</svg>
<!-- 用户 -->
<svg v-else-if="name === 'user'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
<path d="M10 5.5a3 3 0 0 1 2-1" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 成功/对勾 -->
<svg v-else-if="name === 'success'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<path d="M8 12l3 3 5-6" stroke="var(--icon-accent)" stroke-width="2"/>
</svg>
<!-- 删除/垃圾桶 -->
<svg v-else-if="name === 'delete'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
<path d="M9 6v12a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V6" stroke="var(--icon-accent)" stroke-width="0.8" opacity="0.3"/>
</svg>
<!-- 刷新 -->
<svg v-else-if="name === 'refresh'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 2v6h-6"/>
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"/>
<path d="M3 22v-6h6"/>
<path d="M21 12a9 9 0 0 1-15 6.7L3 16"/>
<path d="M21 2v6h-6" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 电影/场记板 -->
<svg v-else-if="name === 'movie'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="6" width="18" height="14" rx="1"/>
<path d="M3 10h18"/>
<path d="M7 6V3"/>
<path d="M12 6V3"/>
<path d="M17 6V3"/>
<line x1="5" y1="8" x2="7" y2="8" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="9" y1="8" x2="11" y2="8" stroke="var(--icon-accent)" stroke-width="1.5"/>
</svg>
<!-- 日志/剪贴板 -->
<svg v-else-if="name === 'log'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
<line x1="8" y1="10" x2="16" y2="10"/>
<line x1="8" y1="14" x2="16" y2="14"/>
<line x1="8" y1="18" x2="12" y2="18"/>
<line x1="8" y1="10" x2="11" y2="10" stroke="var(--icon-accent)" stroke-width="1.5"/>
</svg>
<!-- 工具/扳手 -->
<svg v-else-if="name === 'tools'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 导出/上传 -->
<svg v-else-if="name === 'export'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
<polyline points="17 8 12 3 7 8" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 退出登录 -->
<svg v-else-if="name === 'logout'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
<polyline points="16 17 21 12 16 7"/>
<line x1="21" y1="12" x2="9" y2="12"/>
<polyline points="16 17 21 12 16 7" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 提示/闪光 -->
<svg v-else-if="name === 'sparkle'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22l-2.4-7.2L2 12l7.6-2.8z"/>
<path d="M12 2l2.4 7.2L22 12l-7.6 2.8L12 22z" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.3" opacity="0.2"/>
</svg>
<!-- 保存/软盘 -->
<svg v-else-if="name === 'save'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17 21 17 13 7 13 7 21"/>
<polyline points="7 3 7 8 15 8"/>
<rect x="9" y="15" width="6" height="4" rx="0.5" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.5"/>
</svg>
<!-- 链接 -->
<svg v-else-if="name === 'link'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
<path d="M10 13a5 5 0 0 0 2 1.5" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 地球/网络 -->
<svg v-else-if="name === 'globe'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="2" y1="12" x2="22" y2="12"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
<path d="M12 2a15.3 15.3 0 0 1 4 10" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 眼睛(显示) -->
<svg v-else-if="name === 'eye'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
<circle cx="12" cy="12" r="1.5" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.5"/>
</svg>
<!-- 眼睛关闭(隐藏) -->
<svg v-else-if="name === 'eye-off'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/>
<line x1="1" y1="1" x2="23" y2="23" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 多选/复选框 -->
<svg v-else-if="name === 'checkbox'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="3"/>
<path d="M7 12l3.5 3.5L17 9" stroke="var(--icon-accent)" stroke-width="2"/>
</svg>
<!-- 警告 -->
<svg v-else-if="name === 'warning'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
<line x1="12" y1="9" x2="12" y2="13" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
<!-- 文件/文档 -->
<svg v-else-if="name === 'file'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="8" y1="13" x2="16" y2="13"/>
<line x1="8" y1="17" x2="13" y2="17"/>
<polyline points="14 2 14 8 20 8" stroke="var(--icon-accent)" stroke-width="1" opacity="0.5"/>
</svg>
<!-- 信息 -->
<svg v-else-if="name === 'info'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="12" y1="16" x2="12" y2="12" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="12" y1="8" x2="12.01" y2="8"/>
</svg>
<!-- 内外网切换 -->
<svg v-else-if="name === 'network'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="4" y1="8" x2="20" y2="8"/>
<polyline points="16 4 20 8 16 12" stroke="var(--icon-accent)" stroke-width="1.2"/>
<line x1="20" y1="16" x2="4" y2="16"/>
<polyline points="8 12 4 16 8 20" stroke="var(--icon-accent)" stroke-width="1.2"/>
</svg>
<!-- 标签 -->
<svg v-else-if="name === 'tag'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
<circle cx="7" cy="7" r="1" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.5"/>
</svg>
<!-- 主演/面具 -->
<svg v-else-if="name === 'cast'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12c0-5 4-9 10-9s10 4 10 9"/>
<path d="M2 12c0 3 2 5 4 5s3-2 4-5"/>
<path d="M14 12c1 3 2 5 4 5s4-2 4-5"/>
<path d="M2 12c0 3 2 5 4 5s3-2 4-5" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.3" opacity="0.2"/>
</svg>
<!-- 详情 -->
<svg v-else-if="name === 'detail'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="8" y1="13" x2="16" y2="13"/>
<line x1="8" y1="17" x2="12" y2="17"/>
<circle cx="17" cy="17" r="3" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="19.5" y1="19.5" x2="22" y2="22" stroke="var(--icon-accent)" stroke-width="1.5"/>
</svg>
<!-- 简介/书本 -->
<svg v-else-if="name === 'description'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.3" opacity="0.15"/>
</svg>
<!-- 编辑/铅笔 -->
<svg v-else-if="name === 'edit'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4z" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.3" opacity="0.2"/>
</svg>
<!-- 同步 -->
<svg v-else-if="name === 'sync'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
<polyline points="21 3 21 9 15 9" stroke="var(--icon-accent)" stroke-width="1.5"/>
</svg>
<!-- 失败/叉号 -->
<svg v-else-if="name === 'fail'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<line x1="9" y1="9" x2="15" y2="15" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="15" y1="9" x2="9" y2="15" stroke="var(--icon-accent)" stroke-width="1.5"/>
</svg>
<!-- 关闭/X -->
<svg v-else-if="name === 'close'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18" stroke="var(--icon-accent)" stroke-width="1.2"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>
<!-- 太阳(白天模式) -->
<svg v-else-if="name === 'sun'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
<circle cx="12" cy="12" r="2.5" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.5" opacity="0.3"/>
</svg>
<!-- 月亮(黑夜模式) -->
<svg v-else-if="name === 'moon'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
<path d="M21 12.79A9 9 0 0 1 12 21" fill="var(--icon-accent)" stroke="var(--icon-accent)" stroke-width="0.3" opacity="0.2"/>
</svg>
<!-- 添加文件夹 -->
<svg v-else-if="name === 'add-folder'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
<line x1="12" y1="11" x2="12" y2="17" stroke="var(--icon-accent)" stroke-width="2"/>
<line x1="9" y1="14" x2="15" y2="14" stroke="var(--icon-accent)" stroke-width="2"/>
</svg>
<!-- 添加文件 -->
<svg v-else-if="name === 'add-file'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="12" y1="13" x2="12" y2="19" stroke="var(--icon-accent)" stroke-width="2"/>
<line x1="9" y1="16" x2="15" y2="16" stroke="var(--icon-accent)" stroke-width="2"/>
</svg>
<!-- 批量删除 -->
<svg v-else-if="name === 'batch-delete'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<line x1="10" y1="11" x2="10" y2="17" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="14" y1="11" x2="14" y2="17" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="9" y1="9" x2="15" y2="9" stroke="var(--icon-accent)" stroke-width="1"/>
</svg>
<!-- 批量刮削 -->
<svg v-else-if="name === 'batch-scrape'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="6" width="18" height="14" rx="1"/>
<path d="M3 10h18"/>
<path d="M7 6V3"/><path d="M12 6V3"/><path d="M17 6V3"/>
<line x1="5" y1="8" x2="7" y2="8" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="9" y1="8" x2="11" y2="8" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="13" y1="8" x2="15" y2="8" stroke="var(--icon-accent)" stroke-width="1.5"/>
<line x1="17" y1="8" x2="19" y2="8" stroke="var(--icon-accent)" stroke-width="1.5"/>
</svg>
<!-- 刮削 -->
<svg v-else-if="name === 'scrape'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="6" width="18" height="14" rx="1"/>
<path d="M3 10h18"/>
<path d="M7 6V3"/><path d="M12 6V3"/><path d="M17 6V3"/>
<path d="M14 13a3 3 0 1 1-1-2.2L14 12" stroke="var(--icon-accent)" stroke-width="1.5"/>
<polyline points="12 10 14 12 16 10" stroke="var(--icon-accent)" stroke-width="1.5"/>
</svg>
<!-- 切换视图 -->
<svg v-else-if="name === 'switch-view'" viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="17 1 21 5 17 9" stroke="var(--icon-accent)" stroke-width="1.2"/>
<path d="M3 11V9a4 4 0 0 1 4-4h14"/>
<polyline points="7 23 3 19 7 15" stroke="var(--icon-accent)" stroke-width="1.2"/>
<path d="M21 13v2a4 4 0 0 1-4 4H3"/>
</svg>
<!-- 默认占位 -->
<svg v-else viewBox="0 0 24 24" fill="none" stroke="var(--icon-stroke)" stroke-width="1.5">
<circle cx="12" cy="12" r="10"/>
</svg>
</span>
</template>
<script setup>
/**
* AppIcon 组件属性
* @prop {string} name - 图标名称
* @prop {number|string} size - 图标尺寸默认 20
*/
defineProps({
name: { type: String, default: '' },
size: { type: [Number, String], default: 20 },
})
</script>
<style scoped>
/*
* 图标容器 - 使用 CSS 变量实现主题自适应
* 白天模式: --ink=#172033, --brand=#0f766e
* 黑夜模式: --ink=#e2e8f0, --brand=#38bdf8
*/
.app-icon {
display: inline-flex;
align-items: center;
justify-content: center;
/* 主色(描边): 继承当前文本颜色 */
--icon-stroke: var(--ink);
/* 点缀色(高亮): 继承品牌色 */
--icon-accent: var(--brand);
vertical-align: middle;
line-height: 1;
}
.app-icon svg {
width: 100%;
height: 100%;
}
</style>

View File

@ -1,155 +0,0 @@
<template>
<div class="folder-tree">
<!-- 多选模式工具栏 -->
<div v-if="multiSelectMode" class="multi-select-toolbar">
<span class="multi-select-toolbar__count">已选择 {{ selectedIds.size }} </span>
<button type="button" class="multi-select-toolbar__btn multi-select-toolbar__btn--danger" @click="handleBatchDelete">
<AppIcon name="batch-delete" :size="16" /> 批量删除
</button>
<button type="button" class="multi-select-toolbar__btn" @click="exitMultiSelect">
<AppIcon name="close" :size="16" /> 退出多选
</button>
</div>
<!-- 根节点列表 -->
<div v-for="node in treeData" :key="node.id" class="tree-node">
<TreeNode
:node="node"
:depth="0"
:expanded-folder-ids="expandedFolderIds"
:selected-ids="selectedIds"
:multi-select-mode="multiSelectMode"
@toggle-select="handleToggleSelect"
@add-folder="handleAddFolder"
@add-file="handleAddFile"
@expand="handleExpand"
@edit="handleEdit"
@delete="handleDelete"
@scrape-status="handleScrapeStatus"
@rescrape="handleRescrape"
/>
</div>
</div>
</template>
<script setup>
import TreeNode from './TreeNode.vue'
const props = defineProps({
treeData: {
type: Array,
default: () => [],
},
expandedFolderIds: {
type: Set,
default: () => new Set(),
},
multiSelectMode: {
type: Boolean,
default: false,
},
selectedIds: {
type: Set,
default: () => new Set(),
},
})
const emit = defineEmits(['add-folder', 'add-file', 'expand', 'edit', 'delete', 'toggle-select', 'batch-delete', 'toggle-multi-select-exit', 'scrape-status', 'rescrape'])
function handleAddFolder(parentId) {
emit('add-folder', parentId)
}
function handleAddFile(parentId) {
emit('add-file', parentId)
}
function handleEdit(node) {
emit('edit', node)
}
function handleExpand(node) {
emit('expand', node)
}
function handleDelete(node) {
emit('delete', node)
}
function handleScrapeStatus(node) {
emit('scrape-status', node)
}
function handleRescrape(node) {
emit('rescrape', node)
}
function handleToggleSelect(nodeId) {
emit('toggle-select', nodeId)
}
function handleBatchDelete() {
emit('batch-delete', props.selectedIds)
}
function exitMultiSelect() {
emit('toggle-multi-select-exit')
}
</script>
<style scoped>
.folder-tree {
padding: 16px;
}
.tree-node {
margin-bottom: 8px;
}
/* 多选模式工具栏样式 */
.multi-select-toolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
margin-bottom: 16px;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.08), rgba(139, 92, 246, 0.08));
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 10px;
}
.multi-select-toolbar__count {
font-size: 14px;
font-weight: 600;
color: #6366f1;
}
.multi-select-toolbar__btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
background: rgba(99, 102, 241, 0.1);
color: #6366f1;
}
.multi-select-toolbar__btn:hover {
background: rgba(99, 102, 241, 0.2);
transform: translateY(-1px);
}
.multi-select-toolbar__btn--danger {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
}
.multi-select-toolbar__btn--danger:hover {
background: rgba(239, 68, 68, 0.2);
}
</style>

View File

@ -1,152 +0,0 @@
<template>
<article class="poster-card" :class="{ 'is-readonly': readonly }" @click="handleClick">
<div class="poster-thumb">
<img :src="posterImageUrl" :alt="item.title" loading="lazy" />
<span class="score-badge">{{ displayScore }}</span>
</div>
<div class="poster-body">
<div class="poster-meta-row">
<span class="poster-time">{{ displayFormattedDate }}</span>
</div>
<h3>{{ item.title }}</h3>
<p v-if="item.originalName">{{ item.originalName }}</p>
<!-- 导演和主演 -->
<div v-if="item.directors" class="poster-director">
<AppIcon name="movie" :size="14" /> {{ item.directors }}
</div>
<div v-if="item.casts" class="poster-casts">
<AppIcon name="cast" :size="14" /> {{ item.casts }}
</div>
<!-- 简介按钮点击打开弹窗 -->
<div v-if="item.description" class="poster-description">
<button
type="button"
class="description-toggle"
@click.stop="showDescriptionDialog = true"
>
<AppIcon name="detail" :size="14" /> 查看详情
</button>
</div>
<div class="tag-list">
<span v-for="tag in displayTags" :key="tag">{{ tag }}</span>
</div>
</div>
<!-- 简介弹窗 -->
<Teleport to="body">
<Transition name="dialog-fade">
<div v-if="showDescriptionDialog" class="description-overlay" @click.self="showDescriptionDialog = false">
<div class="description-dialog">
<div class="description-dialog__header">
<h3>{{ item.title }} - 详情</h3>
<button type="button" class="description-dialog__close" @click="showDescriptionDialog = false"></button>
</div>
<div class="description-dialog__body">
<!-- 基本信息 -->
<div v-if="item.description" class="detail-section">
<h4><AppIcon name="description" :size="16" /> 简介</h4>
<p class="detail-text">{{ item.description }}</p>
</div>
<div v-if="item.directors" class="detail-section">
<h4><AppIcon name="movie" :size="16" /> 导演</h4>
<p class="detail-text">{{ item.directors }}</p>
</div>
<div v-if="item.casts" class="detail-section">
<h4><AppIcon name="cast" :size="16" /> 主演</h4>
<p class="detail-text">{{ item.casts }}</p>
</div>
<div v-if="item.tags" class="detail-section">
<h4><AppIcon name="tag" :size="16" /> 标签</h4>
<div class="detail-tags">
<span v-for="tag in displayTags" :key="tag" class="detail-tag">{{ tag }}</span>
</div>
</div>
<div v-if="item.score || item.rating" class="detail-section">
<h4><AppIcon name="sparkle" :size="16" /> 评分</h4>
<p class="detail-text">{{ displayScore }}</p>
</div>
<div v-if="item.category" class="detail-section">
<h4><AppIcon name="folder-open" :size="16" /> 分类</h4>
<p class="detail-text">{{ item.category }}</p>
</div>
<div v-if="item.status" class="detail-section">
<h4><AppIcon name="info" :size="16" /> 状态</h4>
<p class="detail-text">{{ displayStatus }}</p>
</div>
</div>
</div>
</div>
</Transition>
</Teleport>
</article>
</template>
<script setup>
import { computed, ref } from 'vue'
import AppIcon from './AppIcon.vue'
const props = defineProps({
item: {
type: Object,
required: true,
},
readonly: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['edit'])
//
const showDescriptionDialog = ref(false)
// URL
const posterImageUrl = computed(() => {
// poster, posterUrl, backdropUrl
return props.item.poster || props.item.posterUrl || props.item.backdropUrl || ''
})
//
const displayScore = computed(() => {
const score = props.item.score ?? props.item.rating ?? 0
return score ? `${score}` : '暂无评分'
})
// ISO 8601
const displayFormattedDate = computed(() => {
const raw = props.item.updatedAt || props.item.updateTime || props.item.createTime || ''
if (!raw) return ''
// T 2026-06-26T15:55:42 2026-06-26 15:55:42
let formatted = raw.replace('T', ' ')
// .000Z +08:00
formatted = formatted.replace(/\.\d+(Z|[+-]\d{2}:\d{2})?$/, '')
return formatted
})
//
const displayStatus = computed(() => {
return props.item.status || '正常'
})
//
const displayTags = computed(() => {
const tags = props.item.tags
if (Array.isArray(tags)) return tags
if (typeof tags === 'string' && tags.trim()) return tags.split(',').map(t => t.trim())
return []
})
function handleClick() {
if (!props.readonly) {
emit('edit', props.item)
}
}
</script>

View File

@ -1,837 +0,0 @@
<template>
<div class="settings-view">
<div class="settings-header">
<p>配置 OpenList 后端接口绑定您的云盘账户</p>
</div>
<!-- 加载状态等待后端返回数据 -->
<div v-if="loading" class="loading-container">
<div class="loading-spinner-icon"></div>
<p class="loading-text">正在加载绑定信息...</p>
</div>
<!-- 数据加载完成后显示内容 -->
<div v-else class="settings-card">
<!-- 已绑定信息顶部展示 -->
<Transition name="fade">
<div v-if="boundInfo" class="bound-info">
<div class="bound-info__header">
<span class="bound-info__icon"><AppIcon name="success" :size="18" /></span>
<span class="bound-info__text">已绑定 OpenList</span>
</div>
<div class="bound-info__details">
<div class="bound-info__item">
<span class="bound-info__label">地址</span>
<span class="bound-info__value">{{ boundInfo.url }}</span>
</div>
<div class="bound-info__item">
<span class="bound-info__label">账号</span>
<span class="bound-info__value">{{ boundInfo.username }}</span>
</div>
<div class="bound-info__item">
<span class="bound-info__label">存储ID</span>
<span class="bound-info__value">{{ boundInfo.storageId || '-' }}</span>
</div>
<div class="bound-info__item">
<span class="bound-info__label">挂载路径</span>
<span class="bound-info__value">{{ boundInfo.mountPath || '-' }}</span>
</div>
<div class="bound-info__item">
<span class="bound-info__label">状态</span>
<span class="bound-info__value" :class="{ 'status-active': boundInfo.status === 'active' }">{{ boundInfo.status === 'active' ? '正常' : (boundInfo.status || '-') }}</span>
</div>
<div class="bound-info__item">
<span class="bound-info__label">Token有效</span>
<span class="bound-info__value">
<AppIcon :name="boundInfo.tokenValid ? 'success' : 'fail'" :size="14" />
{{ boundInfo.tokenValid ? '是' : '否' }}
</span>
</div>
<div class="bound-info__item" v-if="boundInfo.connectMessage">
<span class="bound-info__label">连接状态</span>
<span class="bound-info__value connect-message" :class="{ 'connect-ok': boundInfo.connected === true, 'connect-error': boundInfo.connected === false }">
<AppIcon :name="boundInfo.connected ? 'success' : 'fail'" :size="14" /> {{ boundInfo.connectMessage }}
</span>
</div>
<div class="bound-info__item">
<span class="bound-info__label">创建时间</span>
<span class="bound-info__value">{{ formatTime(boundInfo.createTime) }}</span>
</div>
<div class="bound-info__item">
<span class="bound-info__label">更新时间</span>
<span class="bound-info__value">{{ formatTime(boundInfo.updateTime) }}</span>
</div>
</div>
<button
type="button"
class="btn-unbind"
@click="handleUnbind"
>
<AppIcon name="delete" :size="16" /> 解除绑定
</button>
</div>
</Transition>
<h3 class="settings-card__title">
<span class="settings-card__icon"><AppIcon name="link" :size="20" /></span>
OpenList 绑定
</h3>
<form class="settings-form" @submit.prevent="handleBind">
<!-- OpenList 地址 -->
<div class="form-group">
<label class="form-label" for="openlist-url">
<span class="form-label__icon"><AppIcon name="globe" :size="16" /></span>
<span class="form-label__text">OpenList 地址</span>
</label>
<input
id="openlist-url"
v-model="formData.url"
type="url"
class="form-input"
placeholder="例如https://olist.example.com 或 http://localhost:5575"
required
autocomplete="off"
readonly
onfocus="this.removeAttribute('readonly')"
/>
<span class="form-help">请填写 OpenList 服务的完整访问地址包含协议头</span>
</div>
<!-- 账号 -->
<div class="form-group">
<label class="form-label" for="openlist-username">
<span class="form-label__icon"><AppIcon name="user" :size="16" /></span>
<span class="form-label__text">账号</span>
</label>
<input
id="openlist-username"
v-model="formData.username"
type="text"
class="form-input"
placeholder="请输入 OpenList 用户名"
required
autocomplete="off"
/>
</div>
<!-- 密码 -->
<div class="form-group">
<label class="form-label" for="openlist-password">
<span class="form-label__icon"><AppIcon name="save" :size="16" /></span>
<span class="form-label__text">密码</span>
</label>
<div class="password-input-wrapper">
<input
id="openlist-password"
v-model="formData.password"
:type="showPassword ? 'text' : 'password'"
class="form-input"
placeholder="请输入 OpenList 密码"
required
autocomplete="off"
/>
<button
type="button"
class="password-toggle"
@click="showPassword = !showPassword"
:title="showPassword ? '隐藏密码' : '显示密码'"
>
<AppIcon :name="showPassword ? 'eye-off' : 'eye'" :size="18" />
</button>
</div>
</div>
<!-- 绑定按钮 -->
<div class="form-actions">
<button
type="submit"
class="btn-bind"
:disabled="binding"
:class="{ 'btn-bind--loading': binding }"
>
<span v-if="!binding"><AppIcon name="link" :size="16" /> 绑定 OpenList</span>
<span v-else class="loading-spinner"><AppIcon name="sync" :size="16" /> 绑定中...</span>
</button>
<button
type="button"
class="btn-reset"
@click="resetForm"
:disabled="binding"
>
重置
</button>
</div>
</form>
<!-- 绑定状态提示 -->
<Transition name="fade">
<div v-if="bindStatus" class="bind-status" :class="`bind-status--${bindStatus.type}`">
<span class="bind-status__icon"><AppIcon v-if="bindStatus.iconName" :name="bindStatus.iconName" :size="18" /></span>
<span class="bind-status__text">{{ bindStatus.message }}</span>
</div>
</Transition>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { bindOpenList, unbindOpenList, getOpenListInfo } from '../api'
import AppIcon from './AppIcon.vue'
//
const formData = reactive({
url: '',
username: '',
password: '',
})
//
const loading = ref(true)
const binding = ref(false)
const showPassword = ref(false)
const bindStatus = ref(null)
const boundInfo = ref(null)
/**
* 加载已绑定的 OpenList 信息
* 后端返回格式
* - 未绑定或报错返回错误信息
* - 已绑定返回 { code: 200, data: { id, openlistUrl, openlistUsername, storageId, mountPath, status, tokenValid, connected, connectMessage, createTime, updateTime } }
*/
async function loadBoundInfo() {
loading.value = true
try {
const result = await getOpenListInfo()
// result null { code: 200, data: {...} }
if (result?.data) {
const d = result.data
boundInfo.value = {
id: d.id,
url: d.openlistUrl || '',
username: d.openlistUsername || '',
storageId: d.storageId || '',
mountPath: d.mountPath || '',
status: d.status || '',
tokenValid: d.tokenValid,
connected: d.connected,
connectMessage: d.connectMessage || '',
createTime: d.createTime || '',
updateTime: d.updateTime || '',
}
bindStatus.value = null //
} else {
// null
boundInfo.value = null
}
} catch (err) {
console.warn('加载绑定信息失败:', err)
boundInfo.value = null
//
const errorMsg = err?.payload?.message || err?.message || '加载绑定信息失败'
setBindStatus('error', errorMsg)
} finally {
loading.value = false
}
}
/**
* 处理绑定表单提交
*/
async function handleBind() {
if (!formData.url || !formData.username || !formData.password) {
setBindStatus('error', '请填写所有必填字段')
return
}
// URL
try {
new URL(formData.url)
} catch {
setBindStatus('error', '请输入有效的 URL 地址(需包含协议头,如 https://')
return
}
binding.value = true
setBindStatus(null, '')
try {
const result = await bindOpenList({
url: formData.url,
username: formData.username,
password: formData.password,
})
if (result?.code === 200) {
setBindStatus('success', '绑定成功!')
boundInfo.value = {
url: formData.url,
username: formData.username,
boundAt: new Date().toLocaleString('zh-CN'),
}
//
formData.password = ''
//
await loadBoundInfo()
} else {
setBindStatus('error', result?.message || '绑定失败,请稍后重试')
}
} catch (err) {
console.error('绑定 OpenList 失败:', err)
const errorMsg = err?.message || err?.payload?.message || '网络错误,请检查后端连接'
setBindStatus('error', errorMsg)
} finally {
binding.value = false
}
}
/**
* 处理解除绑定
*/
async function handleUnbind() {
if (!confirm('确定要解除 OpenList 绑定吗?')) {
return
}
try {
const result = await unbindOpenList()
if (result?.code === 200) {
setBindStatus('success', '已解除绑定')
boundInfo.value = null
resetForm()
setTimeout(() => {
setBindStatus(null, '')
}, 3000)
} else {
setBindStatus('error', result?.message || '解除绑定失败')
}
} catch (err) {
console.error('解除绑定失败:', err)
setBindStatus('error', err?.message || '网络错误')
}
}
/**
* 重置表单
*/
function resetForm() {
formData.url = ''
formData.username = ''
formData.password = ''
setBindStatus(null, '')
}
/**
* 格式化时间
*/
function formatTime(timeStr) {
if (!timeStr) return '-'
try {
const date = new Date(timeStr)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
} catch {
return timeStr
}
}
/**
* 设置绑定状态提示
*/
function setBindStatus(type, message, autoClear = true) {
if (!type) {
bindStatus.value = null
return
}
const iconNames = {
success: 'success',
error: 'fail',
warning: 'warning',
info: 'info',
}
bindStatus.value = {
type,
message,
iconName: iconNames[type] || '',
}
// 3
if (autoClear && type !== 'error') {
setTimeout(() => {
bindStatus.value = null
}, 3000)
}
}
//
onMounted(() => {
loadBoundInfo()
})
</script>
<style scoped>
.settings-view {
padding: 16px 0;
}
.settings-header {
margin-bottom: 24px;
}
.settings-header h2 {
font-size: 24px;
font-weight: 600;
color: #1a1a1a;
margin: 0 0 8px 0;
}
.settings-header p {
font-size: 14px;
color: #666;
margin: 0;
}
/* 卡片样式 */
.settings-card {
background: #fff;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 1px solid #e8e8e8;
}
.settings-card__title {
display: flex;
align-items: center;
gap: 8px;
font-size: 18px;
font-weight: 600;
color: #1a1a1a;
margin: 0 0 24px 0;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.settings-card__icon {
font-size: 20px;
}
/* 表单样式 */
.form-group {
margin-bottom: 20px;
}
.form-label {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.form-label__icon {
font-size: 16px;
}
.form-input {
width: 100%;
padding: 10px 14px;
font-size: 14px;
border: 1px solid #d9d9d9;
border-radius: 8px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
box-sizing: border-box;
}
.form-input:hover {
border-color: #409eff;
}
.form-input:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.15);
}
.form-input::placeholder {
color: #bfbfbf;
}
.form-help {
display: block;
margin-top: 6px;
font-size: 12px;
color: #999;
}
/* 密码输入框 */
.password-input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.password-input-wrapper .form-input {
padding-right: 44px;
}
.password-toggle {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
font-size: 18px;
padding: 4px 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.password-toggle:hover {
background-color: #f5f5f5;
}
/* 按钮组 */
.form-actions {
display: flex;
gap: 12px;
margin-top: 28px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
}
.btn-bind {
flex: 1;
padding: 12px 24px;
font-size: 15px;
font-weight: 500;
color: #fff;
background: linear-gradient(135deg, #409eff, #337ecc);
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
min-width: 140px;
}
.btn-bind:hover:not(:disabled) {
background: linear-gradient(135deg, #66b1ff, #409eff);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
.btn-bind:active:not(:disabled) {
transform: translateY(0);
}
.btn-bind:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-bind--loading {
background: linear-gradient(135deg, #909399, #69696d);
}
.btn-reset {
padding: 12px 24px;
font-size: 14px;
color: #666;
background: #f5f5f5;
border: 1px solid #d9d9d9;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
min-width: 80px;
}
.btn-reset:hover:not(:disabled) {
color: #409eff;
border-color: #409eff;
background: #ecf5ff;
}
.btn-reset:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* 状态提示 */
.bind-status {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
margin-top: 20px;
border-radius: 8px;
font-size: 14px;
}
.bind-status--success {
background: #f0f9ff;
border: 1px solid #b3e19c;
color: #67c23a;
}
.bind-status--error {
background: #fef0f0;
border: 1px solid #fbc4c4;
color: #f56c6c;
}
.bind-status__icon {
font-size: 16px;
}
/* 已绑定信息 */
.bound-info {
margin-top: 20px;
padding: 16px;
background: #f0f9ff;
border: 1px solid #b3e19c;
border-radius: 8px;
}
.bound-info__header {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 500;
color: #67c23a;
margin-bottom: 12px;
}
.bound-info__details {
margin-bottom: 16px;
}
.bound-info__item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #666;
margin-bottom: 6px;
}
.bound-info__label {
color: #999;
min-width: 70px;
}
.bound-info__value {
color: #333;
word-break: break-all;
}
/* 连接状态样式 */
.connect-message {
font-weight: 500;
}
.connect-ok {
color: #67c23a;
}
.connect-error {
color: #f56c6c;
}
.status-active {
color: #67c23a;
font-weight: 600;
}
.btn-unbind {
width: 100%;
padding: 8px 16px;
font-size: 13px;
color: #f56c6c;
background: #fff;
border: 1px solid #fbc4c4;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.btn-unbind:hover {
background: #fef0f0;
border-color: #f56c6c;
}
/* 加载动画 */
.loading-spinner {
display: inline-flex;
align-items: center;
gap: 6px;
}
/* 加载容器 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 24px;
gap: 16px;
}
.loading-spinner-icon {
width: 36px;
height: 36px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-text {
font-size: 14px;
color: #666;
margin: 0;
}
/* 过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.settings-header h2 {
color: #e5e5e5;
}
.settings-header p {
color: #999;
}
.settings-card {
background: #2a2a2a;
border-color: #3a3a3a;
}
.settings-card__title {
color: #e5e5e5;
border-bottom-color: #3a3a3a;
}
.form-label {
color: #ccc;
}
.form-input {
background: #1a1a1a;
border-color: #3a3a3a;
color: #e5e5e5;
}
.form-input:hover {
border-color: #409eff;
}
.form-input:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.form-input::placeholder {
color: #666;
}
.password-toggle:hover {
background-color: #333;
}
.btn-reset {
color: #ccc;
background: #333;
border-color: #444;
}
.btn-reset:hover:not(:disabled) {
color: #409eff;
border-color: #409eff;
background: #1a2a3a;
}
.bound-info {
background: #1a2a3a;
border-color: #2a4a5a;
}
.bound-info__item {
color: #999;
}
.bound-info__value {
color: #ccc;
}
.btn-unbind {
color: #f56c6c;
background: #2a1a1a;
border-color: #4a2a2a;
}
.btn-unbind:hover {
background: #3a1a1a;
border-color: #f56c6c;
}
.loading-spinner-icon {
border-color: #374151;
border-top-color: #60a5fa;
}
.loading-text {
color: #9ca3af;
}
}
/* 响应式 */
@media (max-width: 600px) {
.settings-view {
padding: 16px 12px;
}
.settings-card {
padding: 16px;
}
.form-actions {
flex-direction: column;
}
.btn-bind,
.btn-reset {
width: 100%;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Some files were not shown because too many files have changed in this diff Show More