feat(设置+门户): LOGO 文字配置(校/平台两级,pane 内维护)+ header 徽标旁显示;个人主题保存改 JSON 形态(LOGO文字与主题JSON化)
build / build (push) Successful in 1m27s

This commit is contained in:
chenxing
2026-10-09 10:35:25 +08:00
parent bdc8898b93
commit 2819f96dfd
5 changed files with 108 additions and 6 deletions
+14
View File
@@ -226,6 +226,20 @@ class Base extends Abstract {
return this.postRequest({ url });
}
// ===== LOGO 文字(平台/学校名称,2026-10-09 LOGO 文字轮;后端契约见 SchSettingsController#schLogoText)=====
/** 读 LOGO 文字(已登录可读):{logoText: string|null, schId=实际命中层}——本校空 → 平台层 → null */
getSchLogoText() {
const url = path + "/schLogoText";
return this.getRequest<{ logoText: string | null; schId: number }>({ url });
}
/** 保存 LOGO 文字(仅学校管理员/超管):trim 后落本级(超管=平台层 0,校管=本校),≤64;空串=清空本级 */
saveSchLogoText(logoText: string) {
const url = path + "/saveSchLogoText";
return this.postRequest({ url, params: { logoText } });
}
// ===== 学校默认头像样式(默认头像轮 2026-09-30;后端契约见 SchSettingsController#schAvatarStyle)=====
/**
+8
View File
@@ -28,6 +28,14 @@
height: 36px;
}
& .logo-text {
margin-left: 10px;
font-size: 16px;
font-weight: 600;
color: #252b35;
white-space: nowrap;
}
& .operate-container {
flex-shrink: 1;
& > li {
+7
View File
@@ -1,6 +1,7 @@
<template>
<header :class="headerClass">
<img v-if="!!logoUrl" :src="baseUrl + '/image/default-logo.png'" v-real-img="logoUrl" class="logo-box" alt="" />
<span v-if="logoText" class="logo-text">{{ logoText }}</span>
<ul class="operate-container fix">
<li v-if="isWidgetIconShow" :class="{ active: isWidgetShow }">
<div class="icon-box" v-wave @click="toggleWidgetShow">
@@ -265,6 +266,8 @@ const logoUrl = computed(() => {
const sysLogo = logoList.value.find(x => x.type == 0);
return sysLogo ? sysLogo.url : "";
});
/** LOGO 文字(平台/学校名称,2026-10-09):存 sch_settings.logo_text(级联读),与 LOGO 图独立 */
const logoText = ref("");
const isUserCardShow = ref(false),
isWallpaperShow = ref(false),
isLogoShow = ref(false),
@@ -561,6 +564,10 @@ async function onWallpaperSelect(item: any) {
async function loadLogoList() {
const list = await UserSettingApi.getLogoList();
logoList.value = list || [];
// LOGO 文字与图并行加载(失败静默——无文字仅显示图,不影响 header)
BaseApi.getSchLogoText()
.then(res => (logoText.value = res?.logoText || ""))
.catch(() => {});
}
async function logoSuccess(response: any, file: any, fileListt: any) {
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* 本文件由 YschTheme scripts/gen-theme.mjs 从 assets-src/pc/theme-core.js 生成(版本 20261009-0054-5732ee2),勿手改。
* 本文件由 YschTheme scripts/gen-theme.mjs 从 assets-src/pc/theme-core.js 生成(版本 20261009-1018-fb1c7f6),勿手改。
* 分发方式:YschTheme `npm run sync` 拷进各端 src/utils/theme.ts(与 ThemeSwitcher.vue 配套,MD5 对齐报告)。
* 运行时档位类名由 /static/js/yh-theme-init.js 在 <head> 内先行挂好,本模块只负责读写与切换。
*/
+78 -5
View File
@@ -501,6 +501,16 @@
</div>
</div>
</div>
<!-- LOGO 文字(平台/学校名称,2026-10-09):单值配置存 sch_settings.logo_text,与上方 LOGO 图(logo 表)互不影响 -->
<div class="sec-card">
<h4>LOGO 文字 <span class="tag">平台/学校名称</span></h4>
<div class="logo-text-row">
<Input v-model="logoText" maxlength="64" placeholder="如:华阳中学教育集团智慧服务平台" style="width: 340px" />
<button class="btn primary sm" :disabled="logoTextSaving" @click="onSaveLogoText">保存文字</button>
<button class="btn plain sm" :disabled="logoTextSaving || !layerLogoConfigured" @click="onClearLogoText">清空本级文字</button>
</div>
<p class="hint">{{ logoTextHitLabel }} · 展示在门户与各子应用顶部导航 LOGO 旁,最长 64 字</p>
</div>
<!-- 上级只读参考池:校管视角下的平台 LOGO(type=0,不可删,可直接选用为本级 LOGO);超管视角为空 -->
<div v-if="upperLogoPool.length > 0" class="sec-card">
<h4>平台 LOGO <span class="tag">只读参考</span></h4>
@@ -1014,6 +1024,58 @@ async function onSaveLogo() {
}
}
/* ===== LOGO 文字(平台/学校名称,2026-10-09 LOGO 文字轮):单值配置存 sch_settings.logo_text,
* 与 LOGO 图(logo 表)独立;级联读回填——schId=命中层(校级视角 0=本级未配置、生效的是平台文字回落)===== */
const logoText = ref("");
const layerLogoTextHitSchId = ref<number | null>(null);
const logoTextHasValue = ref(false);
const logoTextSaving = ref(false);
/** 本级是否真配置过(对齐主题层 layerConfigured 写法:平台层一律按本级口径,校级看命中层) */
const layerLogoConfigured = computed(() => logoTextHasValue.value && (curLayer.value === "platform" || layerLogoTextHitSchId.value !== 0));
const logoTextHitLabel = computed(() => {
if (curLayer.value === "platform") return "平台默认";
return layerLogoConfigured.value ? "本校已配置" : "未配置本级,当前生效:平台默认";
});
async function loadLogoText() {
try {
const res = await BaseApi.getSchLogoText();
layerLogoTextHitSchId.value = res?.schId ?? null;
logoText.value = res?.logoText || "";
logoTextHasValue.value = !!res?.logoText;
} catch (e) {
/* 读取失败不打扰 */
}
}
/** 保存/清空走同一端点(saveSchLogoText):空串=清空本级(后端存 null);成功后回读一次刷新命中态 */
async function onSaveLogoText() {
if (logoTextSaving.value) return;
logoTextSaving.value = true;
try {
await BaseApi.saveSchLogoText(logoText.value.trim());
Message.success("LOGO 文字已保存");
await loadLogoText();
} catch (error) {
Message.error("保存失败:" + error);
} finally {
logoTextSaving.value = false;
}
}
async function onClearLogoText() {
if (logoTextSaving.value) return;
logoTextSaving.value = true;
try {
await BaseApi.saveSchLogoText("");
Message.success("已清空本级文字");
await loadLogoText();
} catch (error) {
Message.error("操作失败:" + error);
} finally {
logoTextSaving.value = false;
}
}
/* ===== wallpaper pane · my 层(个人壁纸,2026-10-02 任务A;S4 三层化后仅 curLayer==='my' 渲染)=====
* 能力复制自 Header「设置壁纸」模态框(模态框保留不动):列表 getUserWallpaperList(type 0=系统/1=校区/2=个人)、
* 点选即保存 checkWallpaper(与模态框 onClickWallpaper 同语义)、上传走 /api/wallpaperUpload(普通用户落 type=2 个人壁纸)、
@@ -1080,7 +1142,10 @@ watch(curPane, pane => {
loadMyWallpaperList();
}
}
if (pane === "logo" && isAdmin.value && logoManageList.value.length === 0) loadManageLists();
if (pane === "logo" && isAdmin.value) {
if (logoManageList.value.length === 0) loadManageLists();
loadLogoText();
}
}, { immediate: true });
/* ===== desktop:桌面应用三级默认(S6,后端 used_app 级联 + copy-on-write 已就绪,此处只补 UI)=====
@@ -1302,13 +1367,13 @@ function keyExists(key: string) {
}
}
/** ThemeState → 服务端紧凑编码 theme|sideDeep01|gradOn01|sideFollow01(与 SchSettings 存储形态同款) */
function themeToCompact(s: ThemeState): string {
return `${s.theme}|${s.sideDeep ? 1 : 0}|${s.gradOn ? 1 : 0}|${s.sideFollow ? 1 : 0}`;
/** ThemeState → 服务端 ThemeState JSON 串(2026-10-09 JSON 化契约;后端写侧只收 JSON,旧紧凑串会被拒) */
function themeToJson(s: ThemeState): string {
return JSON.stringify(s);
}
/** 外观偏好服务端双写(2026-10-08):本地体验不变,fire-and-forget 补写真源——成功不提示、失败 console.warn 不回滚本地 */
function syncMyThemeRemote(s: ThemeState | null) {
BaseApi.saveMyTheme(s ? themeToCompact(s) : null).catch((e: any) => console.warn("[appearance] saveMyTheme 失败(本地已生效,不回滚):", e));
BaseApi.saveMyTheme(s ? themeToJson(s) : null).catch((e: any) => console.warn("[appearance] saveMyTheme 失败(本地已生效,不回滚):", e));
}
function syncMyAvatarRemote(style: string | null) {
BaseApi.saveMyAvatarStyle(style).catch((e: any) => console.warn("[appearance] saveMyAvatarStyle 失败(本地已生效,不回滚):", e));
@@ -2382,6 +2447,14 @@ watch(
color: #b3b3b3;
font-size: 13px;
}
/* LOGO 文字行:输入框 + 保存/清空按钮一行排布 */
.logo-text-row {
display: flex;
align-items: center;
gap: 10px;
padding: 14px 0 4px;
flex-wrap: wrap;
}
.logo-item {
height: 72px;
border-radius: 6px;