diff --git a/src/api/BaseApi.ts b/src/api/BaseApi.ts
index 6992e20..5c63700 100644
--- a/src/api/BaseApi.ts
+++ b/src/api/BaseApi.ts
@@ -172,6 +172,15 @@ class Base extends Abstract {
return this.postRequest({ url, data: { oldPassword, password } });
}
+ /**
+ * 保存校级 LOGO(schId 后端按登录人推断:主学校→归属组→0 平台默认)
+ * @memberof Basic
+ */
+ saveSchLogo(logoUrl: string) {
+ const url = path + "/saveSchLogo";
+ return this.postRequest({ url, params: { logoUrl } });
+ }
+
savUserRole(info: any) {
const url = path + "/user/savUserRole";
return this.postRequest({ url, data: info as any });
diff --git a/src/views/Settings.vue b/src/views/Settings.vue
index 4831424..8a490ed 100644
--- a/src/views/Settings.vue
+++ b/src/views/Settings.vue
@@ -49,28 +49,29 @@
@@ -87,7 +88,8 @@
@@ -120,7 +122,7 @@
@@ -134,13 +136,22 @@
当前头像
-
张
+
-
-
+
+
+
+
+
+
-
上传后立即生效,全站同步展示。
+
上传后立即生效,全站同步展示。支持 JPG / PNG / GIF / WebP,≤ 20MB。
@@ -170,25 +181,31 @@
当前 LOGO
+
暂无 LOGO,请先上传
-
{{ l.marks }}{{ l.name }}
+
![]()
+
×
@@ -201,24 +218,33 @@
+
@@ -291,11 +317,22 @@ import { ref, reactive, computed, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useDefaultStore } from "@/store";
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
+import { requireImg } from "@/utils/hook/useImg";
import SysSetting from "@/views/SysSetting.vue";
+import BaseApi from "@/api/BaseApi";
+import SysSettingApi from "@/api/SysSettingApi";
+import UserSettingApi from "@/api/UserSettingApi";
+import Utils from "@/utils";
+import { Message } from "view-ui-plus";
/**
* 设置中心(/settings):左 216px 分组导航卡 + 右内容卡(B 方案左右双卡)。
- * 本轮只做导航壳 + pane 切换 + 背景层,各 pane 内表单均为占位(接口对接属下一轮)。
+ * 五个 pane 已对接真实数据(设置对接 2026-09-30,零新接口):
+ * profile 读 store.userInfo(保存占位——接口待 UserBase 支持本人自助修改,本轮不做);
+ * password 走 updatePassword + 确认登出流程(照抄 UserInfoModal);
+ * avatar 移植 UserInfoModal 直传上传(预设色块/「使用默认头像」保持占位);
+ * logo/wallpaper 管理列表 + 直传 + 删除照抄 SysSetting.vue,LOGO 保存走 saveSchLogo(schId 后端按登录人推断),
+ * 「应用壁纸」采用 checkWallpaper 个人壁纸语义(与 Header 壁纸弹窗一致,执行报告已写明)。
*/
const route = useRoute();
@@ -366,13 +403,91 @@ const navGroups = computed(() => {
return isAdmin.value ? groups : groups.filter(g => !g.adminOnly);
});
-/* ===== 以下均为 pane 占位数据(真实表单/接口对接属下一轮) ===== */
-const profileForm = reactive({ mobile: "138****8899", email: "zhangming@school.edu.cn" });
+/* ===== profile:读 store.userInfo(真实展示;保存占位) ===== */
+const profileForm = reactive({ mobile: "", email: "" });
+/** userInfo 派生展示(手机/邮箱编辑值初始同源;工号暂以登录账号展示——接口未提供独立工号字段) */
+const profileView = computed(() => {
+ const u: any = store.userInfo || {};
+ return {
+ name: u.name || "-",
+ account: u.account || "-",
+ deptName: u.deptName || u.dept || "-",
+ phone: u.phone || "",
+ email: u.email || "",
+ };
+});
+watch(
+ () => store.userInfo,
+ () => {
+ profileForm.mobile = profileView.value.phone;
+ profileForm.email = profileView.value.email;
+ },
+ { immediate: true },
+);
function resetProfileForm() {
- profileForm.mobile = "138****8899";
- profileForm.email = "zhangming@school.edu.cn";
+ profileForm.mobile = profileView.value.phone;
+ profileForm.email = profileView.value.email;
}
+/** 保存占位:接口待 UserBase 支持本人自助修改,本轮不做(拍板结论) */
+
+/* ===== password:updatePassword + 确认登出(流程照抄 UserInfoModal) ===== */
const passwordForm = reactive({ old: "", next: "", confirm: "" });
+const passwordSubmitting = ref(false);
+async function onUpdatePassword() {
+ if (!passwordForm.old || !passwordForm.next) {
+ Message.warning("请填写原密码与新密码");
+ return;
+ }
+ if (passwordForm.next !== passwordForm.confirm) {
+ Message.warning("两次输入的新密码不一致");
+ return;
+ }
+ if (passwordForm.next === passwordForm.old) {
+ Message.warning("新密码不能与旧密码一致");
+ return;
+ }
+ if (passwordForm.next.length < 8) {
+ Message.warning("密码长度不能小于8位");
+ return;
+ }
+ if (passwordSubmitting.value) return;
+ passwordSubmitting.value = true;
+ try {
+ await BaseApi.updatePassword(passwordForm.old, passwordForm.next);
+ Message.success("操作成功!");
+ // 改密后旧会话失效,引导重新登录(照抄 UserInfoModal 确认登出流程)
+ setTimeout(logout, 1500);
+ } catch (error) {
+ Message.error("操作失败:" + error);
+ } finally {
+ passwordSubmitting.value = false;
+ }
+}
+function logout() {
+ let url = window.location.href;
+ url = "/logout?service=" + url;
+ url = $conf.ssoHost + url;
+ if (!!window.top) window.top.location.href = url;
+ else window.location.href = url;
+}
+
+/* ===== avatar:移植 UserInfoModal 直传上传(预设色块/「使用默认头像」保持占位) ===== */
+// Upload 组件直传不经 axios 封装,需手动补网关校验头(照抄 UserInfoModal)
+const uploadHeaders = { yschapirequest: 1, yschreferer: Utils.strToHexCharCode(window.location.href) };
+function uploadAvatarSuccess(response: any) {
+ if (response.code == 0 && response.data && response.data.url) {
+ store.userInfo.profilePicture = response.data.url;
+ Message.success("头像更新成功!");
+ } else {
+ Message.error("头像更新失败:" + (response.msg || "未知错误"));
+ }
+}
+function onAvatarSizeExceeded() {
+ Message.error("图片大小不能超过20MB!");
+}
+function onAvatarFormatError() {
+ Message.error("仅支持 jpg/jpeg/png/gif/webp 格式图片!");
+}
const avatarPresets = [
"linear-gradient(135deg,#5979ea,#7d9bf5)",
@@ -381,24 +496,97 @@ const avatarPresets = [
"linear-gradient(135deg,#e8684a,#f1957e)",
"linear-gradient(135deg,#8a7ce0,#b3a8f0)",
];
-const avatarLetters = ["张", "A", "B", "C", "D"];
+const avatarLetters = computed(() => {
+ const name = String(store.userInfo?.name || "A").trim();
+ return [name.charAt(0) || "A", "A", "B", "C", "D"];
+});
const avatarSel = ref(0);
-const logoList = [
- { name: "智慧校园平台", marks: "🎓" },
- { name: "云海教育", marks: "📘" },
- { name: "第一中学", marks: "🏫" },
-];
-const logoSel = ref(0);
+/* ===== logo / wallpaper:管理列表 + 直传 + 删除(照抄 SysSetting.vue 管理语义) ===== */
+const logoManageList = ref([]);
+const wallpaperManageList = ref([]);
+const logoSelUrl = ref("");
+const wallSelUrl = ref("");
+/** 直传端点(本 pane 无机构树,超管按机构切换请用「系统设置」pane;不带 schId 即落到登录人归属校) */
+const logoUploadAction = $conf.serviceHost + "/api/logoUpload";
+const wallpaperUploadAction = $conf.serviceHost + "/api/wallpaperUpload";
-const wallpaperList = [
- "linear-gradient(135deg,#cfe0ea,#d9e0d2)",
- "linear-gradient(135deg,#e6e2d6,#c9d4c0)",
- "linear-gradient(135deg,#bfd4e8,#a8bdd6)",
- "linear-gradient(135deg,#f0e2d0,#e0cdb8)",
- "linear-gradient(135deg,#d6e0f0,#c0cde6)",
-];
-const wallSel = ref(0);
+async function loadManageLists() {
+ // 本 pane 无机构树:不带 schId,列表按登录人归属校返回(超管按机构切换请用「系统设置」pane)
+ const [walls, logos] = await Promise.all([
+ SysSettingApi.getSchWallpaperManageList(),
+ SysSettingApi.getSchLogoManageList(),
+ ]);
+ wallpaperManageList.value = walls || [];
+ logoManageList.value = logos || [];
+}
+
+function onLogoUploaded() {
+ loadManageLists();
+}
+function onWallpaperUploaded() {
+ loadManageLists();
+}
+async function onDeleteLogo(item: any) {
+ await SysSettingApi.deleteLogoById(item.id);
+ Message.success("删除成功");
+ loadManageLists();
+}
+async function onDeleteWallpaper(item: any) {
+ await SysSettingApi.deleteWallById(item.id);
+ Message.success("删除成功");
+ loadManageLists();
+}
+
+/** LOGO 保存:选中项写入学校级配置(schId 后端按登录人推断:主学校→归属组→0 平台默认) */
+const logoSaving = ref(false);
+async function onSaveLogo() {
+ if (!logoSelUrl.value) {
+ Message.warning("请先选择一个 LOGO");
+ return;
+ }
+ if (logoSaving.value) return;
+ logoSaving.value = true;
+ try {
+ await BaseApi.saveSchLogo(logoSelUrl.value);
+ Message.success("保存成功,全站 LOGO 生效");
+ } catch (error) {
+ Message.error("操作失败:" + error);
+ } finally {
+ logoSaving.value = false;
+ }
+}
+
+/** 应用壁纸:采用 checkWallpaper 个人壁纸语义(与 Header 壁纸弹窗现状一致,见执行报告) */
+const wallApplying = ref(false);
+async function onApplyWallpaper() {
+ if (!wallSelUrl.value) {
+ Message.warning("请先选择一张壁纸");
+ return;
+ }
+ if (wallApplying.value) return;
+ wallApplying.value = true;
+ try {
+ await UserSettingApi.checkWallpaper(store.getCurSchId, wallSelUrl.value);
+ const userSetting = store.curUserSettingInfo;
+ if (!!userSetting) {
+ userSetting.wallpaperUrl = wallSelUrl.value;
+ store.setCurUserSettingsInfo(userSetting);
+ }
+ Message.success("壁纸已应用");
+ } catch (error) {
+ Message.error("操作失败:" + error);
+ } finally {
+ wallApplying.value = false;
+ }
+}
+
+// 首次切到 logo / wallpaper pane 时懒加载管理列表
+watch(curPane, pane => {
+ if ((pane === "logo" || pane === "wallpaper") && isAdmin.value && logoManageList.value.length === 0 && wallpaperManageList.value.length === 0) {
+ loadManageLists();
+ }
+});
const sidebarItems = ref([
{ name: "通知公告", on: true },
@@ -672,23 +860,16 @@ function openBackend() {
align-items: center;
gap: 16px;
}
-.avatar-now {
+.avatar-img {
width: 72px;
height: 72px;
border-radius: 50%;
- background: linear-gradient(135deg, #5979ea, #7d9bf5);
- color: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 26px;
- font-weight: 600;
+ object-fit: cover;
flex-shrink: 0;
box-shadow: 0 4px 14px rgba(89, 121, 234, 0.3);
- &.avatar-sm {
+ &.avatar-img-sm {
width: 44px;
height: 44px;
- font-size: 16px;
}
}
.avatar-ops {
@@ -698,6 +879,7 @@ function openBackend() {
.avatar-btns {
display: flex;
gap: 8px;
+ align-items: center;
}
.tip {
font-size: 12px;
@@ -786,6 +968,12 @@ function openBackend() {
gap: 12px;
padding: 14px 0 4px;
}
+.logo-empty {
+ grid-column: 1 / -1;
+ padding: 18px 0 8px;
+ color: #b3b3b3;
+ font-size: 13px;
+}
.logo-item {
height: 72px;
border-radius: 6px;
@@ -796,10 +984,13 @@ function openBackend() {
justify-content: center;
cursor: pointer;
transition: all 0.18s;
- font-size: 15px;
- font-weight: 600;
- color: #3f5fd4;
- gap: 8px;
+ position: relative;
+ overflow: hidden;
+ img {
+ max-width: 86%;
+ max-height: 82%;
+ object-fit: contain;
+ }
&:hover {
border-color: #5979ea;
background: #eef2ff;
@@ -808,17 +999,30 @@ function openBackend() {
border-color: #5979ea;
box-shadow: 0 0 0 3px rgba(89, 121, 234, 0.18);
}
- .mk {
- width: 26px;
- height: 26px;
- border-radius: 7px;
- background: linear-gradient(135deg, #5979ea, #7d9bf5);
- color: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 14px;
- }
+}
+.wall-item {
+ position: relative;
+}
+.del {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: rgba(0, 0, 0, 0.45);
+ color: #fff;
+ font-size: 12px;
+ font-style: normal;
+ line-height: 18px;
+ text-align: center;
+ cursor: pointer;
+ display: none;
+ z-index: 2;
+}
+.logo-item:hover .del,
+.wall-item:hover .del {
+ display: block;
}
/* 边栏设置:拖拽示意列表 */