mirror of
https://github.com/fankes/komari-theme-purcarte.git
synced 2025-10-18 19:39:22 +08:00
feat: 添加状态卡片显示控制功能
This commit is contained in:
@@ -70,10 +70,11 @@
|
||||
| 磨砂玻璃背景色 | `blurBackgroundColor` | `string` | `rgba(255, 255, 255, 0.5)\|rgba(0, 0, 0, 0.5)` | 调整模糊背景色,推荐 rgba 颜色值,使用“\|”分隔亮色模式和暗色模式的颜色值(eg: rgba(255, 255, 255, 0.5)\|rgba(0, 0, 0, 0.5)) |
|
||||
| 启用标签透明背景 | `enableTransparentTags` | `switch` | `true` | 启用后标签将使用较为透明的背景色,当背景情况复杂导致标签难以辨识时建议关闭 |
|
||||
| 标签默认颜色列表 | `tagDefaultColorList` | `string` | `ruby,gray,gold,bronze,brown,yellow,amber,orange,tomato,red` | 标签默认颜色列表,展示的标签将按顺序调用该颜色池,逗号分隔(可用的颜色列表请参考:https://www.radix-ui.com/themes/docs/theme/color ,改完没有生效则说明填写有误) |
|
||||
| 默认主题颜色 | `selectThemeColor` | `select` | `gray` | 设置默认主题颜色,颜色对照请参考:https://www.radix-ui.com/themes/docs/theme/color |
|
||||
| 启用 localStorage 配置 | `enableLocalStorage` | `switch` | `true` | 启用后将优先使用用户浏览器本地配置的视图和外观设置。关闭后将强制使用下方的主题配置,本地可调整但刷新即恢复 |
|
||||
| 默认展示视图 | `selectedDefaultView` | `select` | `grid` | 设置默认展示视图为网格或表格 |
|
||||
| 默认外观 | `selectedDefaultAppearance` | `select` | `system` | 设置默认外观为浅色、深色或系统主题 |
|
||||
| 默认主题颜色 | `selectThemeColor` | `select` | `gray` | 设置默认主题颜色,颜色对照请参考:https://www.radix-ui.com/themes/docs/theme/color |
|
||||
| 状态卡片显示控制 | `statusCardsVisibility` | `string` | `currentTime:true,currentOnline:true,regionOverview:true,trafficOverview:true,networkSpeed:true` | 控制状态卡片的显示与隐藏,格式为 卡片名称:显示状态(true/false),多个卡片使用逗号分隔,支持的卡片名称包括 currentTime(当前时间), currentOnline(当前在线), regionOverview(点亮地区), trafficOverview(流量概览), networkSpeed(网络速率) |
|
||||
|
||||
#### 标题栏设置
|
||||
|
||||
|
@@ -69,6 +69,14 @@
|
||||
"default": "ruby,gray,gold,bronze,brown,yellow,amber,orange,tomato,red",
|
||||
"help": "标签默认颜色列表,展示的标签将按顺序调用该颜色池,逗号分隔(可用的颜色列表请参考:https://www.radix-ui.com/themes/docs/theme/color,改完没有生效则说明填写有误)"
|
||||
},
|
||||
{
|
||||
"key": "selectThemeColor",
|
||||
"name": "默认主题颜色",
|
||||
"type": "select",
|
||||
"options": "gray,gold,bronze,brown,yellow,amber,orange,tomato,red,ruby,crimson,pink,plum,purple,violet,iris,indigo,blue,cyan,teal,jade,green,grass,lime,mint,sky",
|
||||
"default": "gray",
|
||||
"help": "设置默认主题颜色,颜色对照请参考:https://www.radix-ui.com/themes/docs/theme/color"
|
||||
},
|
||||
{
|
||||
"key": "enableLocalStorage",
|
||||
"name": "启用 localStorage 配置",
|
||||
@@ -93,12 +101,11 @@
|
||||
"help": "设置默认外观为浅色、深色或系统主题"
|
||||
},
|
||||
{
|
||||
"key": "selectThemeColor",
|
||||
"name": "默认主题颜色",
|
||||
"type": "select",
|
||||
"options": "gray,gold,bronze,brown,yellow,amber,orange,tomato,red,ruby,crimson,pink,plum,purple,violet,iris,indigo,blue,cyan,teal,jade,green,grass,lime,mint,sky",
|
||||
"default": "gray",
|
||||
"help": "设置默认主题颜色,颜色对照请参考:https://www.radix-ui.com/themes/docs/theme/color"
|
||||
"key": "statusCardsVisibility",
|
||||
"name": "状态卡片显示控制",
|
||||
"type": "string",
|
||||
"default": "currentTime:true,currentOnline:true,regionOverview:true,trafficOverview:true,networkSpeed:true",
|
||||
"help": "控制状态卡片的显示与隐藏,格式为 卡片名称:显示状态(true/false),多个卡片使用逗号分隔,支持的卡片名称包括 currentTime(当前时间), currentOnline(当前在线), regionOverview(点亮地区), trafficOverview(流量概览), networkSpeed(网络速率)"
|
||||
},
|
||||
{
|
||||
"name": "标题栏设置",
|
||||
|
@@ -15,11 +15,11 @@ import { useIsMobile } from "@/hooks/useMobile";
|
||||
|
||||
interface StatsBarProps {
|
||||
displayOptions: {
|
||||
time: boolean;
|
||||
online: boolean;
|
||||
regions: boolean;
|
||||
traffic: boolean;
|
||||
speed: boolean;
|
||||
currentTime: boolean;
|
||||
currentOnline: boolean;
|
||||
regionOverview: boolean;
|
||||
trafficOverview: boolean;
|
||||
networkSpeed: boolean;
|
||||
};
|
||||
setDisplayOptions: (options: any) => void;
|
||||
stats: {
|
||||
@@ -58,10 +58,10 @@ export const StatsBar = ({
|
||||
// 渲染统计项
|
||||
const renderStatItem = (key: string) => {
|
||||
switch (key) {
|
||||
case "time":
|
||||
case "currentTime":
|
||||
return (
|
||||
displayOptions.time && (
|
||||
<div className="w-full py-1" key="time">
|
||||
displayOptions.currentTime && (
|
||||
<div className="w-full py-1" key="currentTime">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-secondary-foreground text-sm">
|
||||
当前时间
|
||||
@@ -73,10 +73,10 @@ export const StatsBar = ({
|
||||
</div>
|
||||
)
|
||||
);
|
||||
case "online":
|
||||
case "currentOnline":
|
||||
return (
|
||||
displayOptions.online && (
|
||||
<div className="w-full py-1" key="online">
|
||||
displayOptions.currentOnline && (
|
||||
<div className="w-full py-1" key="currentOnline">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-secondary-foreground text-sm">
|
||||
当前在线
|
||||
@@ -90,10 +90,10 @@ export const StatsBar = ({
|
||||
</div>
|
||||
)
|
||||
);
|
||||
case "regions":
|
||||
case "regionOverview":
|
||||
return (
|
||||
displayOptions.regions && (
|
||||
<div className="w-full py-1" key="regions">
|
||||
displayOptions.regionOverview && (
|
||||
<div className="w-full py-1" key="regionOverview">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-secondary-foreground text-sm">
|
||||
点亮地区
|
||||
@@ -105,10 +105,10 @@ export const StatsBar = ({
|
||||
</div>
|
||||
)
|
||||
);
|
||||
case "traffic":
|
||||
case "trafficOverview":
|
||||
return (
|
||||
displayOptions.traffic && (
|
||||
<div className="w-full py-1" key="traffic">
|
||||
displayOptions.trafficOverview && (
|
||||
<div className="w-full py-1" key="trafficOverview">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-secondary-foreground text-sm">
|
||||
流量概览
|
||||
@@ -127,10 +127,10 @@ export const StatsBar = ({
|
||||
</div>
|
||||
)
|
||||
);
|
||||
case "speed":
|
||||
case "networkSpeed":
|
||||
return (
|
||||
displayOptions.speed && (
|
||||
<div className="w-full py-1" key="speed">
|
||||
displayOptions.networkSpeed && (
|
||||
<div className="w-full py-1" key="networkSpeed">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-secondary-foreground text-sm">
|
||||
网络速率
|
||||
@@ -170,45 +170,60 @@ export const StatsBar = ({
|
||||
<DropdownMenuItem className="flex items-center justify-between cursor-pointer">
|
||||
<span>当前时间</span>
|
||||
<Switch
|
||||
checked={displayOptions.time}
|
||||
checked={displayOptions.currentTime}
|
||||
onCheckedChange={(checked) =>
|
||||
setDisplayOptions({ ...displayOptions, time: checked })
|
||||
setDisplayOptions({
|
||||
...displayOptions,
|
||||
currentTime: checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="flex items-center justify-between cursor-pointer">
|
||||
<span>当前在线</span>
|
||||
<Switch
|
||||
checked={displayOptions.online}
|
||||
checked={displayOptions.currentOnline}
|
||||
onCheckedChange={(checked) =>
|
||||
setDisplayOptions({ ...displayOptions, online: checked })
|
||||
setDisplayOptions({
|
||||
...displayOptions,
|
||||
currentOnline: checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="flex items-center justify-between cursor-pointer">
|
||||
<span>点亮地区</span>
|
||||
<Switch
|
||||
checked={displayOptions.regions}
|
||||
checked={displayOptions.regionOverview}
|
||||
onCheckedChange={(checked) =>
|
||||
setDisplayOptions({ ...displayOptions, regions: checked })
|
||||
setDisplayOptions({
|
||||
...displayOptions,
|
||||
regionOverview: checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="flex items-center justify-between cursor-pointer">
|
||||
<span>流量概览</span>
|
||||
<Switch
|
||||
checked={displayOptions.traffic}
|
||||
checked={displayOptions.trafficOverview}
|
||||
onCheckedChange={(checked) =>
|
||||
setDisplayOptions({ ...displayOptions, traffic: checked })
|
||||
setDisplayOptions({
|
||||
...displayOptions,
|
||||
trafficOverview: checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="flex items-center justify-between cursor-pointer">
|
||||
<span>网络速率</span>
|
||||
<Switch
|
||||
checked={displayOptions.speed}
|
||||
checked={displayOptions.networkSpeed}
|
||||
onCheckedChange={(checked) =>
|
||||
setDisplayOptions({ ...displayOptions, speed: checked })
|
||||
setDisplayOptions({
|
||||
...displayOptions,
|
||||
networkSpeed: checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
|
@@ -7,10 +7,11 @@ export interface ConfigOptions {
|
||||
blurBackgroundColor?: string; // 磨砂玻璃背景颜色
|
||||
enableTransparentTags?: boolean; // 是否启用标签透明背景
|
||||
tagDefaultColorList?: string; // 标签默认颜色列表
|
||||
selectThemeColor?: string; // 默认主题颜色
|
||||
enableLocalStorage?: boolean; // 是否启用本地存储
|
||||
selectedDefaultView?: "grid" | "table"; // 默认视图模式
|
||||
selectedDefaultAppearance?: "light" | "dark" | "system"; // 默认外观模式
|
||||
selectThemeColor?: string; // 默认主题颜色
|
||||
statusCardsVisibility?: string; // 状态卡片显示控制
|
||||
enableLogo?: boolean; // 是否启用Logo
|
||||
logoUrl?: string; // Logo图片URL
|
||||
enableTitle?: boolean; // 是否启用标题
|
||||
@@ -38,10 +39,12 @@ export const DEFAULT_CONFIG: ConfigOptions = {
|
||||
enableTransparentTags: true,
|
||||
tagDefaultColorList:
|
||||
"ruby,gray,gold,bronze,brown,yellow,amber,orange,tomato,red",
|
||||
selectThemeColor: "gray",
|
||||
enableLocalStorage: true,
|
||||
selectedDefaultView: "grid",
|
||||
selectedDefaultAppearance: "system",
|
||||
selectThemeColor: "gray",
|
||||
statusCardsVisibility:
|
||||
"currentTime:true,currentOnline:true,regionOverview:true,trafficOverview:true,networkSpeed:true",
|
||||
enableLogo: false,
|
||||
logoUrl: "/assets/logo.png",
|
||||
enableTitle: true,
|
||||
|
@@ -44,6 +44,16 @@ export interface ThemeContextType {
|
||||
setColor: (color: Colors) => void;
|
||||
viewMode: "grid" | "table";
|
||||
setViewMode: (mode: "grid" | "table") => void;
|
||||
statusCardsVisibility: {
|
||||
currentTime: true;
|
||||
currentOnline: true;
|
||||
regionOverview: true;
|
||||
trafficOverview: true;
|
||||
networkSpeed: true;
|
||||
};
|
||||
setStatusCardsVisibility: (
|
||||
visibility: Partial<ThemeContextType["statusCardsVisibility"]>
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const ThemeContext = createContext<ThemeContextType>({
|
||||
@@ -54,6 +64,14 @@ export const ThemeContext = createContext<ThemeContextType>({
|
||||
setColor: () => {},
|
||||
viewMode: DEFAULT_CONFIG.selectedDefaultView as "grid" | "table",
|
||||
setViewMode: () => {},
|
||||
statusCardsVisibility: {
|
||||
currentTime: true,
|
||||
currentOnline: true,
|
||||
regionOverview: true,
|
||||
trafficOverview: true,
|
||||
networkSpeed: true,
|
||||
},
|
||||
setStatusCardsVisibility: () => {},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -107,12 +125,17 @@ const useStoredState = <T>(
|
||||
|
||||
const [state, setState] = useState<T>(() => {
|
||||
if (enableLocalStorage) {
|
||||
const storedValue = localStorage.getItem(key);
|
||||
if (storedValue) {
|
||||
const cleanedValue = storedValue.replace(/^"|"$/g, "");
|
||||
if (!validator || validator(cleanedValue)) {
|
||||
return cleanedValue as T;
|
||||
try {
|
||||
const storedValue = localStorage.getItem(key);
|
||||
if (storedValue) {
|
||||
const parsedValue = JSON.parse(storedValue);
|
||||
if (!validator || validator(parsedValue)) {
|
||||
return parsedValue as T;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing stored state:", error);
|
||||
// Fallback to default value if parsing fails
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
@@ -120,7 +143,11 @@ const useStoredState = <T>(
|
||||
|
||||
useEffect(() => {
|
||||
if (enableLocalStorage) {
|
||||
localStorage.setItem(key, String(state));
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(state));
|
||||
} catch (error) {
|
||||
console.error("Error setting stored state:", error);
|
||||
}
|
||||
}
|
||||
}, [key, state, enableLocalStorage]);
|
||||
|
||||
@@ -133,6 +160,9 @@ export const useThemeManager = () => {
|
||||
) as Appearance;
|
||||
const defaultColor = useConfigItem("selectThemeColor") as Colors;
|
||||
const defaultView = useConfigItem("selectedDefaultView") as "grid" | "table";
|
||||
const defaultStatusCardsVisibility = useConfigItem(
|
||||
"statusCardsVisibility"
|
||||
) as string;
|
||||
|
||||
const [appearance, setAppearance] = useStoredState<Appearance>(
|
||||
"appearance",
|
||||
@@ -147,6 +177,24 @@ export const useThemeManager = () => {
|
||||
defaultView
|
||||
);
|
||||
|
||||
const [statusCardsVisibility, setStatusCardsVisibility] = useStoredState(
|
||||
"statusCardsVisibility",
|
||||
(() => {
|
||||
const visibility: { [key: string]: boolean } = {};
|
||||
defaultStatusCardsVisibility.split(",").forEach((item) => {
|
||||
const [key, value] = item.split(":");
|
||||
visibility[key] = value === "true";
|
||||
});
|
||||
return visibility as ThemeContextType["statusCardsVisibility"];
|
||||
})()
|
||||
);
|
||||
|
||||
const handleSetStatusCardsVisibility = (
|
||||
newVisibility: Partial<ThemeContextType["statusCardsVisibility"]>
|
||||
) => {
|
||||
setStatusCardsVisibility((prev) => ({ ...prev, ...newVisibility }));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setColor(defaultColor);
|
||||
}, [defaultColor, setColor]);
|
||||
@@ -161,6 +209,8 @@ export const useThemeManager = () => {
|
||||
setColor,
|
||||
viewMode,
|
||||
setViewMode,
|
||||
statusCardsVisibility,
|
||||
setStatusCardsVisibility: handleSetStatusCardsVisibility,
|
||||
};
|
||||
};
|
||||
export const useTheme = () => {
|
||||
|
@@ -29,7 +29,8 @@ const homeStateCache = {
|
||||
};
|
||||
|
||||
const HomePage: React.FC<HomePageProps> = ({ searchTerm, setSearchTerm }) => {
|
||||
const { viewMode } = useTheme();
|
||||
const { viewMode, statusCardsVisibility, setStatusCardsVisibility } =
|
||||
useTheme();
|
||||
const { nodes: staticNodes, loading, getGroups } = useNodeData();
|
||||
const { liveData } = useLiveData();
|
||||
const [selectedGroup, setSelectedGroup] = useState(
|
||||
@@ -42,14 +43,6 @@ const HomePage: React.FC<HomePageProps> = ({ searchTerm, setSearchTerm }) => {
|
||||
enableListItemProgressBar,
|
||||
selectTrafficProgressStyle,
|
||||
} = useAppConfig();
|
||||
const [displayOptions, setDisplayOptions] = useState({
|
||||
time: true,
|
||||
online: true,
|
||||
regions: true,
|
||||
traffic: true,
|
||||
speed: true,
|
||||
});
|
||||
|
||||
const combinedNodes = useMemo<NodeWithStatus[]>(() => {
|
||||
if (!staticNodes) return [];
|
||||
return staticNodes.map((node) => {
|
||||
@@ -134,8 +127,8 @@ const HomePage: React.FC<HomePageProps> = ({ searchTerm, setSearchTerm }) => {
|
||||
className="w-[90%] max-w-screen-2xl mx-auto flex-1 flex flex-col pb-10 overflow-y-auto">
|
||||
{enableStatsBar && (
|
||||
<StatsBar
|
||||
displayOptions={displayOptions}
|
||||
setDisplayOptions={setDisplayOptions}
|
||||
displayOptions={statusCardsVisibility}
|
||||
setDisplayOptions={setStatusCardsVisibility}
|
||||
stats={stats}
|
||||
loading={loading}
|
||||
/>
|
||||
|
Reference in New Issue
Block a user