mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 02:07:29 +08:00
feat: 一系列播放优化
This commit is contained in:
@@ -76,7 +76,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { CacheManager } from '@/api/musicParser';
|
||||
@@ -96,7 +96,7 @@ const currentReparsingSource = ref<Platform | null>(null);
|
||||
// 实际存储选中音源的值
|
||||
const selectedSourcesValue = ref<Platform[]>([]);
|
||||
|
||||
const isReparse = ref(localStorage.getItem(`song_source_${String(playMusic.value.id)}`) !== null);
|
||||
const isReparse = computed(() => selectedSourcesValue.value.length > 0);
|
||||
|
||||
// 可选音源列表
|
||||
const musicSourceOptions = ref([
|
||||
@@ -121,7 +121,8 @@ const getSourceIcon = (source: Platform) => {
|
||||
joox: 'ri-disc-fill',
|
||||
pyncmd: 'ri-netease-cloud-music-fill',
|
||||
bilibili: 'ri-bilibili-fill',
|
||||
gdmusic: 'ri-google-fill'
|
||||
gdmusic: 'ri-google-fill',
|
||||
kuwo: 'ri-music-fill'
|
||||
};
|
||||
|
||||
return iconMap[source] || 'ri-music-2-fill';
|
||||
@@ -148,7 +149,6 @@ const initSelectedSources = () => {
|
||||
const clearCustomSource = () => {
|
||||
localStorage.removeItem(`song_source_${String(playMusic.value.id)}`);
|
||||
selectedSourcesValue.value = [];
|
||||
isReparse.value = false;
|
||||
};
|
||||
|
||||
// 直接重新解析当前歌曲
|
||||
@@ -174,7 +174,7 @@ const directReparseMusic = async (source: Platform) => {
|
||||
JSON.stringify(selectedSourcesValue.value)
|
||||
);
|
||||
|
||||
const success = await playerStore.reparseCurrentSong(source);
|
||||
const success = await playerStore.reparseCurrentSong(source, false);
|
||||
|
||||
if (success) {
|
||||
message.success(t('player.reparse.success'));
|
||||
@@ -221,7 +221,10 @@ watch(
|
||||
console.log('URL已过期,自动应用自定义音源重新加载');
|
||||
try {
|
||||
isReparsing.value = true;
|
||||
const success = await playerStore.reparseCurrentSong(sources[0]);
|
||||
const songId = String(playMusic.value.id);
|
||||
const sourceType = localStorage.getItem(`song_source_type_${songId}`);
|
||||
const isAuto = sourceType === 'auto';
|
||||
const success = await playerStore.reparseCurrentSong(sources[0], isAuto);
|
||||
if (!success) {
|
||||
message.error(t('player.reparse.failed'));
|
||||
}
|
||||
|
||||
@@ -4,14 +4,19 @@
|
||||
<!-- 顶部进度条和时间 -->
|
||||
<div class="top-section">
|
||||
<!-- 进度条 -->
|
||||
<div class="progress-bar" @click="handleProgressClick">
|
||||
<div
|
||||
class="progress-bar"
|
||||
:class="{ 'is-dragging': isDragging }"
|
||||
@mousedown="handleProgressMouseDown"
|
||||
@click.stop="handleProgressClick"
|
||||
>
|
||||
<div class="progress-track"></div>
|
||||
<div class="progress-fill" :style="{ width: `${(nowTime / allTime) * 100}%` }"></div>
|
||||
<div class="progress-fill" :style="{ width: `${progressPercentage}%` }"></div>
|
||||
</div>
|
||||
|
||||
<!-- 时间显示 -->
|
||||
<div class="time-display">
|
||||
<span class="current-time">{{ formatTime(nowTime) }}</span>
|
||||
<span class="current-time">{{ formatTime(displayTime) }}</span>
|
||||
<span class="total-time">{{ formatTime(allTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,11 +155,81 @@ const playMusicEvent = async () => {
|
||||
};
|
||||
|
||||
// 进度条控制
|
||||
const isDragging = ref(false);
|
||||
const dragProgress = ref(0); // 拖拽时的预览进度 (0-100)
|
||||
|
||||
// 计算当前显示的进度百分比
|
||||
const progressPercentage = computed(() => {
|
||||
if (isDragging.value) {
|
||||
return dragProgress.value;
|
||||
}
|
||||
if (allTime.value === 0) return 0;
|
||||
return (nowTime.value / allTime.value) * 100;
|
||||
});
|
||||
|
||||
// 计算显示的时间
|
||||
const displayTime = computed(() => {
|
||||
if (isDragging.value) {
|
||||
return (dragProgress.value / 100) * allTime.value;
|
||||
}
|
||||
return nowTime.value;
|
||||
});
|
||||
|
||||
// 计算进度百分比的辅助函数
|
||||
const calculateProgress = (clientX: number, element: HTMLElement): number => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
||||
return percent * 100;
|
||||
};
|
||||
|
||||
// 更新音频进度
|
||||
const seekToProgress = (percentage: number) => {
|
||||
const targetTime = (percentage / 100) * allTime.value;
|
||||
audioService.seek(targetTime);
|
||||
// 不立即更新 nowTime,让音频服务的回调来更新,避免不同步
|
||||
};
|
||||
|
||||
// 鼠标按下开始拖拽
|
||||
const handleProgressMouseDown = (e: MouseEvent) => {
|
||||
if (e.button !== 0) return; // 只响应左键
|
||||
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
isDragging.value = true;
|
||||
dragProgress.value = calculateProgress(e.clientX, target);
|
||||
|
||||
// 添加全局鼠标移动和释放监听
|
||||
const handleMouseMove = (moveEvent: MouseEvent) => {
|
||||
if (isDragging.value) {
|
||||
dragProgress.value = calculateProgress(moveEvent.clientX, target);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (isDragging.value) {
|
||||
// 拖拽结束,执行跳转
|
||||
seekToProgress(dragProgress.value);
|
||||
isDragging.value = false;
|
||||
}
|
||||
// 移除事件监听
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
|
||||
// 防止文本选择
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// 点击进度条跳转
|
||||
const handleProgressClick = (e: MouseEvent) => {
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
const percent = (e.clientX - rect.left) / rect.width;
|
||||
audioService.seek(allTime.value * percent);
|
||||
nowTime.value = allTime.value * percent;
|
||||
// 如果正在拖拽,不处理点击事件
|
||||
if (isDragging.value) return;
|
||||
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const percentage = calculateProgress(e.clientX, target);
|
||||
seekToProgress(percentage);
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
@@ -348,6 +423,7 @@ onMounted(() => {
|
||||
|
||||
.progress-bar {
|
||||
@apply relative cursor-pointer h-2 mb-2 w-full;
|
||||
user-select: none;
|
||||
|
||||
.progress-track {
|
||||
@apply absolute inset-0 rounded-full transition-all duration-150;
|
||||
@@ -364,10 +440,6 @@ onMounted(() => {
|
||||
.progress-track {
|
||||
background-color: var(--track-color-hover);
|
||||
}
|
||||
.progress-track,
|
||||
.progress-fill {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
box-shadow: 0 0 12px var(--fill-color-transparent);
|
||||
|
||||
@@ -5,63 +5,94 @@
|
||||
:title="t('settings.playback.musicSources')"
|
||||
:positive-text="t('common.confirm')"
|
||||
:negative-text="t('common.cancel')"
|
||||
class="music-source-modal"
|
||||
@positive-click="handleConfirm"
|
||||
@negative-click="handleCancel"
|
||||
style="width: 800px; max-width: 90vw"
|
||||
>
|
||||
<n-space vertical>
|
||||
<p>{{ t('settings.playback.musicSourcesDesc') }}</p>
|
||||
<n-space vertical :size="20">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ t('settings.playback.musicSourcesDesc') }}
|
||||
</p>
|
||||
|
||||
<n-checkbox-group v-model:value="selectedSources">
|
||||
<n-grid :cols="2" :x-gap="12" :y-gap="8">
|
||||
<!-- 遍历常规音源 -->
|
||||
<n-grid-item v-for="source in regularMusicSources" :key="source.value">
|
||||
<n-checkbox :value="source.value">
|
||||
{{ t('settings.playback.sourceLabels.' + source.value) }}
|
||||
<n-tooltip v-if="source.value === 'gdmusic'">
|
||||
<template #trigger>
|
||||
<n-icon size="16" class="ml-1 text-blue-500 cursor-help">
|
||||
<i class="ri-information-line"></i>
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ t('settings.playback.gdmusicInfo') }}
|
||||
</n-tooltip>
|
||||
</n-checkbox>
|
||||
</n-grid-item>
|
||||
<!-- 音源卡片列表 -->
|
||||
<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>
|
||||
|
||||
<!-- 单独处理自定义API选项 -->
|
||||
<n-grid-item>
|
||||
<n-checkbox value="custom" :disabled="!settingsStore.setData.customApiPlugin">
|
||||
{{ t('settings.playback.sourceLabels.custom') }}
|
||||
<n-tooltip v-if="!settingsStore.setData.customApiPlugin">
|
||||
<template #trigger>
|
||||
<n-icon size="16" class="ml-1 text-gray-400 cursor-help">
|
||||
<i class="ri-question-line"></i>
|
||||
</n-icon>
|
||||
</template>
|
||||
{{ t('settings.playback.customApi.enableHint') }}
|
||||
</n-tooltip>
|
||||
</n-checkbox>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-checkbox-group>
|
||||
<!-- 自定义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>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div class="mt-4 border-t pt-4 border-gray-200 dark:border-gray-700"></div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 自定义API导入区域 -->
|
||||
<div>
|
||||
<h3 class="text-base font-medium mb-2">
|
||||
<div class="custom-api-section">
|
||||
<h3 class="custom-api-section__title">
|
||||
{{ t('settings.playback.customApi.sectionTitle') }}
|
||||
</h3>
|
||||
<div class="flex items-center gap-4">
|
||||
<n-button @click="importPlugin" size="small">{{
|
||||
t('settings.playback.customApi.importConfig')
|
||||
}}</n-button>
|
||||
<p v-if="settingsStore.setData.customApiPluginName" class="text-sm">
|
||||
<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="text-sm text-gray-500">
|
||||
<p v-else class="custom-api-section__status custom-api-section__status--empty">
|
||||
{{ t('settings.playback.customApi.notImported') }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -78,9 +109,26 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useSettingsStore } from '@/store';
|
||||
import { type Platform } from '@/types/music';
|
||||
|
||||
// 扩展 Platform 类型以包含 'custom'
|
||||
// ==================== 类型定义 ====================
|
||||
type ExtendedPlatform = Platform | 'custom';
|
||||
|
||||
interface MusicSourceConfig {
|
||||
key: string;
|
||||
description?: string;
|
||||
color: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// ==================== 音源配置 ====================
|
||||
const MUSIC_SOURCES: MusicSourceConfig[] = [
|
||||
{ key: 'migu', color: '#ff6600' },
|
||||
{ key: 'kugou', color: '#2979ff' },
|
||||
{ key: 'kuwo', color: '#ff8c00' },
|
||||
{ key: 'pyncmd', color: '#ec4141' },
|
||||
{ key: 'bilibili', color: '#00a1d6' }
|
||||
];
|
||||
|
||||
// ==================== Props & Emits ====================
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
@@ -88,34 +136,59 @@ const props = defineProps({
|
||||
},
|
||||
sources: {
|
||||
type: Array as () => ExtendedPlatform[],
|
||||
default: () => ['migu', 'kugou', 'pyncmd', 'bilibili']
|
||||
default: () => ['migu', 'kugou', 'kuwo', 'pyncmd', 'bilibili'] as ExtendedPlatform[]
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:show', 'update:sources']);
|
||||
|
||||
// ==================== 状态管理 ====================
|
||||
const { t } = useI18n();
|
||||
const settingsStore = useSettingsStore();
|
||||
const message = useMessage();
|
||||
const visible = ref(props.show);
|
||||
const selectedSources = ref<ExtendedPlatform[]>(props.sources);
|
||||
const selectedSources = ref<ExtendedPlatform[]>([...props.sources]);
|
||||
|
||||
// 将常规音源和自定义音源分开定义
|
||||
const regularMusicSources = ref([
|
||||
{ value: 'migu' },
|
||||
{ value: 'kugou' },
|
||||
{ value: 'pyncmd' },
|
||||
{ value: 'bilibili' },
|
||||
{ value: 'gdmusic' }
|
||||
]);
|
||||
// ==================== 计算属性 ====================
|
||||
const isSourceSelected = (sourceKey: string): boolean => {
|
||||
return selectedSources.value.includes(sourceKey as ExtendedPlatform);
|
||||
};
|
||||
|
||||
// ==================== 方法 ====================
|
||||
/**
|
||||
* 切换音源选择状态
|
||||
*/
|
||||
const toggleSource = (sourceKey: string) => {
|
||||
// 检查是否是自定义API且未导入
|
||||
if (sourceKey === 'custom' && !settingsStore.setData.customApiPlugin) {
|
||||
message.warning(t('settings.playback.customApi.enableHint'));
|
||||
return;
|
||||
}
|
||||
|
||||
const index = selectedSources.value.indexOf(sourceKey as ExtendedPlatform);
|
||||
if (index > -1) {
|
||||
// 至少保留一个音源
|
||||
if (selectedSources.value.length <= 1) {
|
||||
message.warning(t('settings.playback.musicSourcesMinWarning'));
|
||||
return;
|
||||
}
|
||||
selectedSources.value.splice(index, 1);
|
||||
} else {
|
||||
selectedSources.value.push(sourceKey as ExtendedPlatform);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 导入自定义API插件
|
||||
*/
|
||||
const importPlugin = async () => {
|
||||
try {
|
||||
const result = await window.api.importCustomApiPlugin();
|
||||
if (result && result.name && result.content) {
|
||||
settingsStore.setCustomApiPlugin(result);
|
||||
message.success(t('settings.playback.customApi.importSuccess', { name: result.name }));
|
||||
// 导入成功后,如果用户还没勾选,则自动勾选上
|
||||
|
||||
// 导入成功后自动勾选
|
||||
if (!selectedSources.value.includes('custom')) {
|
||||
selectedSources.value.push('custom');
|
||||
}
|
||||
@@ -125,7 +198,27 @@ const importPlugin = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 监听自定义插件内容的变化。如果用户清除了插件,要确保 'custom' 选项被取消勾选
|
||||
/**
|
||||
* 确认选择
|
||||
*/
|
||||
const handleConfirm = () => {
|
||||
const defaultPlatforms: ExtendedPlatform[] = ['migu', 'kugou', 'kuwo', 'pyncmd', 'bilibili'];
|
||||
const valuesToEmit =
|
||||
selectedSources.value.length > 0 ? [...new Set(selectedSources.value)] : defaultPlatforms;
|
||||
emit('update:sources', valuesToEmit);
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消选择
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
selectedSources.value = [...props.sources];
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
// ==================== 监听器 ====================
|
||||
// 监听自定义插件内容变化
|
||||
watch(
|
||||
() => settingsStore.setData.customApiPlugin,
|
||||
(newPluginContent) => {
|
||||
@@ -162,18 +255,189 @@ watch(
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const handleConfirm = () => {
|
||||
// 确保至少选择一个音源
|
||||
const defaultPlatforms = ['migu', 'kugou', 'pyncmd', 'bilibili'];
|
||||
const valuesToEmit =
|
||||
selectedSources.value.length > 0 ? [...new Set(selectedSources.value)] : defaultPlatforms;
|
||||
emit('update:sources', valuesToEmit);
|
||||
visible.value = false;
|
||||
};
|
||||
const handleCancel = () => {
|
||||
// 取消时还原为props传入的初始值
|
||||
selectedSources.value = [...props.sources];
|
||||
visible.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.music-sources-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.source-card {
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border: 2px solid transparent;
|
||||
background:
|
||||
linear-gradient(white, white) padding-box,
|
||||
linear-gradient(135deg, var(--source-color, #ddd) 0%, transparent 100%) border-box;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--source-color);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
&__indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: var(--source-color);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
&__check {
|
||||
color: var(--source-color);
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin: 0;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--source-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&--selected {
|
||||
border-color: var(--source-color);
|
||||
background:
|
||||
linear-gradient(white, white) padding-box,
|
||||
var(--source-color) border-box;
|
||||
|
||||
.source-card__indicator {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.source-card__check {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
opacity: 0.05;
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 深色模式适配
|
||||
:global(.dark) {
|
||||
.source-card {
|
||||
background:
|
||||
linear-gradient(#1f1f1f, #1f1f1f) padding-box,
|
||||
linear-gradient(135deg, var(--source-color, #555) 0%, transparent 100%) border-box;
|
||||
|
||||
&__name {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
background:
|
||||
linear-gradient(#1f1f1f, #1f1f1f) padding-box,
|
||||
var(--source-color) border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #e5e5e5 50%, transparent);
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
:global(.dark) .divider {
|
||||
background: linear-gradient(90deg, transparent, #333 50%, transparent);
|
||||
}
|
||||
|
||||
.custom-api-section {
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&__status {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
|
||||
&--empty {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.dark) .custom-api-section {
|
||||
&__title {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
&__status {
|
||||
color: #999;
|
||||
|
||||
&--empty {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user