feat(apply): 深链 from 参数解析 + 顶栏「返回门户」(T4)——site.ts 新增 resolveFrom(仅 http/https 且域名属本部署可信域,防伪协议/外域注入;session 级内存不落 localStorage),Apply.load 在 resolveSite 后调用,App.vue nav 按需渲染返回门户链接(nav 为主色底 header,白字+分隔线与既有风格一致)
This commit is contained in:
@@ -12,9 +12,12 @@ import { onMounted, ref } from "vue";
|
||||
import { baseApi } from "@/api/module/base";
|
||||
import config from "@/assets/config/host.config";
|
||||
import { applyTheme, resolveTheme, type ThemeManifest } from "@/utils/theme";
|
||||
import { useSite } from "@/utils/site";
|
||||
|
||||
const userName = ref("");
|
||||
const theme = ref<ThemeManifest>(resolveTheme(null));
|
||||
// CMS 指南页来路(T4 回跳闭环):仅 /#/apply 深链带合法 from 参数时非空
|
||||
const { portalFrom } = useSite();
|
||||
|
||||
onMounted(async () => {
|
||||
// 皮肤先应用(首屏观感),再拉用户信息——用户信息失败不影响办理页面使用
|
||||
@@ -41,6 +44,7 @@ onMounted(async () => {
|
||||
<nav class="sh-nav">
|
||||
<router-link to="/hall">办事大厅</router-link>
|
||||
<router-link to="/my">我的办件</router-link>
|
||||
<a v-if="portalFrom" :href="portalFrom" class="sh-back-portal">返回门户</a>
|
||||
</nav>
|
||||
<span class="sh-user" v-if="userName">{{ userName }}</span>
|
||||
</header>
|
||||
|
||||
@@ -80,6 +80,12 @@ body {
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
font-weight: 600;
|
||||
}
|
||||
/* 返回门户(T4 回跳闭环):外跳文字链,与 nav 同风格白字,加分隔线区分站内导航。
|
||||
方案原文写 var(--sh-primary),但 nav 位于主色底 header 上会隐形,故取 nav 现有风格。 */
|
||||
.sh-header .sh-nav a.sh-back-portal {
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 0;
|
||||
}
|
||||
.sh-user {
|
||||
margin-left: 16px;
|
||||
font-size: 13px;
|
||||
|
||||
+25
-1
@@ -13,6 +13,8 @@ import config from "@/assets/config/host.config";
|
||||
*/
|
||||
const currentSiteCode = ref<string>(localStorage.getItem("svchall.public.site") || "");
|
||||
const lastItemCode = ref<string>(localStorage.getItem("svchall.public.item") || "");
|
||||
/** CMS 指南页来路(session 级内存,不落 localStorage——回跳是"这一次来的路",不是偏好) */
|
||||
const portalFrom = ref<string>("");
|
||||
|
||||
export function useSite() {
|
||||
/** 从 query / 运行时配置 / 本地记忆依次解析站点,命中即记住 */
|
||||
@@ -29,6 +31,28 @@ export function useSite() {
|
||||
return hit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并校验 from 参数(CMS 指南页深链带回,T4 回跳闭环):
|
||||
* 仅接受 http/https 且 hostname 属于本部署可信域(当前页域名或 baseHost)的 URL,否则忽略——
|
||||
* 防 javascript: 伪协议与外域注入。
|
||||
*/
|
||||
function resolveFrom(queryFrom?: unknown): string {
|
||||
const raw = typeof queryFrom === "string" ? queryFrom.trim() : "";
|
||||
if (!raw) return "";
|
||||
try {
|
||||
const u = new URL(raw);
|
||||
if (u.protocol !== "http:" && u.protocol !== "https:") return "";
|
||||
const mine = location.hostname;
|
||||
const base = String(config.baseHost || "");
|
||||
const baseHost = base ? new URL(base).hostname : "";
|
||||
if (u.hostname !== mine && (!baseHost || u.hostname !== baseHost)) return "";
|
||||
portalFrom.value = raw;
|
||||
} catch {
|
||||
// 非法 URL 当没有
|
||||
}
|
||||
return portalFrom.value;
|
||||
}
|
||||
|
||||
function setSite(siteCode: string) {
|
||||
currentSiteCode.value = siteCode;
|
||||
if (siteCode) {
|
||||
@@ -44,5 +68,5 @@ export function useSite() {
|
||||
}
|
||||
}
|
||||
|
||||
return { currentSiteCode, lastItemCode, resolveSite, setSite, rememberItem };
|
||||
return { currentSiteCode, lastItemCode, portalFrom, resolveSite, resolveFrom, setSite, rememberItem };
|
||||
}
|
||||
|
||||
+3
-1
@@ -19,7 +19,7 @@ import { useSite } from "@/utils/site";
|
||||
import DynamicForm from "@/components/DynamicForm.vue";
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { resolveSite, rememberItem } = useSite();
|
||||
const { resolveSite, resolveFrom, rememberItem } = useSite();
|
||||
|
||||
/** 轮询参数:代发起 + 回调一般在 1s 内完成,给到 ~9s 足够,避免用户盯着"待提交"发懵 */
|
||||
const POLL_TIMES = 12;
|
||||
@@ -37,6 +37,8 @@ const schemaJson = computed(() => (item.value?.schemaJson as string) || "");
|
||||
|
||||
async function load() {
|
||||
siteCode.value = resolveSite(route.query.siteCode);
|
||||
// CMS 指南页来路(T4):校验通过即记入 session,App.vue 顶栏渲染"返回门户"
|
||||
resolveFrom(route.query.from);
|
||||
const itemCode = String(route.query.itemCode || "");
|
||||
if (!siteCode.value || !itemCode) {
|
||||
loadError.value = "缺少 siteCode/itemCode 参数";
|
||||
|
||||
Reference in New Issue
Block a user