Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5b39b96c6 | ||
|
|
f508924ae8 | ||
|
|
a4f954d3b8 | ||
|
|
41b79566c1 | ||
|
|
f636075a5e |
@@ -1,5 +1,5 @@
|
||||
# 开发环境 (dev):仅 vite dev --mode dev 加载;build 产物不读任何 .env.*
|
||||
# (.env.base/.env.ld/.env.prod 已删,线上由 /api/env-config?target=front 运行时下发)。
|
||||
# (.env.base/.env.ld/.env.prod/.env.mock 已删,线上由 /api/env-config?target=hall 运行时下发)。
|
||||
# ⚠️ VITE_* 仅本地开发生效。
|
||||
# API 走 /api 同源相对路径(vite proxy 转发到本地后端 8086),
|
||||
# 规避后端 CAS 过滤器不回 CORS 头导致的跨域拦截;SSO/基础平台用真实环境
|
||||
@@ -11,5 +11,6 @@ VITE_ZHUANXIANG_HOST=https://base.bestsch.com/BschZncpd
|
||||
VITE_IS_MOCK=false
|
||||
|
||||
# 本地开发:站点标识与皮肤包(线上由 env-config 下发,build 产物不读 .env)
|
||||
VITE_SITE_CODE=yhms
|
||||
# siteCode=demo:本地 bsch_cms 的演示站点(与 CMS 本地种子数据对应)
|
||||
VITE_SITE_CODE=demo
|
||||
VITE_THEME=default
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# 本地纯前端调试模式:仅 vite dev --mode mock 加载(build 产物不读任何 .env.*,
|
||||
# 线上由 /api/env-config?target=front 运行时下发)。后端 SSO/base 不可达时启用内置 mock
|
||||
# 启动:npm run dev:mock
|
||||
VITE_BASE_HOST=
|
||||
VITE_SSO_HOST=
|
||||
VITE_SERVICE_HOST=http://localhost:8101
|
||||
VITE_ZIYUANKU_HOST=
|
||||
VITE_ZHUANXIANG_HOST=
|
||||
VITE_IS_MOCK=true
|
||||
@@ -89,7 +89,7 @@ push 到 `main` → gitea CI(`.gitea/workflows/build.yml`):`npm run build`
|
||||
**首次上线前**需在 projman 应用 `svc-hall` 下新增前端目标 `hall`(路径片段 `hall`),
|
||||
并在配置管理里补 `env-config-hall.json`(`siteCode`、`theme`、`baseHost`、`ssoHost`),
|
||||
再把 CMS 站点的 `svc_hall_base` 指向新前端(`https://<域名>/svc-hall/hall`)。详见
|
||||
`docs/公共端上线与projman接入说明.md`。
|
||||
`../docs/用户面(hall)上线与projman接入说明.md`。
|
||||
|
||||
## 契约纪律
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
//铃铛debug
|
||||
window.conf = {
|
||||
baseHost: "http://basedebug.bestsch.com",
|
||||
ssoHost: "http://ssodebug.bestsch.com",
|
||||
serviceHost: "http://basedebug.bestsch.com/QualityEvaluate",
|
||||
};
|
||||
+4
-4
@@ -8,15 +8,15 @@
|
||||
<!-- 运行时配置引导:在任何应用模块执行前同步拉取 /<contextPath>/api/env-config
|
||||
(后端免登录端点,projman 配置管理在线维护),替代生产构建环境变量。
|
||||
必须同步:host.config.ts 在模块求值期就读 window.__APP_CONFIG__;失败/本地开发
|
||||
(根路径,段数不足 2)则不设置:本地走 .env.dev/.env.mock(VITE_*),线上走同域推导兜底。
|
||||
target = URL 第 2 段目录名:本应用产物部署于 /{contextPath}/public/,故为 public,
|
||||
对应 projman 前端目标名 public(文件 /app/config/env-config-public.json)。
|
||||
(根路径,段数不足 2)则不设置:本地走 .env.dev(VITE_*),线上走同域推导兜底。
|
||||
target = URL 第 2 段目录名:本应用产物部署于 /{contextPath}/hall/,故为 hall,
|
||||
对应 projman 前端目标名 hall(文件 /app/config/env-config-hall.json)。
|
||||
注意:同步 XHR 不支持 timeout 属性(赋值会抛 InvalidAccessError),依赖内网快速失败 -->
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var seg = location.pathname.split("/").filter(Boolean);
|
||||
if (seg.length < 2) return; // 本地开发(根路径):走 .env.dev / .env.mock
|
||||
if (seg.length < 2) return; // 本地开发(根路径):走 .env.dev
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/" + seg[0] + "/api/env-config?target=" + encodeURIComponent(seg[1]), false);
|
||||
xhr.send(null);
|
||||
|
||||
Generated
+77
-493
File diff suppressed because it is too large
Load Diff
@@ -6,11 +6,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --mode dev",
|
||||
"dev:mock": "vite --mode mock",
|
||||
"build": "vite build --mode base",
|
||||
"build:base": "vite build --mode base",
|
||||
"build:ld": "vite build --mode ld",
|
||||
"build:prod": "vite build --mode prod",
|
||||
"preview": "vite preview",
|
||||
"api:gen": "alova gen -f"
|
||||
},
|
||||
@@ -24,12 +21,10 @@
|
||||
"axios": "^0.24.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"pinia": "^2.1.4",
|
||||
"sass": "^1.64.1",
|
||||
"view-ui-plus": "^1.3.16",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
"vue": "^3.4.19",
|
||||
"vue-router": "4",
|
||||
"vuedraggable": "^4.1.0"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 186 KiB |
+45
-3
@@ -5,20 +5,31 @@
|
||||
* 与后台前端(front/)刻意不同:不渲染平台模块菜单(app_module 是管理端契约),
|
||||
* 只做两个入口——办事大厅、我的办件(详情/办理页从列表进入)。
|
||||
*
|
||||
* 皮肤:启动时按运行时配置(env-config 的 theme/themeParams)应用皮肤包;
|
||||
* 未配置 → default;未注册 → default(整页回退,见 utils/theme.ts)。
|
||||
* 皮肤三层优先级(T3 弱联动,2026-10-03):
|
||||
* CMS 站点推荐(/api/hall/site 的 theme/themeParams,经 utils/cmsTheme 映射;
|
||||
* stale-while-revalidate——localStorage 缓存先画、后台刷新,失败静默)
|
||||
* > env-config 部署级皮肤 > default。
|
||||
* 推荐值、不保证生效:任一层缺失/非法都回落下一层(utils/theme.ts)。
|
||||
*/
|
||||
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 { toHallSkin } from "@/utils/cmsTheme";
|
||||
import { useSite } from "@/utils/site";
|
||||
import { hallApi } from "@/api/module/svc";
|
||||
|
||||
const userName = ref("");
|
||||
const theme = ref<ThemeManifest>(resolveTheme(null));
|
||||
// CMS 指南页来路(T4 回跳闭环):仅 /#/apply 深链带合法 from 参数时非空
|
||||
const { portalFrom, currentSiteCode } = useSite();
|
||||
|
||||
onMounted(async () => {
|
||||
// 皮肤先应用(首屏观感),再拉用户信息——用户信息失败不影响办理页面使用
|
||||
// ① 基线:env-config 部署级皮肤(现状语义不变,先应用保证首屏观感)
|
||||
theme.value = applyTheme(config.theme, config.themeParams);
|
||||
// ② 站点推荐:CMS 站点主题(弱联动)——缓存先画,后台刷新
|
||||
applySiteThemeRecommend();
|
||||
// 用户信息失败不影响办理页面使用
|
||||
try {
|
||||
const user: any = await baseApi.getUserInfo().send();
|
||||
userName.value = user?.name || "";
|
||||
@@ -29,6 +40,36 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 站点主题推荐应用(stale-while-revalidate):
|
||||
* 未登录时该请求会 302 跳 CAS(与 items 同行为,页面本来就要登录);
|
||||
* 任何失败静默保留现皮肤——推荐、不保证生效,绝不阻塞首屏。
|
||||
*/
|
||||
async function applySiteThemeRecommend() {
|
||||
const code = currentSiteCode.value; // useSite() 模块级 ref,当前视图 onMounted 已 resolve
|
||||
if (!code) return;
|
||||
const cacheKey = `svchall.public.siteTheme.${code}`;
|
||||
// ① stale:同步读缓存立即应用(二次访问无闪色)
|
||||
try {
|
||||
const cached = JSON.parse(localStorage.getItem(cacheKey) || "null");
|
||||
const stale = toHallSkin(cached);
|
||||
if (stale) theme.value = applyTheme(stale.name, stale.params);
|
||||
} catch {
|
||||
// 坏缓存当没有
|
||||
}
|
||||
// ② revalidate:后台刷新(失败静默保留现皮肤——推荐、不保证生效)
|
||||
try {
|
||||
const info: any = await hallApi.site(code).send();
|
||||
const pick = toHallSkin({ theme: info?.theme, themeParams: info?.themeParams });
|
||||
localStorage.setItem(cacheKey, JSON.stringify({
|
||||
theme: info?.theme || null, themeParams: info?.themeParams || null,
|
||||
}));
|
||||
if (pick) theme.value = applyTheme(pick.name, pick.params);
|
||||
} catch (e) {
|
||||
console.warn("[public-hall] 站点主题获取失败,沿用当前皮肤", e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -41,6 +82,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>
|
||||
|
||||
@@ -63,6 +63,10 @@ class HallApi {
|
||||
filters(siteCode: string) {
|
||||
return get<HallFilters>(`${HALL}/filters`, { siteCode });
|
||||
}
|
||||
/** 站点信息(含 CMS 主题 raw;主题弱联动数据源,失败静默——见 App.vue 应用链,T3) */
|
||||
site(siteCode: string) {
|
||||
return get(`${HALL}/site`, { siteCode });
|
||||
}
|
||||
}
|
||||
|
||||
class ApplyApi {
|
||||
|
||||
@@ -3,10 +3,10 @@ import { BaseConf } from "@/types/Basics";
|
||||
/**
|
||||
* 配置优先级(高 → 低):
|
||||
* ① window.__APP_CONFIG__:线上运行时配置,index.html 引导脚本在应用模块加载前同步拉取
|
||||
* /<contextPath>/api/env-config?target=<部署目录名>(本应用部署于 /{contextPath}/front/,
|
||||
* 故为 front,对应后端键 yh.env-config.source.front,projman 配置管理维护)写入
|
||||
* ② import.meta.env.VITE_*:仅本地开发/演示(vite dev --mode dev 读 .env.dev、--mode mock 读 .env.mock);
|
||||
* build 产物不读任何 .env.*(.env.base/.env.ld/.env.prod 已删),VITE_* 恒为 undefined,
|
||||
* /<contextPath>/api/env-config?target=<部署目录名>(本应用部署于 /{contextPath}/hall/,
|
||||
* 故为 hall,对应 projman 前端目标 hall / 文件 /app/config/env-config-hall.json)写入
|
||||
* ② import.meta.env.VITE_*:仅本地开发/演示(vite dev --mode dev 读 .env.dev);
|
||||
* build 产物不读任何 .env.*(.env.base/.env.ld/.env.prod/.env.mock 已删),VITE_* 恒为 undefined,
|
||||
* 本层自动跳过,产物不含任何环境值
|
||||
* ③ 同域推导兜底:env-config 未配置/拉取失败时框架至少能渲染并调通同源 API;
|
||||
* ssoHost 为空 → 登出跳转不可用——与后端未就绪状态一致,可接受。
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 186 KiB |
@@ -98,18 +98,7 @@ h4.subtitle {
|
||||
}
|
||||
}
|
||||
|
||||
.table-spin-load {
|
||||
width: 60px;
|
||||
height: 90px;
|
||||
padding-top: 60px;
|
||||
background-image: url(../image/loading.gif);
|
||||
background-size: 60px 60px;
|
||||
background-position: top center;
|
||||
background-repeat: no-repeat;
|
||||
border: none;
|
||||
color: #577afb;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.ivu-table-cell-tree {
|
||||
margin-right: 4px;
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
--sh-text: #24292f;
|
||||
--sh-text-weak: #6b7280;
|
||||
--sh-border: #e6e9ef;
|
||||
/* 语义色令牌(T6 令牌化:此前 ApplyDetail/tag 各写死一份) */
|
||||
--sh-success: #1f9254;
|
||||
--sh-danger: #d03050;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -80,6 +83,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;
|
||||
@@ -130,13 +139,13 @@ body {
|
||||
background: rgba(26, 92, 176, 0.06);
|
||||
}
|
||||
.sh-tag-success {
|
||||
color: #1f9254;
|
||||
border-color: #1f9254;
|
||||
color: var(--sh-success);
|
||||
border-color: var(--sh-success);
|
||||
background: rgba(31, 146, 84, 0.07);
|
||||
}
|
||||
.sh-tag-error {
|
||||
color: #d03050;
|
||||
border-color: #d03050;
|
||||
color: var(--sh-danger);
|
||||
border-color: var(--sh-danger);
|
||||
background: rgba(208, 48, 80, 0.07);
|
||||
}
|
||||
.sh-tag-warning {
|
||||
@@ -154,3 +163,23 @@ body {
|
||||
background: var(--sh-primary-dark);
|
||||
border-color: var(--sh-primary-dark);
|
||||
}
|
||||
|
||||
/* view-ui 主色跟随皮肤(克制补丁:仅 hall 实际出现的交互态,2026-10-03 T3 联动配套;
|
||||
未用到的 Tabs/Switch/ivu-btn-text 不加,不做全组件库 retheme) */
|
||||
.dyn-file-btn:hover .dyn-file-btn-inner {
|
||||
color: var(--sh-primary);
|
||||
}
|
||||
.ivu-checkbox-checked .ivu-checkbox-inner,
|
||||
.ivu-radio-checked .ivu-radio-inner {
|
||||
border-color: var(--sh-primary);
|
||||
}
|
||||
.ivu-checkbox-checked .ivu-checkbox-inner:after,
|
||||
.ivu-radio-checked .ivu-radio-inner:after {
|
||||
background: var(--sh-primary);
|
||||
}
|
||||
.ivu-page-item-active a {
|
||||
color: var(--sh-primary);
|
||||
}
|
||||
.ivu-modal-confirm-head-icon-confirm .ivu-icon-ios-alert {
|
||||
color: var(--sh-accent);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// utils 工具// 颜色
|
||||
// (2026-10-03 T6:_varible.scss 已删——本文件仅剩 4 个色值依赖它,就地内联,
|
||||
// 产物 CSS 与删除前逐字节一致;原文件唯一 import 形式为不带下划线的 "./varible")
|
||||
$color-error: #e8684a;
|
||||
$color-success: #45b972;
|
||||
$color-warning: #e9b954;
|
||||
$color-primary: #5979ea;
|
||||
|
||||
@import "./varible";
|
||||
.bg-error {
|
||||
background-color: $color-error !important;
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*scss变量*/
|
||||
|
||||
// 主题色
|
||||
$color-error: #e8684a;
|
||||
$color-success: #45b972;
|
||||
$color-warning: #e9b954;
|
||||
$color-primary: #5979ea;
|
||||
|
||||
// 文字色
|
||||
$color-black: #404042;
|
||||
$color-dark: #333;
|
||||
$color-deep: #555;
|
||||
$color-pl: #999999;
|
||||
$color-weak: #b3b3b3;
|
||||
$color-white: #fff;
|
||||
|
||||
// 背景色
|
||||
$bg-bar: #f9f9f9;
|
||||
$bg-page: #f3f3f3;
|
||||
$bg-page-light: #f9f9f9;
|
||||
|
||||
$border-color: #e9e9e9;
|
||||
@@ -399,8 +399,8 @@ watch(
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
.dyn-file-btn:hover .dyn-file-btn-inner {
|
||||
color: #2d8cf0;
|
||||
border-color: #2d8cf0;
|
||||
color: var(--sh-primary);
|
||||
border-color: var(--sh-primary);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -3,9 +3,11 @@ import type { RouteRecordRaw } from "vue-router";
|
||||
/**
|
||||
* 公共办事大厅路由(用户面;后台管理路由在 svc-hall/front,两仓互不引用)。
|
||||
* 四个页面:事项大厅(入口)/ 在线办理 / 我的办件 / 办件详情。
|
||||
* 全部走 hash 路由(部署于 /{contextPath}/public/,hash 免服务端 rewrite)。
|
||||
* 全部走 hash 路由(部署于 /{contextPath}/hall/,hash 免服务端 rewrite)。
|
||||
* 参数页用 query 传参(与 CMS 指南页"我要办理"外链形态一致:
|
||||
* {svc_hall_base}/#/apply?siteCode=&itemCode=)——拼成路径参数会 no-match 空白页。
|
||||
* {svc_hall_base}/#/apply?siteCode=&itemCode=&from=<指南页绝对URL>;
|
||||
* from=T4 回跳闭环参数,hall 侧 resolveFrom 校验可信域后显示"返回门户")
|
||||
* ——拼成路径参数会 no-match 空白页。
|
||||
*/
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* 校园青绿——第二套皮肤:换令牌 + 换圆角 + 换列表布局(结构变体走 data-sh-* 钩子) */
|
||||
/* 校园青绿——第二套皮肤:换令牌 + 换圆角 + 换列表布局(结构变体走 data-sh-* 钩子)
|
||||
色值与 CMS campus-glow 同源(T3 弱联动对齐,2026-10-03),与 theme.json default 一致 */
|
||||
:root[data-theme="campus-green"] {
|
||||
--sh-primary: #0e8a6e;
|
||||
--sh-primary-dark: #0a6a55;
|
||||
--sh-accent: #d97706;
|
||||
--sh-page-bg: #f2f8f5;
|
||||
--sh-primary: #0e8a5f;
|
||||
--sh-primary-dark: #0a6244;
|
||||
--sh-accent: #ff7a1a;
|
||||
--sh-page-bg: #f4f7f5;
|
||||
--sh-radius: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"specVersion": 1,
|
||||
"name": "campus-green",
|
||||
"title": "校园青绿",
|
||||
"version": "1.0.0",
|
||||
"description": "第二套可切换皮肤:青绿主色 + 更宽松的圆角,用于验证皮肤包机制(新增皮肤=新增目录,宿主零改动)。",
|
||||
"version": "1.1.0",
|
||||
"description": "第二套可切换皮肤:青绿主色 + 更宽松的圆角(色值与 CMS campus-glow 同源,T3 弱联动对齐 2026-10-03)。",
|
||||
"params": [
|
||||
{ "key": "primary", "cssVar": "--sh-primary", "label": "主色", "type": "color", "default": "#0e8a6e" },
|
||||
{ "key": "primaryDark", "cssVar": "--sh-primary-dark", "label": "主色加深", "type": "color", "default": "#0a6a55" },
|
||||
{ "key": "accent", "cssVar": "--sh-accent", "label": "强调色", "type": "color", "default": "#d97706" },
|
||||
{ "key": "pageBg", "cssVar": "--sh-page-bg", "label": "页面底色", "type": "color", "default": "#f2f8f5" },
|
||||
{ "key": "primary", "cssVar": "--sh-primary", "label": "主色", "type": "color", "default": "#0e8a5f" },
|
||||
{ "key": "primaryDark", "cssVar": "--sh-primary-dark", "label": "主色加深", "type": "color", "default": "#0a6244" },
|
||||
{ "key": "accent", "cssVar": "--sh-accent", "label": "强调色", "type": "color", "default": "#ff7a1a" },
|
||||
{ "key": "pageBg", "cssVar": "--sh-page-bg", "label": "页面底色", "type": "color", "default": "#f4f7f5" },
|
||||
{
|
||||
"key": "listLayout",
|
||||
"label": "事项列表布局",
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* CMS 门户主题 → hall 皮肤的映射适配器(色值级弱联动,2026-10-03 方案 T3)。
|
||||
* 原则:CMS 主题是"推荐值"——未映射/字段缺失一律返回 null,调用方回落 env-config 皮肤;
|
||||
* 联动只对齐品牌色 token,不复刻 CMS 模板结构。
|
||||
* 契约来源:CMS /oapi/site/list raw 透传(SiteOapiController),键:theme、themeParams。
|
||||
*/
|
||||
|
||||
export interface CmsSiteThemeInfo {
|
||||
theme: string | null;
|
||||
themeParams: Record<string, string> | null;
|
||||
}
|
||||
|
||||
export interface HallSkinChoice {
|
||||
/** hall 皮肤名(一定已注册,未映射返回 null 整体回落) */
|
||||
name: string;
|
||||
/** 覆盖值(键必须命中目标皮肤的 params 声明,applyTheme 会再过滤校验) */
|
||||
params: Record<string, string>;
|
||||
}
|
||||
|
||||
/** CMS 皮肤包名 → hall 皮肤名(新增 CMS 主题时在此登记;不认识=回落) */
|
||||
const SKIN_MAP: Record<string, string> = {
|
||||
"default": "default",
|
||||
"campus-glow": "campus-green",
|
||||
"scholar-blue": "default",
|
||||
};
|
||||
|
||||
/** CMS default 主题 colorScheme 预设 → hall 参数(值译自 CMS default/theme.json presets 段) */
|
||||
const COLOR_SCHEMES: Record<string, Record<string, string>> = {
|
||||
"blue-white": { primary: "#2f6bd8", primaryDark: "#1d4fae", pageBg: "#f7f9fc" },
|
||||
"classic-red": { primary: "#b01a2e", primaryDark: "#7f1224", pageBg: "#f7f5f2" },
|
||||
"fresh-mint": { primary: "#17a2a2", primaryDark: "#0f7c7c", pageBg: "#f5fbfa" },
|
||||
"warm-kinder": { primary: "#e8722a", primaryDark: "#c25a1a", pageBg: "#fff8f0" },
|
||||
};
|
||||
|
||||
/** CMS themeParams 键 → hall 皮肤参数键(同名直传,bg 特译;accent 同名但仅部分 CMS 主题声明) */
|
||||
const PARAM_ALIASE: Record<string, string> = {
|
||||
primary: "primary",
|
||||
primaryDark: "primaryDark",
|
||||
accent: "accent",
|
||||
bg: "pageBg",
|
||||
};
|
||||
|
||||
export function toHallSkin(cms: CmsSiteThemeInfo | null | undefined): HallSkinChoice | null {
|
||||
if (!cms || !cms.theme) return null;
|
||||
const skin = SKIN_MAP[cms.theme];
|
||||
if (!skin) return null;
|
||||
const params: Record<string, string> = {};
|
||||
// ① colorScheme 预设打底(custom/未配=无)
|
||||
const scheme = cms.themeParams?.colorScheme;
|
||||
if (scheme && COLOR_SCHEMES[scheme]) Object.assign(params, COLOR_SCHEMES[scheme]);
|
||||
// ② 站点显式色值覆盖预设(同键后者赢)
|
||||
const tp = cms.themeParams || {};
|
||||
for (const [cmsKey, hallKey] of Object.entries(PARAM_ALIASE)) {
|
||||
const v = tp[cmsKey];
|
||||
if (typeof v === "string" && /^#[0-9a-fA-F]{3,8}$/.test(v)) params[hallKey] = v;
|
||||
}
|
||||
return { name: skin, params };
|
||||
}
|
||||
+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 参数";
|
||||
|
||||
@@ -325,7 +325,7 @@ onMounted(load);
|
||||
}
|
||||
.step-dot.active {
|
||||
background: var(--sh-primary);
|
||||
box-shadow: 0 0 0 3px rgba(26, 92, 176, 0.15);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--sh-primary) 15%, transparent);
|
||||
}
|
||||
.step-body {
|
||||
flex: 1;
|
||||
@@ -354,10 +354,10 @@ onMounted(load);
|
||||
font-weight: 500;
|
||||
}
|
||||
.step-done .assignee-name {
|
||||
color: #1f9254;
|
||||
color: var(--sh-success);
|
||||
}
|
||||
.step-reject .assignee-name {
|
||||
color: #d03050;
|
||||
color: var(--sh-danger);
|
||||
}
|
||||
.step-invalid {
|
||||
opacity: 0.55;
|
||||
|
||||
+2
-14
@@ -3,7 +3,6 @@ import { createHtmlPlugin } from "vite-plugin-html";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import path, { resolve } from "path";
|
||||
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
|
||||
import { viteMockServe } from "vite-plugin-mock";
|
||||
import ViteAutoImport from "unplugin-auto-import/vite";
|
||||
|
||||
import UnoCSS from "@unocss/vite";
|
||||
@@ -27,11 +26,8 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
||||
inject: "body-last", // 插入的位置
|
||||
customDomId: "__svg__icons__dom__", // svg的id
|
||||
}),
|
||||
viteMockServe({
|
||||
mockPath: "./src/assets/mock",
|
||||
// 仅 --mode mock 时启用 mock 中间件;dev/联调/正式均走真实后端
|
||||
localEnabled: mode === "mock",
|
||||
}),
|
||||
// vite-plugin-mock 已裁(2026-10-03 T6):mockPath 指向的 ./src/assets/mock 目录
|
||||
// 并不存在,dev:mock/.env.mock 一并移除——dev/联调/正式均走真实后端
|
||||
ViteAutoImport({
|
||||
imports: [
|
||||
"vue",
|
||||
@@ -108,14 +104,6 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
||||
},
|
||||
},
|
||||
},
|
||||
// css预处理器
|
||||
css: {
|
||||
// preprocessorOptions: {
|
||||
// scss: {
|
||||
// additionalData: '@import "./src/assets/styles/utils/_varible.scss";',
|
||||
// },
|
||||
// },
|
||||
},
|
||||
base: "./",
|
||||
build: {
|
||||
target: "modules", //设置最终构建的浏览器兼容目标 //es2015(编译成es5) | modules
|
||||
|
||||
Reference in New Issue
Block a user