feat(W4): 站点path全链路-portalUrlOf/previewPortalUrl纯函数,站点列/向导地址预览/预览frame sitePath,store携带path,mock嵌套路由,T-W4-16..18 绿51/51

This commit is contained in:
chenxing
2026-09-08 15:14:10 +08:00
parent 7b99ec398d
commit dedd4a6a3a
16 changed files with 297 additions and 37 deletions
+23 -5
View File
@@ -4,6 +4,7 @@ import { Message } from "view-ui-plus";
import { siteApi, themeApi } from "@/api/module/cms";
import { SiteVo, ThemeVo } from "@/types/cms";
import { formModelFromTheme, FormModelValue, mergeThemeParamsIntoConfigJson } from "@/utils/themeParamForm";
import { previewPortalUrl } from "@/utils/sitePath";
import ThemePicker from "@/components/theme/ThemePicker.vue";
import ThemeParamForm from "@/components/theme/ThemeParamForm.vue";
import ThemePreviewFrame from "@/components/theme/ThemePreviewFrame.vue";
@@ -42,13 +43,13 @@ const form = reactive({
const themeName = ref("default");
const themeParams = ref<FormModelValue>({});
const sourceSiteCode = ref<string>("");
const sourceSitePath = ref<string>("");
const saving = ref(false);
/** 内容源候选:启用中的站点(顶层/子站皆可作内容源) */
const sourceSites = computed(() => props.sites.filter(s => s.status === 1));
const selectedTheme = computed(() => themes.value.find(t => t.name === themeName.value));
const canPreview = computed(() => !!sourceSiteCode.value && !!themeName.value);
const canPreview = computed(() => !!sourceSitePath.value && !!themeName.value);
const previewTick = ref(0);
const previewParams = ref<FormModelValue>({});
@@ -60,6 +61,14 @@ const parentOptions = computed(() =>
: props.sites.filter(s => !s.parentSiteId)
);
/** W4(G5):门户地址预览——随上级与编码输入联动(子站=上级地址/编码;顶层=编码) */
const wizardParent = computed(() =>
form.parentSiteId ? props.sites.find(s => s.id === form.parentSiteId) : undefined
);
const previewUrl = computed(() =>
previewPortalUrl(wizardParent.value?.path ?? wizardParent.value?.code, form.code)
);
function debouncePreview() {
previewParams.value = { ...themeParams.value };
previewTick.value = Date.now();
@@ -79,7 +88,7 @@ watch(
if (!v) return;
step.value = 1;
Object.assign(form, { code: "", parentSiteId: props.lockParentId, schId: undefined, schName: "", name: "", status: 1, configJson: "" });
sourceSiteCode.value = sourceSites.value[0]?.code || "";
sourceSitePath.value = sourceSites.value[0]?.path || sourceSites.value[0]?.code || "";
if (!themes.value.length) await loadThemes();
themeName.value = "default";
const t = selectedTheme.value;
@@ -176,7 +185,7 @@ async function createSite() {
<div class="left">
<FormItem label="预览内容源" :label-width="90">
<Select v-model="sourceSiteCode" style="width: 260px">
<Option v-for="s in sourceSites" :key="s.id" :value="s.code">{{ s.name }}({{ s.code }})</Option>
<Option v-for="s in sourceSites" :key="s.id" :value="s.path || s.code">{{ s.name }}({{ s.path || s.code }})</Option>
</Select>
</FormItem>
<ThemeParamForm :theme="selectedTheme" v-model="themeParams" />
@@ -184,7 +193,7 @@ async function createSite() {
<div class="right">
<ThemePreviewFrame
v-if="canPreview"
:site-code="sourceSiteCode"
:site-path="sourceSitePath"
:theme="themeName"
:key="previewTick"
:theme-params="previewParams"
@@ -207,6 +216,10 @@ async function createSite() {
<FormItem label="站点名称" prop="name">
<Input v-model="form.name" :maxlength="128" placeholder="如 XX学校门户网站" style="width: 320px" />
</FormItem>
<FormItem label="门户地址预览">
<span class="portal-preview-url">{{ previewUrl || "输入编码后自动生成" }}</span>
<div class="pbu-tip">子站地址 = 上级地址 / 编码;创建后经站点管理「门户路径」列直达。</div>
</FormItem>
<FormItem label="上级站点">
<Select v-model="form.parentSiteId" :disabled="!!props.lockParentId" placeholder="空=顶层校站点;选主站则为院系子站" style="width: 320px">
<Option v-for="s in parentOptions" :key="s.id" :value="s.id">{{ s.name }}({{ s.code }})</Option>
@@ -288,6 +301,11 @@ async function createSite() {
.info-form {
margin-top: 4px;
}
.portal-preview-url {
color: #2d8cf0;
font-family: Consolas, Menlo, monospace;
word-break: break-all;
}
.cfg-form {
margin-top: 12px;
padding-top: 4px;