- index.html 插 A 类 loader:唯一接入点,FOUC 门/manifest 预取/主题 link 追加与排序 全由通道 yh-theme-init.js 承担,主题升级走 /static 通道,本应用无需重新构建 - vite.config 加 /static dev 代理(YH_THEME_PROXY_TARGET 可覆盖,默认线上通道) - host.config 回写 __APP_CONFIG__.serviceHost(仅同源):init 的 appearance 请求有硬性 同源限制,跨源会被直接跳过;本地 dev 保持空串走同源 /api 代理 - 顶栏头像接平台键 yh-avatar,监听 yh-appearance-updated,userInfo 兜底 - view-ui-plus 声明锁 1.3.24(对齐通道皮肤生成版本)
This commit is contained in:
@@ -3,6 +3,10 @@
|
|||||||
# ⚠️ VITE_* 仅本地开发生效。
|
# ⚠️ VITE_* 仅本地开发生效。
|
||||||
# API 走 /api 同源相对路径(vite proxy 转发到本地后端 8105),
|
# API 走 /api 同源相对路径(vite proxy 转发到本地后端 8105),
|
||||||
# 规避后端 CAS 过滤器不回 CORS 头导致的跨域拦截;SSO/基础平台用真实环境
|
# 规避后端 CAS 过滤器不回 CORS 头导致的跨域拦截;SSO/基础平台用真实环境
|
||||||
|
# 主题通道(A 类免插件接入 §2):index.html 的 loader 与 vite.config.ts 的 /static 代理已就位,
|
||||||
|
# 本地 dev 的 /static/** 由代理兜到线上通道;联调本地通道源时用环境变量覆盖代理目标:
|
||||||
|
# YH_THEME_PROXY_TARGET=http://localhost:9527 npm run dev
|
||||||
|
|
||||||
VITE_BASE_HOST=http://base.yunhaixingtong.com
|
VITE_BASE_HOST=http://base.yunhaixingtong.com
|
||||||
VITE_SSO_HOST=http://sso.yunhaixingtong.com
|
VITE_SSO_HOST=http://sso.yunhaixingtong.com
|
||||||
# 留空 → serviceHost 走同源相对 /api(经 vite 代理到本地后端 8105);填绝对地址会绕过代理直连该域
|
# 留空 → serviceHost 走同源相对 /api(经 vite 代理到本地后端 8105);填绝对地址会绕过代理直连该域
|
||||||
|
|||||||
+20
@@ -29,6 +29,26 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<!-- 主题通道引导(A 类免插件接入,见 YschTheme《管理端主题远程化与公共静态通道方案》§11):
|
||||||
|
唯一接入点。不装 npm 依赖、不改构建配置;FOUC 门、manifest 预取、主题 <link> 追加与
|
||||||
|
排序(晚于 viewuiplus.css)全由通道 yh-theme-init.js 承担,主题升级走 /static 通道,
|
||||||
|
本应用无需重新构建。
|
||||||
|
origin 零写死,优先级四档:__APP_CONFIG__.staticHost(专用通道覆盖)
|
||||||
|
> __APP_CONFIG__.baseHost(env-config 在线维护,换域名改配置即生效)
|
||||||
|
> %VITE_THEME_HOST%(仅 dev 注入的跨源直连,需通道已开 CORS)
|
||||||
|
> 同源相对 "/static/…"(生产常态:本应用部署在 base 域 /<app>/front/,与通道同源;
|
||||||
|
本地 dev 由 vite.config 的 /static 代理兜到通道)。
|
||||||
|
document.write 是有意为之:保证 parser-blocking,让档位类与 FOUC 门先于首帧;
|
||||||
|
build 不读 .env,占位符留字面量 → 判首字符 % 置空 → 产物零环境值烧入。 -->
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var c = window.__APP_CONFIG__ || {};
|
||||||
|
var themeHost = "%VITE_THEME_HOST%";
|
||||||
|
if (themeHost.charAt(0) === "%") themeHost = "";
|
||||||
|
var host = String(c.staticHost || c.baseHost || themeHost).replace(/\/+$/, "");
|
||||||
|
document.write('<script src="' + host + '/static/js/yh-theme-init.js"><\/script>');
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<title><%- title %></title>
|
<title><%- title %></title>
|
||||||
<link rel="stylesheet" href="./libs/ztree/zTreeStyle.css" />
|
<link rel="stylesheet" href="./libs/ztree/zTreeStyle.css" />
|
||||||
<script src="./libs/jquery.min.js"></script>
|
<script src="./libs/jquery.min.js"></script>
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
"pdfjs-dist": "^4.10.38",
|
"pdfjs-dist": "^4.10.38",
|
||||||
"pinia": "^2.1.4",
|
"pinia": "^2.1.4",
|
||||||
"sass": "^1.64.1",
|
"sass": "^1.64.1",
|
||||||
"view-ui-plus": "^1.3.16",
|
"view-ui-plus": "1.3.24",
|
||||||
"vue": "^3.4.19",
|
"vue": "^3.4.19",
|
||||||
"vue-router": "4",
|
"vue-router": "4",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
|
|||||||
@@ -107,4 +107,30 @@ if (ishttps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回写 __APP_CONFIG__.serviceHost(仅同源场景)——云海子 A 类接入 §3 外观三键。
|
||||||
|
* 主题通道 yh-theme-init.js 的 appearance 请求有硬性同源限制:
|
||||||
|
* if (!url || !appearanceSameOrigin(url)) return; → 跨源会被直接跳过、连请求都不发。
|
||||||
|
* 本地 dev:serviceHost 是 VITE_SERVICE_HOST 的绝对地址(跨源),此时不能回写,
|
||||||
|
* 保持空串让 init 走同源 "/api/appearance",由 vite 代理转发到后端。
|
||||||
|
* 生产:serviceHost = 同域 ctx(https://base.../<contextPath>),同源,回写后 init 请求正确前缀。
|
||||||
|
* 已有 env-config 下发值时不动(以平台配置为准)。
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
const w = window as any;
|
||||||
|
w.__APP_CONFIG__ = w.__APP_CONFIG__ || {};
|
||||||
|
if (!w.__APP_CONFIG__.serviceHost) {
|
||||||
|
const candidate = conf.serviceHost || "";
|
||||||
|
let sameOrigin = !candidate;
|
||||||
|
try {
|
||||||
|
sameOrigin = new URL(candidate, window.location.href).origin === window.location.origin;
|
||||||
|
} catch {
|
||||||
|
sameOrigin = false;
|
||||||
|
}
|
||||||
|
if (sameOrigin) w.__APP_CONFIG__.serviceHost = candidate;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* 极端环境无 window:忽略,保持同源兜底 */
|
||||||
|
}
|
||||||
|
|
||||||
export default conf;
|
export default conf;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onBeforeMount, watch, computed, inject } from "vue";
|
import { ref, onBeforeMount, watch, computed, inject, onMounted, onBeforeUnmount } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { Message } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
import { normalizeModList, flattenMenuNodes } from "@/utils/menuRoute";
|
import { normalizeModList, flattenMenuNodes } from "@/utils/menuRoute";
|
||||||
@@ -19,6 +19,25 @@ const ssoHost = computed(() => props.layoutData?.ssoHost || "");
|
|||||||
const appName = computed(() => props.layoutData?.appName || "家校宿舍通知");
|
const appName = computed(() => props.layoutData?.appName || "家校宿舍通知");
|
||||||
const appList = computed(() => props.layoutData?.appList || []);
|
const appList = computed(() => props.layoutData?.appList || []);
|
||||||
const userInfo = computed(() => props.layoutData?.userInfo || { name: "管理员", avatar: "" });
|
const userInfo = computed(() => props.layoutData?.userInfo || { name: "管理员", avatar: "" });
|
||||||
|
/* 头像:平台键 yh-avatar(yh-theme-init 外观补齐段写入)优先,本仓 userInfo 兜底;
|
||||||
|
监听平台事件重读,不轮询、不手写清理(清键归 init,规范 §3) */
|
||||||
|
const yhAvatar = ref("");
|
||||||
|
const readYhAvatar = () => {
|
||||||
|
let v: string | null = null;
|
||||||
|
try {
|
||||||
|
v = localStorage.getItem("yh-avatar");
|
||||||
|
} catch {
|
||||||
|
v = null;
|
||||||
|
}
|
||||||
|
yhAvatar.value = v || "";
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
readYhAvatar();
|
||||||
|
document.addEventListener("yh-appearance-updated", readYhAvatar);
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => document.removeEventListener("yh-appearance-updated", readYhAvatar));
|
||||||
|
const avatarUrl = computed(() => yhAvatar.value || userInfo.value?.avatar || "");
|
||||||
|
|
||||||
const isAlone = computed(() => props.layoutData?.isAlone ?? false);
|
const isAlone = computed(() => props.layoutData?.isAlone ?? false);
|
||||||
const modList = computed(() => props.layoutData?.modList || []);
|
const modList = computed(() => props.layoutData?.modList || []);
|
||||||
|
|
||||||
@@ -244,7 +263,7 @@ function linkHomePage() {
|
|||||||
<h4 class="fl align-middle">{{ appName }}</h4>
|
<h4 class="fl align-middle">{{ appName }}</h4>
|
||||||
<div class="school-bg"></div>
|
<div class="school-bg"></div>
|
||||||
<div class="header-aside fr">
|
<div class="header-aside fr">
|
||||||
<img :src="userInfo.avatar" class="fl align-middle" @click="linkUserInfo" />
|
<img :src="avatarUrl" class="fl align-middle" @click="linkUserInfo" />
|
||||||
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
|
<a class="name fl align-middle text-ellipsis" href="javascript:void(0);" @click="linkUserInfo">
|
||||||
{{ userInfo.name }}
|
{{ userInfo.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
host: "localhost",
|
host: "localhost",
|
||||||
port: 8078,
|
port: 8078,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
// 主题通道本地联调(A 类接入 §2.2):默认走线上通道;联调本地通道源用
|
||||||
|
// YH_THEME_PROXY_TARGET=http://localhost:9527 npm run dev
|
||||||
|
"/static": {
|
||||||
|
target: process.env.YH_THEME_PROXY_TARGET || "http://base.yunhaixingtong.com",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
// dev 正式模式:serviceHost 为空(同源相对路径 /api/...),经此代理转发到本地后端
|
// dev 正式模式:serviceHost 为空(同源相对路径 /api/...),经此代理转发到本地后端
|
||||||
// (后端 CAS 过滤器对 /api/* 不回 CORS 头,浏览器直连会被拦,必须同源代理)
|
// (后端 CAS 过滤器对 /api/* 不回 CORS 头,浏览器直连会被拦,必须同源代理)
|
||||||
// 宿舍通知后端:本地 DormitoryNotice(server.port=8105,无 context-path)
|
// 宿舍通知后端:本地 DormitoryNotice(server.port=8105,无 context-path)
|
||||||
|
|||||||
Reference in New Issue
Block a user