mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-12 02:21:06 +08:00
优化样式以支持动态颜色显示。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moviepilot",
|
||||
"version": "2.6.8",
|
||||
"version": "2.6.9",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": "dist/service.js",
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useDynamicButton } from '@/composables/useDynamicButton'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { VCardActions } from 'vuetify/components'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
import { getItemColor, initializeItemColors } from '@/utils/colorUtils'
|
||||
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
@@ -161,6 +162,18 @@ const pluginDashboardRefreshStatus = ref<{ [key: string]: boolean }>({})
|
||||
// 弹窗
|
||||
const dialog = ref(false)
|
||||
|
||||
// 为每个项目生成随机颜色
|
||||
const itemColors = ref<{ [key: string]: string }>({})
|
||||
|
||||
// 初始化颜色
|
||||
function initializeColors() {
|
||||
initializeItemColors(dashboardConfigs.value, item => buildPluginDashboardId(item.id, item.key))
|
||||
dashboardConfigs.value.forEach(item => {
|
||||
const itemId = buildPluginDashboardId(item.id, item.key)
|
||||
itemColors.value[itemId] = getItemColor(itemId)
|
||||
})
|
||||
}
|
||||
|
||||
// 使用动态按钮钩子
|
||||
useDynamicButton({
|
||||
icon: 'mdi-view-dashboard-edit',
|
||||
@@ -286,6 +299,11 @@ async function getPluginDashboard(id: string, key: string) {
|
||||
dashboardConfigs.value[index] = res
|
||||
} else {
|
||||
dashboardConfigs.value.push(res)
|
||||
// 为新增的插件仪表板生成颜色
|
||||
const pluginDashboardId = buildPluginDashboardId(id, key)
|
||||
if (!itemColors.value[pluginDashboardId]) {
|
||||
itemColors.value[pluginDashboardId] = getItemColor(pluginDashboardId)
|
||||
}
|
||||
// 排序
|
||||
sortDashboardConfigs()
|
||||
}
|
||||
@@ -322,6 +340,7 @@ function dragOrderEnd() {
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await loadDashboardConfig()
|
||||
initializeColors()
|
||||
getPluginDashboardMeta()
|
||||
})
|
||||
|
||||
@@ -390,6 +409,7 @@ onDeactivated(() => {
|
||||
:class="{
|
||||
'enabled': enableConfig[buildPluginDashboardId(item.id, item.key)],
|
||||
}"
|
||||
:style="{ '--item-color': itemColors[buildPluginDashboardId(item.id, item.key)] }"
|
||||
@click="
|
||||
enableConfig[buildPluginDashboardId(item.id, item.key)] =
|
||||
!enableConfig[buildPluginDashboardId(item.id, item.key)]
|
||||
@@ -444,8 +464,11 @@ onDeactivated(() => {
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
flex: 1;
|
||||
color: rgba(var(--v-theme-on-surface), 0.8);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
@@ -462,7 +485,7 @@ onDeactivated(() => {
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
background-color: var(--item-color, #4caf50);
|
||||
block-size: 100%;
|
||||
content: '';
|
||||
inline-size: 4px;
|
||||
@@ -472,16 +495,15 @@ onDeactivated(() => {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(var(--v-theme-on-surface), 0.15);
|
||||
background-color: rgba(var(--v-theme-surface-variant), 0.6);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&.enabled {
|
||||
border-color: rgba(var(--v-theme-primary), 0.5);
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
border-color: rgba(var(--v-theme-primary), 0.3);
|
||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||
|
||||
.setting-label {
|
||||
color: rgb(var(--v-theme-primary));
|
||||
color: rgba(var(--v-theme-primary), 0.9);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -490,9 +512,16 @@ onDeactivated(() => {
|
||||
.setting-item-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.setting-check {
|
||||
margin-inline-end: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.settings-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,7 @@ import api from '@/api'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
|
||||
import { getItemColor, initializeItemColors } from '@/utils/colorUtils'
|
||||
|
||||
const display = useDisplay()
|
||||
|
||||
@@ -44,6 +45,17 @@ const extraDiscoverSources = ref<DiscoverSource[]>([])
|
||||
// 排序对话框
|
||||
const orderConfigDialog = ref(false)
|
||||
|
||||
// 为每个项目生成随机颜色
|
||||
const itemColors = ref<{ [key: string]: string }>({})
|
||||
|
||||
// 初始化颜色
|
||||
function initializeColors() {
|
||||
initializeItemColors(discoverTabs.value, item => item.mediaid_prefix)
|
||||
discoverTabs.value.forEach(item => {
|
||||
itemColors.value[item.mediaid_prefix] = getItemColor(item.mediaid_prefix)
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化发现标签
|
||||
function initDiscoverTabs() {
|
||||
const tabs = getDiscoverTabs()
|
||||
@@ -70,6 +82,10 @@ async function loadExtraDiscoverSources() {
|
||||
continue
|
||||
}
|
||||
discoverTabs.value.push(source)
|
||||
// 为新增的数据源生成颜色
|
||||
if (!itemColors.value[source.mediaid_prefix]) {
|
||||
itemColors.value[source.mediaid_prefix] = getItemColor(source.mediaid_prefix)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
@@ -145,6 +161,7 @@ registerHeaderTab({
|
||||
|
||||
onBeforeMount(async () => {
|
||||
initDiscoverTabs()
|
||||
initializeColors()
|
||||
await loadOrderConfig()
|
||||
await loadExtraDiscoverSources()
|
||||
sortSubscribeOrder()
|
||||
@@ -225,9 +242,14 @@ onActivated(async () => {
|
||||
:component-data="{ 'class': 'settings-grid' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<VCard variant="text" class="setting-item enabled">
|
||||
<div class="setting-item-inner cursor-move text-center">
|
||||
<VCard
|
||||
variant="text"
|
||||
class="setting-item enabled"
|
||||
:style="{ '--item-color': itemColors[element.mediaid_prefix] }"
|
||||
>
|
||||
<div class="setting-item-inner">
|
||||
<span class="setting-label">{{ element.name }}</span>
|
||||
<VIcon icon="mdi-drag" class="drag-icon cursor-move" />
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -269,8 +291,11 @@ onActivated(async () => {
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
flex: 1;
|
||||
color: rgba(var(--v-theme-on-surface), 0.8);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
@@ -287,8 +312,7 @@ onActivated(async () => {
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
background-color: var(--item-color, #4caf50);
|
||||
block-size: 100%;
|
||||
content: '';
|
||||
inline-size: 4px;
|
||||
@@ -298,16 +322,15 @@ onActivated(async () => {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(var(--v-theme-on-surface), 0.15);
|
||||
background-color: rgba(var(--v-theme-surface-variant), 0.6);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&.enabled {
|
||||
border-color: rgba(var(--v-theme-primary), 0.5);
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
border-color: rgba(var(--v-theme-primary), 0.3);
|
||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||
|
||||
.setting-label {
|
||||
color: rgb(var(--v-theme-primary));
|
||||
color: rgba(var(--v-theme-primary), 0.9);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -316,9 +339,22 @@ onActivated(async () => {
|
||||
.setting-item-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.setting-check {
|
||||
margin-inline-end: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drag-icon {
|
||||
flex-shrink: 0;
|
||||
color: rgba(var(--v-theme-on-surface), 0.5);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.settings-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
|
||||
import { getItemColor, initializeItemColors } from '@/utils/colorUtils'
|
||||
|
||||
const display = useDisplay()
|
||||
|
||||
@@ -114,6 +115,17 @@ const enableConfig = ref<{ [key: string]: boolean }>({
|
||||
...Object.fromEntries(viewList.map(item => [item.title, true])),
|
||||
})
|
||||
|
||||
// 为每个项目生成随机颜色
|
||||
const itemColors = ref<{ [key: string]: string }>({})
|
||||
|
||||
// 初始化颜色
|
||||
function initializeColors() {
|
||||
initializeItemColors(viewList, item => item.title)
|
||||
viewList.forEach(item => {
|
||||
itemColors.value[item.title] = getItemColor(item.title)
|
||||
})
|
||||
}
|
||||
|
||||
// 弹窗
|
||||
const dialog = ref(false)
|
||||
|
||||
@@ -127,8 +139,8 @@ async function loadExtraRecommendSources() {
|
||||
if (extraRecommendSources.value.length > 0) {
|
||||
extraRecommendSources.value.map(source => {
|
||||
if (!viewList.some(item => item.apipath === source.api_path)) {
|
||||
const querySeparator = source.api_path.includes('?') ? '&' : '?';
|
||||
const linkUrl = `/browse/${source.api_path}${querySeparator}title=${encodeURIComponent(source.name)}`;
|
||||
const querySeparator = source.api_path.includes('?') ? '&' : '?'
|
||||
const linkUrl = `/browse/${source.api_path}${querySeparator}title=${encodeURIComponent(source.name)}`
|
||||
viewList.push({
|
||||
apipath: source.api_path,
|
||||
linkurl: linkUrl,
|
||||
@@ -221,10 +233,17 @@ registerHeaderTab({
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await loadConfig()
|
||||
initializeColors()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await loadExtraRecommendSources()
|
||||
// 为新增的数据源也生成颜色
|
||||
extraRecommendSources.value.forEach(source => {
|
||||
if (!itemColors.value[source.name]) {
|
||||
itemColors.value[source.name] = getItemColor(source.name)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
onActivated(async () => {
|
||||
@@ -275,8 +294,8 @@ onActivated(async () => {
|
||||
class="setting-item"
|
||||
:class="{
|
||||
'enabled': enableConfig[item.title],
|
||||
[item.type]: true,
|
||||
}"
|
||||
:style="{ '--item-color': itemColors[item.title] }"
|
||||
@click="enableConfig[item.title] = !enableConfig[item.title]"
|
||||
>
|
||||
<div class="setting-item-inner">
|
||||
@@ -394,7 +413,7 @@ onActivated(async () => {
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
background-color: transparent;
|
||||
background-color: var(--item-color, #4caf50);
|
||||
block-size: 100%;
|
||||
content: '';
|
||||
inline-size: 4px;
|
||||
@@ -403,19 +422,6 @@ onActivated(async () => {
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
&.电影::before {
|
||||
background-color: #4caf50;
|
||||
} // Green
|
||||
&.电视剧::before {
|
||||
background-color: #2196f3;
|
||||
} // Blue
|
||||
&.动漫::before {
|
||||
background-color: #ff9800;
|
||||
} // Orange
|
||||
&.排行榜::before {
|
||||
background-color: #9c27b0;
|
||||
} // Purple
|
||||
|
||||
&.enabled {
|
||||
border-color: rgba(var(--v-theme-primary), 0.3);
|
||||
background-color: rgba(var(--v-theme-primary), 0.1);
|
||||
@@ -452,7 +458,7 @@ onActivated(async () => {
|
||||
|
||||
@media (width <= 600px) {
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
137
src/utils/colorUtils.ts
Normal file
137
src/utils/colorUtils.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
// 预定义的颜色数组,包含更多丰富的颜色选项
|
||||
const COLORS = [
|
||||
// 基础颜色
|
||||
'#4caf50', // 绿色
|
||||
'#2196f3', // 蓝色
|
||||
'#ff9800', // 橙色
|
||||
'#9c27b0', // 紫色
|
||||
'#f44336', // 红色
|
||||
'#00bcd4', // 青色
|
||||
'#8bc34a', // 浅绿色
|
||||
'#ff5722', // 深橙色
|
||||
'#3f51b5', // 靛蓝色
|
||||
'#009688', // 青绿色
|
||||
'#e91e63', // 粉红色
|
||||
'#673ab7', // 深紫色
|
||||
'#ffc107', // 琥珀色
|
||||
'#795548', // 棕色
|
||||
'#607d8b', // 蓝灰色
|
||||
|
||||
// 扩展颜色
|
||||
'#ff4081', // 深粉红色
|
||||
'#00e676', // 浅绿色
|
||||
'#ff6f00', // 深橙色
|
||||
'#4fc3f7', // 浅蓝色
|
||||
'#ba68c8', // 浅紫色
|
||||
'#81c784', // 浅绿色
|
||||
'#ffb74d', // 浅橙色
|
||||
'#64b5f6', // 浅蓝色
|
||||
'#f06292', // 浅粉红色
|
||||
'#4db6ac', // 浅青绿色
|
||||
'#aed581', // 浅绿色
|
||||
'#ffd54f', // 浅黄色
|
||||
'#7986cb', // 浅靛蓝色
|
||||
'#4dd0e1', // 浅青色
|
||||
'#ff8a65', // 浅红色
|
||||
'#9575cd', // 浅紫色
|
||||
'#4fc3f7', // 天蓝色
|
||||
'#ffcc02', // 金黄色
|
||||
'#7cb342', // 浅绿色
|
||||
'#42a5f5', // 蓝色
|
||||
'#ab47bc', // 紫色
|
||||
'#26a69a', // 青绿色
|
||||
'#66bb6a', // 绿色
|
||||
'#ff7043', // 深橙色
|
||||
'#29b6f6', // 浅蓝色
|
||||
'#7e57c2', // 紫色
|
||||
'#26c6da', // 青色
|
||||
'#9ccc65', // 浅绿色
|
||||
'#ffb300', // 琥珀色
|
||||
'#8d6e63', // 棕色
|
||||
'#78909c', // 蓝灰色
|
||||
'#ef5350', // 红色
|
||||
'#ec407a', // 粉红色
|
||||
'#ab47bc', // 紫色
|
||||
'#42a5f5', // 蓝色
|
||||
'#7cb342', // 绿色
|
||||
'#ffa726', // 橙色
|
||||
'#26c6da', // 青色
|
||||
'#d4e157', // 浅绿色
|
||||
'#ffca28', // 黄色
|
||||
'#9fa8da', // 浅靛蓝色
|
||||
'#80cbc4', // 浅青绿色
|
||||
'#c5e1a5', // 浅绿色
|
||||
'#ffe082', // 浅黄色
|
||||
'#b39ddb', // 浅紫色
|
||||
'#90caf9', // 浅蓝色
|
||||
'#a5d6a7', // 浅绿色
|
||||
'#ffcc80', // 浅橙色
|
||||
'#b2dfdb', // 浅青绿色
|
||||
'#f8bbd9', // 浅粉红色
|
||||
'#c8e6c9', // 浅绿色
|
||||
'#fff9c4', // 浅黄色
|
||||
'#d1c4e9', // 浅紫色
|
||||
'#bbdefb', // 浅蓝色
|
||||
'#c8e6c9', // 浅绿色
|
||||
'#ffecb3', // 浅琥珀色
|
||||
'#d7ccc8', // 浅棕色
|
||||
'#cfd8dc', // 浅蓝灰色
|
||||
]
|
||||
|
||||
// 颜色缓存,确保同一项目总是获得相同颜色
|
||||
const colorCache = new Map<string, string>()
|
||||
|
||||
/**
|
||||
* 生成随机颜色
|
||||
* @returns 随机颜色值
|
||||
*/
|
||||
export function generateRandomColor(): string {
|
||||
return COLORS[Math.floor(Math.random() * COLORS.length)]
|
||||
}
|
||||
|
||||
/**
|
||||
* 为指定项目获取或生成颜色
|
||||
* @param itemKey 项目的唯一标识
|
||||
* @returns 颜色值
|
||||
*/
|
||||
export function getItemColor(itemKey: string): string {
|
||||
if (!colorCache.has(itemKey)) {
|
||||
colorCache.set(itemKey, generateRandomColor())
|
||||
}
|
||||
return colorCache.get(itemKey)!
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化项目颜色
|
||||
* @param items 项目数组
|
||||
* @param keyExtractor 从项目中提取唯一键的函数
|
||||
*/
|
||||
export function initializeItemColors<T>(items: T[], keyExtractor: (item: T) => string): void {
|
||||
items.forEach(item => {
|
||||
const key = keyExtractor(item)
|
||||
getItemColor(key) // 这会自动缓存颜色
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除颜色缓存
|
||||
*/
|
||||
export function clearColorCache(): void {
|
||||
colorCache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有预定义颜色
|
||||
* @returns 颜色数组
|
||||
*/
|
||||
export function getAllColors(): string[] {
|
||||
return [...COLORS]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取颜色总数
|
||||
* @returns 颜色数量
|
||||
*/
|
||||
export function getColorCount(): number {
|
||||
return COLORS.length
|
||||
}
|
||||
Reference in New Issue
Block a user