From 93249cf5f589bd874ac799dc55615084ef068890 Mon Sep 17 00:00:00 2001 From: chenxing Date: Tue, 6 Oct 2026 20:20:30 +0800 Subject: [PATCH] =?UTF-8?q?chore(sync):=20YschTheme=20sync=20=E4=BB=B6?= =?UTF-8?q?=E5=88=B7=E6=96=B0=EF=BC=88theme.ts=2020261006-2013-75c1d10=20+?= =?UTF-8?q?=20avatar=20=E4=B8=A4=E4=BB=B6=E9=A6=96=E6=AC=A1=E5=85=A5?= =?UTF-8?q?=E5=BA=93=EF=BC=89+=20view-ui-plus=20^1.3.24=20=E5=AF=B9?= =?UTF-8?q?=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit theme.ts 仅版本戳行变更(20260930-1248 → 20261006-2013);avatar.ts/UserAvatar.vue 为 YschTheme 默认头像轮 sync 下发公共源;package.json view-ui-plus ^1.3.19 → ^1.3.24 对齐通道生成基线(YschTheme iview-version 版本锁基准) --- package.json | 2 +- src/components/UserAvatar.vue | 93 ++++++++++++++++++++++++++++++++++ src/utils/avatar.ts | 94 +++++++++++++++++++++++++++++++++++ src/utils/theme.ts | 2 +- 4 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 src/components/UserAvatar.vue create mode 100644 src/utils/avatar.ts diff --git a/package.json b/package.json index a3e0ee8..6d18c0c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "pinia": "^2.1.4", "sass": "^1.64.1", "v-wave": "^3.0.2", - "view-ui-plus": "^1.3.19", + "view-ui-plus": "^1.3.24", "vite-plugin-importer": "^0.2.4", "vue": "^3.4.19", "vue-codemirror": "^6.1.1", diff --git a/src/components/UserAvatar.vue b/src/components/UserAvatar.vue new file mode 100644 index 0000000..aefb0b1 --- /dev/null +++ b/src/components/UserAvatar.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/utils/avatar.ts b/src/utils/avatar.ts new file mode 100644 index 0000000..40d5bda --- /dev/null +++ b/src/utils/avatar.ts @@ -0,0 +1,94 @@ +/** + * 默认头像样式(首字 / 剪影)公共源(YschTheme,scripts/sync-apps.mjs 下发三端 src/utils/avatar.ts)。 + * 取值链与主题同构(theme-core.js resolveEffectiveThemeState 的合成链写法): + * URL ?yh-avatar=text|silhouette(仅预览/排障覆盖:只影响读、绝不写回) + * > localStorage["yh-avatar"](用户自选,纯字符串) + * > localStorage["yh-avatar-sch-default"](学校默认,UserHome 登录后写入) + * > "text"(系统默认) + * 值域恒为 "text" | "silhouette",键缺失/值非法均静默回落下一级,不报错不阻塞渲染。 + * 渲染三态与取字规则的唯一契约见 UserHome《设置页修复与默认头像落地方案》§10.1/§10.2。 + */ + +export type AvatarStyle = "text" | "silhouette"; + +interface StorageLike { + getItem(k: string): string | null; + setItem(k: string, v: string): void; + removeItem(k: string): void; +} + +export const AVATAR_STORAGE_KEY = "yh-avatar"; + +/** 学校默认头像存储键(UserHome 登录时写入;值格式与用户键相同,仅无"用户自选"语义) */ +export const SCHOOL_DEFAULT_AVATAR_STORAGE_KEY = "yh-avatar-sch-default"; + +export const AVATAR_STYLES: readonly AvatarStyle[] = ["text", "silhouette"]; + +export const DEFAULT_AVATAR_STYLE: AvatarStyle = "text"; + +/** 值合法才采用(用户键/学校键同一判据;脏数据不生效) */ +function readAvatarStyleOrNull(raw: string | null): AvatarStyle | null { + return raw === "text" || raw === "silhouette" ? raw : null; +} + +/** URL 覆盖参数(?yh-avatar=text|silhouette):非法值忽略,且只影响"读哪一档"、绝不写回 localStorage */ +function readAvatarOverride(): AvatarStyle | null { + try { + const search = (globalThis.location && globalThis.location.search) || ""; + const m = new RegExp("[?&]yh-avatar=([^&]*)").exec(search); + return m ? readAvatarStyleOrNull(decodeURIComponent(m[1])) : null; + } catch { + /* 无 location 环境(测试/SSR):无覆盖 */ + return null; + } +} + +/** + * 生效样式合成(与 resolveEffectiveThemeState 同构): + * URL 覆盖 > localStorage["yh-avatar"](用户自选)> localStorage["yh-avatar-sch-default"](学校默认) + * > DEFAULT_AVATAR_STYLE。 + */ +export function resolveAvatarStyle(storage: StorageLike | undefined = globalThis.localStorage): AvatarStyle { + let style: AvatarStyle | null = null; + if (storage) { + style = readAvatarStyleOrNull(storage.getItem(AVATAR_STORAGE_KEY)); + if (!style) style = readAvatarStyleOrNull(storage.getItem(SCHOOL_DEFAULT_AVATAR_STORAGE_KEY)); + } + if (!style) style = DEFAULT_AVATAR_STYLE; + return readAvatarOverride() || style; +} + +/** 保存用户自选(直写 localStorage,无响应式;调用方操作后自行刷新 UI) */ +export function saveAvatarStyle(style: AvatarStyle, storage: StorageLike | undefined = globalThis.localStorage): void { + try { + storage?.setItem(AVATAR_STORAGE_KEY, style); + } catch { + /* 隐私模式/配额失败静默:仅丢持久化,不丢本次会话生效 */ + } +} + +/** 跟随学校默认 = 删用户自选键(之后回落学校默认/系统默认) */ +export function clearAvatarStyle(storage: StorageLike | undefined = globalThis.localStorage): void { + try { + storage?.removeItem(AVATAR_STORAGE_KEY); + } catch { + /* 隐私模式静默 */ + } +} + +/** + * 取字(顺序判定,先命中先返回):含 "@" 先截 "@" 前段(邮箱取首字母); + * 首字符必须用展开 [...s][0](禁用 charAt(0),避免代理对截半出乱码)。 + * ASCII 字母转大写;纯数字回落 ""(剪影);符号/emoji/空白回落 ""(剪影);其余(CJK 等)原样。 + */ +export function pickAvatarText(name: string | null | undefined): string { + let s = String(name ?? ""); + const at = s.indexOf("@"); + if (at > -1) s = s.slice(0, at); + const ch = [...s.trim()][0]; + if (ch === undefined) return ""; + if (/^[A-Za-z]$/.test(ch)) return ch.toUpperCase(); + if (/^[0-9]$/.test(ch)) return ""; + if (/^[\p{P}\p{S}\s]$/u.test(ch)) return ""; + return ch; +} diff --git a/src/utils/theme.ts b/src/utils/theme.ts index fbc7ceb..8e83c37 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -1,5 +1,5 @@ /** - * 本文件由 YschTheme scripts/gen-theme.mjs 从 assets-src/pc/theme-core.js 生成(版本 20260930-1248-4e12e6d),勿手改。 + * 本文件由 YschTheme scripts/gen-theme.mjs 从 assets-src/pc/theme-core.js 生成(版本 20261006-2013-75c1d10),勿手改。 * 分发方式:YschTheme `npm run sync` 拷进各端 src/utils/theme.ts(与 ThemeSwitcher.vue 配套,MD5 对齐报告)。 * 运行时档位类名由 /static/js/yh-theme-init.js 在 内先行挂好,本模块只负责读写与切换。 */