mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 02:07:29 +08:00
feat: 添加 lx 音源导入
This commit is contained in:
@@ -122,7 +122,8 @@ const getSourceIcon = (source: Platform) => {
|
||||
pyncmd: 'ri-netease-cloud-music-fill',
|
||||
bilibili: 'ri-bilibili-fill',
|
||||
gdmusic: 'ri-google-fill',
|
||||
kuwo: 'ri-music-fill'
|
||||
kuwo: 'ri-music-fill',
|
||||
lxMusic: 'ri-leaf-fill'
|
||||
};
|
||||
|
||||
return iconMap[source] || 'ri-music-2-fill';
|
||||
|
||||
@@ -10,107 +10,262 @@
|
||||
@negative-click="handleCancel"
|
||||
style="width: 800px; max-width: 90vw"
|
||||
>
|
||||
<n-space vertical :size="20">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ t('settings.playback.musicSourcesDesc') }}
|
||||
</p>
|
||||
|
||||
<!-- 音源卡片列表 -->
|
||||
<div class="music-sources-grid">
|
||||
<div
|
||||
v-for="source in MUSIC_SOURCES"
|
||||
:key="source.key"
|
||||
class="source-card"
|
||||
:class="{
|
||||
'source-card--selected': isSourceSelected(source.key),
|
||||
'source-card--disabled': source.disabled && !isSourceSelected(source.key)
|
||||
}"
|
||||
:style="{ '--source-color': source.color }"
|
||||
@click="toggleSource(source.key)"
|
||||
>
|
||||
<div class="source-card__indicator"></div>
|
||||
<div class="source-card__content">
|
||||
<div class="source-card__header">
|
||||
<span class="source-card__name">{{ source.key }}</span>
|
||||
<n-icon v-if="isSourceSelected(source.key)" size="18" class="source-card__check">
|
||||
<i class="ri-checkbox-circle-fill"></i>
|
||||
</n-icon>
|
||||
</div>
|
||||
<p v-if="source.description" class="source-card__description">
|
||||
{{ source.description }}
|
||||
<div class="h-[400px]">
|
||||
<n-tabs type="segment" animated class="h-full flex flex-col">
|
||||
<!-- Tab 1: 音源选择 -->
|
||||
<n-tab-pane name="sources" tab="音源选择" class="h-full overflow-y-auto">
|
||||
<n-space vertical :size="20" class="pt-4 pr-2">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ t('settings.playback.musicSourcesDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义API卡片 -->
|
||||
<div
|
||||
class="source-card source-card--custom"
|
||||
:class="{
|
||||
'source-card--selected': isSourceSelected('custom'),
|
||||
'source-card--disabled': !settingsStore.setData.customApiPlugin
|
||||
}"
|
||||
style="--source-color: #8b5cf6"
|
||||
@click="toggleSource('custom')"
|
||||
>
|
||||
<div class="source-card__indicator"></div>
|
||||
<div class="source-card__content">
|
||||
<div class="source-card__header">
|
||||
<span class="source-card__name">{{
|
||||
t('settings.playback.sourceLabels.custom')
|
||||
}}</span>
|
||||
<n-icon v-if="isSourceSelected('custom')" size="18" class="source-card__check">
|
||||
<i class="ri-checkbox-circle-fill"></i>
|
||||
</n-icon>
|
||||
<!-- 音源卡片列表 -->
|
||||
<div class="music-sources-grid">
|
||||
<div
|
||||
v-for="source in MUSIC_SOURCES"
|
||||
:key="source.key"
|
||||
class="source-card"
|
||||
:class="{
|
||||
'source-card--selected': isSourceSelected(source.key),
|
||||
'source-card--disabled': source.disabled && !isSourceSelected(source.key)
|
||||
}"
|
||||
:style="{ '--source-color': source.color }"
|
||||
@click="toggleSource(source.key)"
|
||||
>
|
||||
<div class="source-card__indicator"></div>
|
||||
<div class="source-card__content">
|
||||
<div class="source-card__header">
|
||||
<span class="source-card__name">{{ source.key }}</span>
|
||||
<n-icon
|
||||
v-if="isSourceSelected(source.key)"
|
||||
size="18"
|
||||
class="source-card__check"
|
||||
>
|
||||
<i class="ri-checkbox-circle-fill"></i>
|
||||
</n-icon>
|
||||
</div>
|
||||
<p v-if="source.description" class="source-card__description">
|
||||
{{ source.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 落雪音源卡片 (仅开关) -->
|
||||
<div
|
||||
class="source-card source-card--lxmusic"
|
||||
:class="{
|
||||
'source-card--selected': isSourceSelected('lxMusic'),
|
||||
'source-card--disabled': !settingsStore.setData.lxMusicScript
|
||||
}"
|
||||
style="--source-color: #10b981"
|
||||
@click="toggleSource('lxMusic')"
|
||||
>
|
||||
<div class="source-card__indicator"></div>
|
||||
<div class="source-card__content">
|
||||
<div class="source-card__header">
|
||||
<span class="source-card__name">落雪音源</span>
|
||||
<n-icon v-if="isSourceSelected('lxMusic')" size="18" class="source-card__check">
|
||||
<i class="ri-checkbox-circle-fill"></i>
|
||||
</n-icon>
|
||||
</div>
|
||||
<p class="source-card__description">
|
||||
{{
|
||||
settingsStore.setData.lxMusicScript
|
||||
? lxMusicScriptInfo?.name || '已导入'
|
||||
: '未导入 (请去落雪音源Tab配置)'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自定义API卡片 (仅开关) -->
|
||||
<div
|
||||
class="source-card source-card--custom"
|
||||
:class="{
|
||||
'source-card--selected': isSourceSelected('custom'),
|
||||
'source-card--disabled': !settingsStore.setData.customApiPlugin
|
||||
}"
|
||||
style="--source-color: #8b5cf6"
|
||||
@click="toggleSource('custom')"
|
||||
>
|
||||
<div class="source-card__indicator"></div>
|
||||
<div class="source-card__content">
|
||||
<div class="source-card__header">
|
||||
<span class="source-card__name">{{
|
||||
t('settings.playback.sourceLabels.custom')
|
||||
}}</span>
|
||||
<n-icon v-if="isSourceSelected('custom')" size="18" class="source-card__check">
|
||||
<i class="ri-checkbox-circle-fill"></i>
|
||||
</n-icon>
|
||||
</div>
|
||||
<p class="source-card__description">
|
||||
{{
|
||||
settingsStore.setData.customApiPlugin
|
||||
? t('settings.playback.customApi.status.imported')
|
||||
: t('settings.playback.customApi.status.notImported')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- Tab 2: 落雪音源管理 -->
|
||||
<n-tab-pane name="lxMusic" tab="落雪音源" class="h-full overflow-y-auto">
|
||||
<div class="pt-4 pr-2">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-base font-medium">已导入的音源脚本</h3>
|
||||
<div class="flex gap-2">
|
||||
<n-button @click="importLxMusicScript" size="small" secondary type="success">
|
||||
<template #icon>
|
||||
<n-icon><i class="ri-upload-line"></i></n-icon>
|
||||
</template>
|
||||
本地导入
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已导入的音源列表 -->
|
||||
<div v-if="lxMusicApis.length > 0" class="lx-api-list mb-4">
|
||||
<div
|
||||
v-for="api in lxMusicApis"
|
||||
:key="api.id"
|
||||
class="lx-api-item"
|
||||
:class="{ 'lx-api-item--active': activeLxApiId === api.id }"
|
||||
>
|
||||
<div class="lx-api-item__radio">
|
||||
<n-radio
|
||||
:checked="activeLxApiId === api.id"
|
||||
@update:checked="() => setActiveLxApi(api.id)"
|
||||
/>
|
||||
</div>
|
||||
<div class="lx-api-item__info">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="lx-api-item__name" v-if="editingScriptId !== api.id">{{
|
||||
api.name
|
||||
}}</span>
|
||||
<n-input
|
||||
v-else
|
||||
v-model:value="editingName"
|
||||
size="tiny"
|
||||
class="w-32"
|
||||
ref="renameInputRef"
|
||||
@blur="saveScriptName(api.id)"
|
||||
@keyup.enter="saveScriptName(api.id)"
|
||||
/>
|
||||
|
||||
<n-button
|
||||
v-if="editingScriptId !== api.id"
|
||||
text
|
||||
size="tiny"
|
||||
@click="startRenaming(api)"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon class="text-gray-400 hover:text-primary"
|
||||
><i class="ri-edit-line"></i
|
||||
></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
<span v-if="api.info.version" class="lx-api-item__version"
|
||||
>v{{ api.info.version }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="lx-api-item__actions">
|
||||
<n-button text size="tiny" type="error" @click="removeLxApi(api.id)">
|
||||
<template #icon>
|
||||
<n-icon><i class="ri-close-line"></i></n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<n-empty description="暂无已导入的落雪音源" />
|
||||
</div>
|
||||
|
||||
<!-- URL 导入区域 -->
|
||||
<div class="mt-6">
|
||||
<h4 class="text-sm font-medium mb-2 text-gray-600 dark:text-gray-400">在线导入</h4>
|
||||
<div class="flex items-center gap-2">
|
||||
<n-input
|
||||
v-model:value="lxScriptUrl"
|
||||
placeholder="输入落雪音源脚本 URL"
|
||||
size="small"
|
||||
class="flex-1"
|
||||
:disabled="isImportingFromUrl"
|
||||
/>
|
||||
<n-button
|
||||
@click="importLxMusicScriptFromUrl"
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="isImportingFromUrl"
|
||||
:disabled="!lxScriptUrl.trim()"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon><i class="ri-download-line"></i></n-icon>
|
||||
</template>
|
||||
导入
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="source-card__description">
|
||||
{{
|
||||
settingsStore.setData.customApiPlugin
|
||||
? t('settings.playback.customApi.status.imported')
|
||||
: t('settings.playback.customApi.status.notImported')
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div class="divider"></div>
|
||||
<!-- Tab 3: 自定义API管理 -->
|
||||
<n-tab-pane name="customApi" tab="自定义API" class="h-full overflow-y-auto">
|
||||
<div class="pt-4 flex flex-col items-center justify-center h-full gap-4">
|
||||
<div class="text-center">
|
||||
<h3 class="text-lg font-medium mb-2">
|
||||
{{ t('settings.playback.customApi.sectionTitle') }}
|
||||
</h3>
|
||||
<p class="text-gray-500 text-sm mb-4">导入兼容的自定义 API 插件以扩展音源</p>
|
||||
</div>
|
||||
|
||||
<!-- 自定义API导入区域 -->
|
||||
<div class="custom-api-section">
|
||||
<h3 class="custom-api-section__title">
|
||||
{{ t('settings.playback.customApi.sectionTitle') }}
|
||||
</h3>
|
||||
<div class="custom-api-section__content">
|
||||
<n-button @click="importPlugin" size="small" secondary>
|
||||
<template #icon>
|
||||
<n-icon><i class="ri-upload-line"></i></n-icon>
|
||||
</template>
|
||||
{{ t('settings.playback.customApi.importConfig') }}
|
||||
</n-button>
|
||||
<p v-if="settingsStore.setData.customApiPluginName" class="custom-api-section__status">
|
||||
{{ t('settings.playback.customApi.currentSource') }}:
|
||||
<span class="font-semibold">{{ settingsStore.setData.customApiPluginName }}</span>
|
||||
</p>
|
||||
<p v-else class="custom-api-section__status custom-api-section__status--empty">
|
||||
{{ t('settings.playback.customApi.notImported') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<n-button @click="importPlugin" type="primary" secondary>
|
||||
<template #icon>
|
||||
<n-icon><i class="ri-upload-line"></i></n-icon>
|
||||
</template>
|
||||
{{ t('settings.playback.customApi.importConfig') }}
|
||||
</n-button>
|
||||
|
||||
<p
|
||||
v-if="settingsStore.setData.customApiPluginName"
|
||||
class="text-green-600 text-sm mt-2 flex items-center gap-1"
|
||||
>
|
||||
<i class="ri-check-circle-line"></i>
|
||||
{{ t('settings.playback.customApi.currentSource') }}:
|
||||
<span class="font-semibold">{{ settingsStore.setData.customApiPluginName }}</span>
|
||||
</p>
|
||||
<p v-else class="text-gray-400 text-sm mt-2">
|
||||
{{ t('settings.playback.customApi.notImported') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ref, watch } from 'vue';
|
||||
import { computed, nextTick, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import {
|
||||
initLxMusicRunner,
|
||||
parseScriptInfo,
|
||||
setLxMusicRunner
|
||||
} from '@/services/LxMusicSourceRunner';
|
||||
import { useSettingsStore } from '@/store';
|
||||
import type { LxMusicScriptConfig, LxScriptInfo, LxSourceKey } from '@/types/lxMusic';
|
||||
import { type Platform } from '@/types/music';
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
type ExtendedPlatform = Platform | 'custom';
|
||||
type ExtendedPlatform = Platform | 'custom' | 'lxMusic';
|
||||
|
||||
interface MusicSourceConfig {
|
||||
key: string;
|
||||
@@ -149,6 +304,37 @@ const message = useMessage();
|
||||
const visible = ref(props.show);
|
||||
const selectedSources = ref<ExtendedPlatform[]>([...props.sources]);
|
||||
|
||||
// 落雪音源列表(从 store 中的脚本解析)
|
||||
const lxMusicApis = computed<LxMusicScriptConfig[]>(() => {
|
||||
const scripts = settingsStore.setData.lxMusicScripts || [];
|
||||
return scripts;
|
||||
});
|
||||
|
||||
// 当前激活的音源 ID
|
||||
const activeLxApiId = computed<string | null>({
|
||||
get: () => settingsStore.setData.activeLxMusicApiId || null,
|
||||
set: (id) => {
|
||||
settingsStore.setSetData({ activeLxMusicApiId: id });
|
||||
}
|
||||
});
|
||||
|
||||
// 落雪音源脚本信息(保持向后兼容)
|
||||
const lxMusicScriptInfo = computed<LxScriptInfo | null>(() => {
|
||||
const activeId = activeLxApiId.value;
|
||||
if (!activeId) return null;
|
||||
const activeApi = lxMusicApis.value.find((api) => api.id === activeId);
|
||||
return activeApi?.info || null;
|
||||
});
|
||||
|
||||
// URL 导入相关状态
|
||||
const lxScriptUrl = ref('');
|
||||
const isImportingFromUrl = ref(false);
|
||||
|
||||
// 重命名相关状态
|
||||
const editingScriptId = ref<string | null>(null);
|
||||
const editingName = ref('');
|
||||
const renameInputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
const isSourceSelected = (sourceKey: string): boolean => {
|
||||
return selectedSources.value.includes(sourceKey as ExtendedPlatform);
|
||||
@@ -165,6 +351,12 @@ const toggleSource = (sourceKey: string) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是落雪音源且未导入
|
||||
if (sourceKey === 'lxMusic' && !settingsStore.setData.lxMusicScript) {
|
||||
message.warning('请先导入落雪音源脚本');
|
||||
return;
|
||||
}
|
||||
|
||||
const index = selectedSources.value.indexOf(sourceKey as ExtendedPlatform);
|
||||
if (index > -1) {
|
||||
// 至少保留一个音源
|
||||
@@ -198,6 +390,224 @@ const importPlugin = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 导入落雪音源脚本
|
||||
*/
|
||||
const importLxMusicScript = async () => {
|
||||
try {
|
||||
const result = await window.api.importLxMusicScript();
|
||||
if (result && result.content) {
|
||||
await addLxMusicScript(result.content);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('导入落雪音源脚本失败:', error);
|
||||
message.error(`导入失败:${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加落雪音源脚本到列表
|
||||
*/
|
||||
const addLxMusicScript = async (scriptContent: string) => {
|
||||
// 解析脚本信息
|
||||
const scriptInfo = parseScriptInfo(scriptContent);
|
||||
|
||||
// 尝试初始化执行器以验证脚本
|
||||
try {
|
||||
const runner = await initLxMusicRunner(scriptContent);
|
||||
const sources = runner.getSources();
|
||||
const sourceKeys = Object.keys(sources) as LxSourceKey[];
|
||||
|
||||
// 生成唯一 ID
|
||||
const id = `lx_api_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
// 创建新的脚本配置
|
||||
const newApiConfig: LxMusicScriptConfig = {
|
||||
id,
|
||||
name: scriptInfo.name,
|
||||
script: scriptContent,
|
||||
info: scriptInfo,
|
||||
sources: sourceKeys,
|
||||
enabled: true,
|
||||
createdAt: Date.now()
|
||||
};
|
||||
|
||||
// 添加到列表
|
||||
const scripts = [...(settingsStore.setData.lxMusicScripts || []), newApiConfig];
|
||||
|
||||
settingsStore.setSetData({
|
||||
lxMusicScripts: scripts,
|
||||
activeLxMusicApiId: id // 自动激活新添加的音源
|
||||
});
|
||||
|
||||
message.success(`音源脚本导入成功:${scriptInfo.name},支持 ${sourceKeys.length} 个音源`);
|
||||
|
||||
// 导入成功后自动勾选
|
||||
if (!selectedSources.value.includes('lxMusic')) {
|
||||
selectedSources.value.push('lxMusic');
|
||||
}
|
||||
} catch (initError: any) {
|
||||
console.error('落雪音源脚本初始化失败:', initError);
|
||||
message.error(`脚本初始化失败:${initError.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置激活的落雪音源
|
||||
*/
|
||||
const setActiveLxApi = async (apiId: string) => {
|
||||
const api = lxMusicApis.value.find((a) => a.id === apiId);
|
||||
if (!api) {
|
||||
message.error('音源不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 初始化选中的脚本
|
||||
await initLxMusicRunner(api.script);
|
||||
|
||||
// 更新激活的音源 ID
|
||||
activeLxApiId.value = apiId;
|
||||
|
||||
// 确保 lxMusic 在已选音源中
|
||||
if (!selectedSources.value.includes('lxMusic')) {
|
||||
selectedSources.value.push('lxMusic');
|
||||
}
|
||||
|
||||
message.success(`已切换到音源: ${api.name}`);
|
||||
} catch (error: any) {
|
||||
console.error('切换落雪音源失败:', error);
|
||||
message.error(`切换失败:${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除落雪音源
|
||||
*/
|
||||
const removeLxApi = (apiId: string) => {
|
||||
const scripts = [...(settingsStore.setData.lxMusicScripts || [])];
|
||||
const index = scripts.findIndex((s) => s.id === apiId);
|
||||
|
||||
if (index === -1) return;
|
||||
|
||||
const removedScript = scripts[index];
|
||||
scripts.splice(index, 1);
|
||||
|
||||
// 更新 store
|
||||
settingsStore.setSetData({
|
||||
lxMusicScripts: scripts
|
||||
});
|
||||
|
||||
// 如果删除的是当前激活的音源
|
||||
if (activeLxApiId.value === apiId) {
|
||||
// 自动选择下一个可用音源,或者清空
|
||||
if (scripts.length > 0) {
|
||||
setActiveLxApi(scripts[0].id);
|
||||
} else {
|
||||
setLxMusicRunner(null);
|
||||
settingsStore.setSetData({ activeLxMusicApiId: null });
|
||||
// 从已选音源中移除 lxMusic
|
||||
const srcIndex = selectedSources.value.indexOf('lxMusic');
|
||||
if (srcIndex > -1) {
|
||||
selectedSources.value.splice(srcIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.success(`已删除音源: ${removedScript.name}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 从 URL 导入落雪音源脚本
|
||||
*/
|
||||
const importLxMusicScriptFromUrl = async () => {
|
||||
const url = lxScriptUrl.value.trim();
|
||||
if (!url) {
|
||||
message.warning('请输入脚本 URL');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证 URL 格式
|
||||
try {
|
||||
new URL(url);
|
||||
} catch {
|
||||
message.error('无效的 URL 格式');
|
||||
return;
|
||||
}
|
||||
|
||||
isImportingFromUrl.value = true;
|
||||
|
||||
try {
|
||||
// 下载脚本内容
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const content = await response.text();
|
||||
|
||||
// 验证脚本格式
|
||||
if (
|
||||
!content.includes('globalThis.lx') &&
|
||||
!content.includes('lx.on') &&
|
||||
!content.includes('EVENT_NAMES')
|
||||
) {
|
||||
throw new Error('无效的落雪音源脚本,未找到 globalThis.lx 相关代码');
|
||||
}
|
||||
|
||||
// 使用统一的添加方法
|
||||
await addLxMusicScript(content);
|
||||
|
||||
// 清空 URL 输入框
|
||||
lxScriptUrl.value = '';
|
||||
} catch (error: any) {
|
||||
console.error('从 URL 导入落雪音源脚本失败:', error);
|
||||
message.error(`在线导入失败:${error.message}`);
|
||||
} finally {
|
||||
isImportingFromUrl.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 开始重命名
|
||||
*/
|
||||
const startRenaming = (api: LxMusicScriptConfig) => {
|
||||
editingScriptId.value = api.id;
|
||||
editingName.value = api.name;
|
||||
nextTick(() => {
|
||||
renameInputRef.value?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存脚本名称
|
||||
*/
|
||||
const saveScriptName = (apiId: string) => {
|
||||
if (!editingName.value.trim()) {
|
||||
message.warning('名称不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
const scripts = [...(settingsStore.setData.lxMusicScripts || [])];
|
||||
const index = scripts.findIndex((s) => s.id === apiId);
|
||||
|
||||
if (index > -1) {
|
||||
scripts[index] = {
|
||||
...scripts[index],
|
||||
name: editingName.value.trim()
|
||||
};
|
||||
|
||||
settingsStore.setSetData({
|
||||
lxMusicScripts: scripts
|
||||
});
|
||||
|
||||
message.success('重命名成功');
|
||||
}
|
||||
|
||||
editingScriptId.value = null;
|
||||
editingName.value = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* 确认选择
|
||||
*/
|
||||
@@ -392,52 +802,83 @@ watch(
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #e5e5e5 50%, transparent);
|
||||
margin: 8px 0;
|
||||
.lx-api-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:global(.dark) .divider {
|
||||
background: linear-gradient(90deg, transparent, #333 50%, transparent);
|
||||
}
|
||||
.lx-api-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.custom-api-section {
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
&--active {
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.08), rgba(59, 130, 246, 0.08));
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
&__content {
|
||||
&__radio {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__status {
|
||||
&__name {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&--empty {
|
||||
color: #999;
|
||||
&__version {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover &__actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.dark) {
|
||||
.lx-api-item {
|
||||
background: #2a2a2a;
|
||||
|
||||
&__name {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
&__version {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.dark) .custom-api-section {
|
||||
&__title {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
&__status {
|
||||
color: #999;
|
||||
|
||||
&--empty {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
.empty-state {
|
||||
padding: 32px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user