mirror of
https://github.com/ialley-workshop-open/uni-halo.git
synced 2026-09-12 16:40:40 +08:00
refactor: 重构偏好设置模块,新增全局设置弹窗与通用hook
1. 抽离usePreferenceRows通用hook,统一管理偏好设置的字段定义、状态读写与交互逻辑 2. 新增全局操作组件与全局设置弹窗,替换原有的滚动到顶部组件 3. 重构页面端偏好设置页面,复用通用hook减少重复代码 4. 修复并优化了原有组件的页面适配逻辑
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 偏好设置展示行与交互(页面 setting.vue 与全局弹窗 uh-settings-popup 共用)
|
||||
* 收敛:字段定义(PrefDef/页面分组/布局/功能)、三态(跟随站点默认/已自定义/恢复默认)、
|
||||
* 行构建、选值交互。两个消费方不再各自重复定义。
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
import { DefaultAppSettings } from '@/config/appSettings';
|
||||
import { useSettingStore } from '@/store/setting';
|
||||
import { isLocalOverride, readLocalPrefs } from '@/utils/preference';
|
||||
import type { LocalPrefs } from '@/utils/preference';
|
||||
|
||||
/** 偏好字段定义 */
|
||||
export interface PrefDef {
|
||||
key: string;
|
||||
label: string;
|
||||
kind: 'bool' | 'enum';
|
||||
path: string[];
|
||||
options?: { label: string; value: string }[];
|
||||
siteLabelOf?: (value: string) => string;
|
||||
}
|
||||
|
||||
/** 布局设置按页面分组(每组:列表布局 + 卡片样式) */
|
||||
export const PAGE_GROUPS = [
|
||||
{ key: 'home', label: '首页' },
|
||||
{ key: 'articles', label: '文章页面' },
|
||||
{ key: 'archives', label: '归档页面' }
|
||||
];
|
||||
|
||||
/** 布局偏好字段(页面分组 × 列表布局/卡片样式) */
|
||||
export const LAYOUT_PREFS: PrefDef[] = PAGE_GROUPS.flatMap((group) => [
|
||||
{
|
||||
key: `${group.key}ListLayout`,
|
||||
label: '列表布局',
|
||||
kind: 'enum',
|
||||
path: ['layout', group.key, 'listLayout'],
|
||||
options: [
|
||||
{ label: '单列', value: 'single' },
|
||||
{ label: '双列', value: 'double' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: `${group.key}CardType`,
|
||||
label: '卡片样式',
|
||||
kind: 'enum',
|
||||
path: ['layout', group.key, 'cardType'],
|
||||
options: [
|
||||
{ label: '上图下文', value: 'image_top' },
|
||||
{ label: '左文右图', value: 'image_right' },
|
||||
{ label: '上文下图', value: 'image_bottom' },
|
||||
{ label: '左图右文', value: 'image_left' }
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
/** 功能偏好字段 */
|
||||
export const FEATURE_PREFS: PrefDef[] = [{ key: 'isAvatarRadius', label: '是否圆形头像', kind: 'bool', path: ['isAvatarRadius'] }];
|
||||
|
||||
/** 顶部分段器(布局 / 功能) */
|
||||
export const SETTING_TABS: { key: 'layout' | 'feature'; label: string }[] = [
|
||||
{ key: 'layout', label: '布局' },
|
||||
{ key: 'feature', label: '功能' }
|
||||
];
|
||||
|
||||
export function usePreferenceRows() {
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
/* ---------------- 路径取值工具 ---------------- */
|
||||
function getByPath(obj: unknown, path: string[]): unknown {
|
||||
let cursor: unknown = obj;
|
||||
for (const key of path) {
|
||||
if (cursor === null || cursor === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
cursor = (cursor as Record<string, unknown>)[key];
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/** 按路径构造差异 patch(null 表示删除该键=跟随站点默认) */
|
||||
function buildPatch(path: string[], value: unknown): LocalPrefs {
|
||||
const [head, ...rest] = path;
|
||||
if (rest.length === 0) return { [head]: value } as LocalPrefs;
|
||||
return { [head]: buildPatch(rest, value) } as LocalPrefs;
|
||||
}
|
||||
|
||||
/* ---------------- 状态读取 ---------------- */
|
||||
function prefValueOf(path: string[]): unknown {
|
||||
return getByPath(settingStore.settings, path);
|
||||
}
|
||||
|
||||
/** 站点默认值(未配置时回退内置默认) */
|
||||
function siteDefaultOf(path: string[]): unknown {
|
||||
const site = getByPath(settingStore.siteDefaults, path);
|
||||
if (site !== undefined && site !== null) return site;
|
||||
return getByPath(DefaultAppSettings, path);
|
||||
}
|
||||
|
||||
function isOverridden(path: string[]): boolean {
|
||||
return isLocalOverride(readLocalPrefs(), path);
|
||||
}
|
||||
|
||||
function enumLabelOf(def: PrefDef, value: unknown): string {
|
||||
const hit = def.options?.find((opt) => opt.value === value);
|
||||
if (hit) return hit.label;
|
||||
if (def.siteLabelOf && typeof value === 'string') return def.siteLabelOf(value);
|
||||
return value === undefined || value === null ? '—' : String(value);
|
||||
}
|
||||
|
||||
/* ---------------- 展示行(预计算,避免模板渲染期函数调用) ---------------- */
|
||||
interface PrefRow extends PrefDef {
|
||||
displayValue: string;
|
||||
following: boolean;
|
||||
/** 仅 kind==='bool' 使用 */
|
||||
boolValue: boolean;
|
||||
}
|
||||
|
||||
function buildRows(defs: PrefDef[]): PrefRow[] {
|
||||
return defs.map((def) => ({
|
||||
...def,
|
||||
displayValue: enumLabelOf(def, prefValueOf(def.path)),
|
||||
following: isFollowing(def.path),
|
||||
boolValue: def.kind === 'bool' ? prefValueOf(def.path) === true : false
|
||||
}));
|
||||
}
|
||||
|
||||
const layoutRows = computed(() => buildRows(LAYOUT_PREFS));
|
||||
const featureRows = computed(() => buildRows(FEATURE_PREFS));
|
||||
|
||||
/** 布局设置按页面分组的展示行 */
|
||||
const layoutGroups = computed(() =>
|
||||
PAGE_GROUPS.map((group) => ({
|
||||
key: group.key,
|
||||
label: group.label,
|
||||
rows: layoutRows.value.filter((row) => row.path[1] === group.key)
|
||||
}))
|
||||
);
|
||||
|
||||
/** 当前项是否处于「跟随站点默认」(本地未覆盖) */
|
||||
function isFollowing(path: string[]): boolean {
|
||||
return !isOverridden(path);
|
||||
}
|
||||
|
||||
/** 单项还原为跟随站点默认 */
|
||||
function handleRevert(path: string[]): void {
|
||||
settingStore.savePreference(buildPatch(path, null));
|
||||
}
|
||||
|
||||
/** 开关类:选值等于站点默认则还原为跟随(只存差异) */
|
||||
function handleBoolChange(path: string[], next: boolean): void {
|
||||
if (next === siteDefaultOf(path)) {
|
||||
settingStore.savePreference(buildPatch(path, null));
|
||||
} else {
|
||||
settingStore.savePreference(buildPatch(path, next));
|
||||
}
|
||||
}
|
||||
|
||||
/** 枚举选值:null 或等于站点默认则还原为跟随,否则写入差异(内联分段器与弹层共用) */
|
||||
function handleChoose(path: string[], value: string | null): void {
|
||||
if (value === null || value === siteDefaultOf(path)) {
|
||||
handleRevert(path);
|
||||
} else {
|
||||
settingStore.savePreference(buildPatch(path, value));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
settingStore,
|
||||
PAGE_GROUPS,
|
||||
SETTING_TABS,
|
||||
getByPath,
|
||||
buildPatch,
|
||||
prefValueOf,
|
||||
siteDefaultOf,
|
||||
isOverridden,
|
||||
enumLabelOf,
|
||||
buildRows,
|
||||
layoutRows,
|
||||
featureRows,
|
||||
layoutGroups,
|
||||
isFollowing,
|
||||
handleRevert,
|
||||
handleBoolChange,
|
||||
handleChoose
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user