refactor: 清理废弃模块并重构管理端与全端代码
- 移除所有废弃的旧首页配置模块,包括destinationHero、demand相关、HomeExperience、vehicleService等表结构与代码,新增数据库迁移删除遗留表 - 重构管理端后台布局为标准RuoYi风格,新增Sidebar、Navbar、SettingsDrawer等组件,替换旧的过渡菜单实现 - 调整移动端响应断点为768px,更新布局相关测试用例,新增布局组件测试 - 删除OperationsTools发布/重置页面,移除/tools菜单入口,清理对应的API调用与测试代码 - 重构玩法模块编辑器:替换旧的图片手动输入框为图片上传组件,移除冗余的取消按钮 - 新增线索查询offset参数支持,完善查询参数处理逻辑 - 优化rbac菜单构建逻辑,新增可见性控制参数 - 修正管家编辑器提示文案,优化系统资源编辑器表单初始化逻辑 - 清理小程序端废弃的体验推荐组件与相关测试代码 - 更新文档与种子脚本,匹配新的API契约与使用说明
This commit is contained in:
1 parent
f6c5ad589f
commit
fbf2c772a8
63 files changed
+2272
-1991
No files matched your search
@@ -2,63 +2,76 @@
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import {
|
||||
createHomePlayRecommendation,
|
||||
createHomeTeamBuilding,
|
||||
createHomeWildArchive,
|
||||
createSiteConfigItem,
|
||||
deleteHomePlayRecommendation,
|
||||
deleteHomeTeamBuilding,
|
||||
deleteHomeWildArchive,
|
||||
deleteSiteConfigItem,
|
||||
getHomePlayRecommendations,
|
||||
getHomeTeamBuildings,
|
||||
getHomeWildArchives,
|
||||
getSiteConfig,
|
||||
getWanfa,
|
||||
reorderHomePlayRecommendations,
|
||||
reorderHomeTeamBuildings,
|
||||
reorderHomeWildArchives,
|
||||
reorderSiteConfigItems,
|
||||
updateHomePlayRecommendation,
|
||||
updateHomeTeamBuilding,
|
||||
updateHomeWildArchive,
|
||||
updateSiteConfigItem,
|
||||
} from "@/api/client";
|
||||
import HomeContentEditor, { type HomeContentDraft, type HomeContentKind } from "@/components/home/HomeContentEditor.vue";
|
||||
import SiteConfigEditor from "@/components/site-config/SiteConfigEditor.vue";
|
||||
import {
|
||||
compactSiteItemPayload,
|
||||
createEmptySiteItemDraft,
|
||||
getSiteModuleDefinition,
|
||||
siteItemDraft,
|
||||
siteModuleDefinitions,
|
||||
validateSiteItemPayload,
|
||||
} from "@/lib/site-config";
|
||||
import { compactSiteItemPayload, createEmptySiteItemDraft, getSiteModuleDefinition, siteItemDraft, validateSiteItemPayload } from "@/lib/site-config";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import type { SiteConfigItem, SiteItemPatch, SiteModule } from "@/types";
|
||||
import type { HomeTeamBuilding, HomeWanfaRecommendation, HomeWildArchive, SiteConfigItem, SiteItemPatch, WanfaCategory } from "@/types";
|
||||
|
||||
type SiteModule = "heroSlides" | "vehicleOptions";
|
||||
type HomeModule = SiteModule | HomeContentKind;
|
||||
|
||||
const auth = useAuthStore();
|
||||
const activeModule = ref<SiteModule>("heroSlides");
|
||||
const moduleItems = ref<Record<SiteModule, SiteConfigItem[]>>({
|
||||
heroSlides: [],
|
||||
destinationHero: [],
|
||||
demandHero: [],
|
||||
demandFeatureCards: [],
|
||||
demandForm: [],
|
||||
vehicleOptions: [],
|
||||
vehicleService: [],
|
||||
});
|
||||
const activeModule = ref<HomeModule>("heroSlides");
|
||||
const siteItems = ref<Record<SiteModule, SiteConfigItem[]>>({ heroSlides: [], vehicleOptions: [] });
|
||||
const homeItems = ref<Record<HomeContentKind, Array<HomeWanfaRecommendation | HomeTeamBuilding | HomeWildArchive>>>({ recommendation: [], team: [], wild: [] });
|
||||
const categories = ref<WanfaCategory[]>([]);
|
||||
const loading = ref(true);
|
||||
const loadError = ref<string | null>(null);
|
||||
const editorOpen = ref(false);
|
||||
const siteEditorOpen = ref(false);
|
||||
const editingId = ref<string | null>(null);
|
||||
const draft = ref<SiteItemPatch>({});
|
||||
const homeDraft = ref<HomeContentDraft>(emptyHomeDraft("recommendation"));
|
||||
const siteDraft = ref<SiteItemPatch>({});
|
||||
const saving = ref(false);
|
||||
const deletingId = ref<string | null>(null);
|
||||
const reorderingId = ref<string | null>(null);
|
||||
|
||||
const activeDefinition = computed(() => getSiteModuleDefinition(activeModule.value));
|
||||
const activeItems = computed(() => moduleItems.value[activeModule.value]);
|
||||
const isCreating = computed(() => editingId.value === null);
|
||||
const canCreate = computed(() => activeDefinition.value.singleton ? activeItems.value.length === 0 : true);
|
||||
const isSiteModule = computed(() => activeModule.value === "heroSlides" || activeModule.value === "vehicleOptions");
|
||||
const activeSiteModule = computed(() => activeModule.value as SiteModule);
|
||||
const activeHomeKind = computed(() => activeModule.value as HomeContentKind);
|
||||
const activeSiteItems = computed(() => siteItems.value[activeSiteModule.value]);
|
||||
const activeHomeItems = computed(() => homeItems.value[activeHomeKind.value]);
|
||||
const activeItems = computed(() => isSiteModule.value ? activeSiteItems.value : activeHomeItems.value);
|
||||
const activeDefinition = computed(() => getSiteModuleDefinition(activeSiteModule.value));
|
||||
const creating = computed(() => editingId.value === null);
|
||||
|
||||
function emptyHomeDraft(kind: HomeContentKind): HomeContentDraft {
|
||||
if (kind === "recommendation") return { categoryId: "", isActive: true, sortOrder: 0 };
|
||||
if (kind === "team") return { tag: "", title: "", description: "", image: "", demandKeyword: "", detailSubtitle: "", detailParagraphs: [], isActive: true, sortOrder: 0 };
|
||||
return { title: "", image: "", images: [], demandKeyword: "", isActive: true, sortOrder: 0 };
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
loading.value = true;
|
||||
loadError.value = null;
|
||||
try {
|
||||
const config = await getSiteConfig();
|
||||
moduleItems.value = {
|
||||
heroSlides: config.heroSlides,
|
||||
destinationHero: config.destinationHero,
|
||||
demandHero: config.demandHero,
|
||||
demandFeatureCards: config.demandFeatureCards,
|
||||
demandForm: config.demandForm,
|
||||
vehicleOptions: config.vehicleOptions,
|
||||
vehicleService: config.vehicleService,
|
||||
};
|
||||
const [config, wanfa, recommendations, teamBuildings, wildArchives] = await Promise.all([getSiteConfig(), getWanfa(), getHomePlayRecommendations(), getHomeTeamBuildings(), getHomeWildArchives()]);
|
||||
siteItems.value = { heroSlides: config.heroSlides, vehicleOptions: config.vehicleOptions };
|
||||
categories.value = wanfa;
|
||||
homeItems.value = { recommendation: recommendations.items, team: teamBuildings.items, wild: wildArchives.items };
|
||||
} catch (error) {
|
||||
loadError.value = error instanceof Error ? error.message : "首页配置加载失败";
|
||||
} finally {
|
||||
@@ -66,205 +79,175 @@ async function loadConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
function startCreate() {
|
||||
if (!auth.hasPermission("admin:site-config") || !canCreate.value) return;
|
||||
editingId.value = null;
|
||||
draft.value = createEmptySiteItemDraft(activeModule.value, activeItems.value.length);
|
||||
editorOpen.value = true;
|
||||
}
|
||||
|
||||
function startEdit(item: SiteConfigItem) {
|
||||
if (!auth.hasPermission("admin:site-config")) return;
|
||||
editingId.value = item.id;
|
||||
draft.value = siteItemDraft(item);
|
||||
editorOpen.value = true;
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
if (saving.value) return;
|
||||
editorOpen.value = false;
|
||||
siteEditorOpen.value = false;
|
||||
editingId.value = null;
|
||||
draft.value = {};
|
||||
homeDraft.value = emptyHomeDraft(activeHomeKind.value);
|
||||
siteDraft.value = {};
|
||||
}
|
||||
|
||||
async function saveItem() {
|
||||
if (saving.value || !auth.hasPermission("admin:site-config")) return;
|
||||
const payload = compactSiteItemPayload(activeModule.value, draft.value);
|
||||
const validationError = validateSiteItemPayload(activeModule.value, payload);
|
||||
if (validationError) {
|
||||
ElMessage.warning(validationError);
|
||||
return;
|
||||
}
|
||||
const primaryValue = activeDefinition.value.primaryField === "introTitle" ? payload.introTitle : payload.title;
|
||||
if (!String(primaryValue ?? "").trim()) {
|
||||
ElMessage.warning(`请先填写${activeDefinition.value.primaryField === "introTitle" ? "服务标题" : "标题"}`);
|
||||
return;
|
||||
function startCreate() {
|
||||
if (!auth.hasPermission("admin:site-config")) return;
|
||||
editingId.value = null;
|
||||
if (isSiteModule.value) {
|
||||
siteDraft.value = createEmptySiteItemDraft(activeSiteModule.value, activeSiteItems.value.length);
|
||||
siteEditorOpen.value = true;
|
||||
} else {
|
||||
homeDraft.value = { ...emptyHomeDraft(activeHomeKind.value), sortOrder: activeHomeItems.value.length };
|
||||
editorOpen.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(item: SiteConfigItem | HomeWanfaRecommendation | HomeTeamBuilding | HomeWildArchive) {
|
||||
if (!auth.hasPermission("admin:site-config")) return;
|
||||
editingId.value = item.id;
|
||||
if (isSiteModule.value) {
|
||||
siteDraft.value = siteItemDraft(item as SiteConfigItem);
|
||||
siteEditorOpen.value = true;
|
||||
} else {
|
||||
const kind = activeHomeKind.value;
|
||||
if (kind === "recommendation") {
|
||||
const recommendation = item as HomeWanfaRecommendation;
|
||||
homeDraft.value = { categoryId: recommendation.categoryId, isActive: recommendation.isActive, sortOrder: recommendation.sortOrder };
|
||||
} else if (kind === "team") {
|
||||
const team = item as HomeTeamBuilding;
|
||||
homeDraft.value = { tag: team.tag, title: team.title, description: team.description, image: team.image, demandKeyword: team.demandKeyword, detailSubtitle: team.detailSubtitle ?? "", detailParagraphs: [...(team.detailParagraphs ?? [])], isActive: team.isActive, sortOrder: team.sortOrder };
|
||||
} else {
|
||||
const wild = item as HomeWildArchive;
|
||||
homeDraft.value = { title: wild.title, image: wild.image, images: [...(wild.images ?? [])], demandKeyword: wild.demandKeyword, isActive: wild.isActive, sortOrder: wild.sortOrder };
|
||||
}
|
||||
editorOpen.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSiteItem() {
|
||||
const payload = compactSiteItemPayload(activeSiteModule.value, siteDraft.value);
|
||||
const validationError = validateSiteItemPayload(activeSiteModule.value, payload);
|
||||
if (validationError) return ElMessage.warning(validationError);
|
||||
if (!String(payload.title ?? "").trim()) return ElMessage.warning("请先填写标题");
|
||||
saving.value = true;
|
||||
try {
|
||||
const saved = editingId.value
|
||||
? await updateSiteConfigItem(activeModule.value, editingId.value, payload)
|
||||
: await createSiteConfigItem(activeModule.value, payload);
|
||||
replaceItem(saved);
|
||||
const saved = editingId.value ? await updateSiteConfigItem(activeSiteModule.value, editingId.value, payload) : await createSiteConfigItem(activeSiteModule.value, payload);
|
||||
const next = siteItems.value[activeSiteModule.value].filter((item) => item.id !== saved.id);
|
||||
siteItems.value[activeSiteModule.value] = [...next, saved].sort((left, right) => ("sortOrder" in left ? left.sortOrder : 0) - ("sortOrder" in right ? right.sortOrder : 0));
|
||||
ElMessage.success(editingId.value ? "配置已保存" : "配置已新增");
|
||||
closeEditor();
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : "配置保存失败");
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : "配置保存失败"); } finally { saving.value = false; }
|
||||
}
|
||||
|
||||
async function removeItem(item: SiteConfigItem) {
|
||||
if (deletingId.value || !auth.hasPermission("admin:site-config")) return;
|
||||
async function saveHomeItem() {
|
||||
const kind = activeHomeKind.value;
|
||||
if (kind === "recommendation" && !homeDraft.value.categoryId) return ElMessage.warning("请选择玩法分类");
|
||||
if (kind !== "recommendation" && (!homeDraft.value.title?.trim() || !homeDraft.value.image?.trim() || !homeDraft.value.demandKeyword?.trim())) return ElMessage.warning("请填写标题、图片和需求关键词");
|
||||
if (kind === "wild" && !homeDraft.value.images?.length) return ElMessage.warning("极境视界至少需要一张详情图片");
|
||||
saving.value = true;
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除「${itemTitle(item)}」吗?删除后会同步影响前台展示。`, "删除站点配置", { type: "warning" });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const saved = await saveHomeByKind(kind);
|
||||
homeItems.value[kind] = [...activeHomeItems.value.filter((item) => item.id !== saved.id), saved].sort((left, right) => left.sortOrder - right.sortOrder) as typeof activeHomeItems.value;
|
||||
ElMessage.success(editingId.value ? "首页配置已保存" : "首页配置已新增");
|
||||
closeEditor();
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : "首页配置保存失败"); } finally { saving.value = false; }
|
||||
}
|
||||
|
||||
async function saveHomeByKind() {
|
||||
const kind = activeHomeKind.value;
|
||||
if (kind === "recommendation") {
|
||||
const input = { categoryId: homeDraft.value.categoryId ?? "", isActive: homeDraft.value.isActive, sortOrder: homeDraft.value.sortOrder };
|
||||
return editingId.value ? updateHomePlayRecommendation(editingId.value, input) : createHomePlayRecommendation(input);
|
||||
}
|
||||
if (kind === "team") {
|
||||
const input = { tag: homeDraft.value.tag ?? "", title: homeDraft.value.title ?? "", description: homeDraft.value.description ?? "", image: homeDraft.value.image ?? "", demandKeyword: homeDraft.value.demandKeyword ?? "", detailSubtitle: homeDraft.value.detailSubtitle || null, detailParagraphs: homeDraft.value.detailParagraphs ?? [], isActive: homeDraft.value.isActive, sortOrder: homeDraft.value.sortOrder };
|
||||
return editingId.value ? updateHomeTeamBuilding(editingId.value, input) : createHomeTeamBuilding(input);
|
||||
}
|
||||
const input = { title: homeDraft.value.title ?? "", image: homeDraft.value.image ?? "", images: homeDraft.value.images ?? [], demandKeyword: homeDraft.value.demandKeyword ?? "", isActive: homeDraft.value.isActive, sortOrder: homeDraft.value.sortOrder };
|
||||
return editingId.value ? updateHomeWildArchive(editingId.value, input) : createHomeWildArchive(input);
|
||||
}
|
||||
|
||||
async function removeItem(item: SiteConfigItem | HomeWanfaRecommendation | HomeTeamBuilding | HomeWildArchive) {
|
||||
if (deletingId.value) return;
|
||||
try { await ElMessageBox.confirm(`确认删除「${itemTitle(item)}」吗?删除后会同步影响首页展示。`, "删除首页配置", { type: "warning" }); } catch { return; }
|
||||
deletingId.value = item.id;
|
||||
try {
|
||||
await deleteSiteConfigItem(activeModule.value, item.id);
|
||||
moduleItems.value[activeModule.value] = activeItems.value.filter((candidate) => candidate.id !== item.id);
|
||||
if (editingId.value === item.id) closeEditor();
|
||||
if (isSiteModule.value) await deleteSiteConfigItem(activeSiteModule.value, item.id);
|
||||
else if (activeHomeKind.value === "recommendation") await deleteHomePlayRecommendation(item.id);
|
||||
else if (activeHomeKind.value === "team") await deleteHomeTeamBuilding(item.id);
|
||||
else await deleteHomeWildArchive(item.id);
|
||||
if (isSiteModule.value) siteItems.value[activeSiteModule.value] = activeSiteItems.value.filter((candidate) => candidate.id !== item.id);
|
||||
else homeItems.value[activeHomeKind.value] = activeHomeItems.value.filter((candidate) => candidate.id !== item.id) as typeof activeHomeItems.value;
|
||||
ElMessage.success("配置已删除");
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : "配置删除失败");
|
||||
} finally {
|
||||
deletingId.value = null;
|
||||
}
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : "配置删除失败"); } finally { deletingId.value = null; }
|
||||
}
|
||||
|
||||
async function moveItem(item: SiteConfigItem, direction: -1 | 1) {
|
||||
if (reorderingId.value || !activeDefinition.value.ordered || !auth.hasPermission("admin:site-config")) return;
|
||||
async function moveItem(item: SiteConfigItem | HomeWanfaRecommendation | HomeTeamBuilding | HomeWildArchive, direction: -1 | 1) {
|
||||
const items = activeItems.value;
|
||||
const currentIndex = items.findIndex((candidate) => candidate.id === item.id);
|
||||
const nextIndex = currentIndex + direction;
|
||||
if (currentIndex < 0 || nextIndex < 0 || nextIndex >= items.length) return;
|
||||
|
||||
const nextIds = items.map((candidate) => candidate.id);
|
||||
[nextIds[currentIndex], nextIds[nextIndex]] = [nextIds[nextIndex], nextIds[currentIndex]];
|
||||
const index = items.findIndex((candidate) => candidate.id === item.id);
|
||||
const nextIndex = index + direction;
|
||||
if (index < 0 || nextIndex < 0 || nextIndex >= items.length || reorderingId.value) return;
|
||||
const ids = items.map((candidate) => candidate.id);
|
||||
[ids[index], ids[nextIndex]] = [ids[nextIndex], ids[index]];
|
||||
reorderingId.value = item.id;
|
||||
try {
|
||||
const result = await reorderSiteConfigItems(activeModule.value, nextIds);
|
||||
moduleItems.value[activeModule.value] = result.items;
|
||||
const result = isSiteModule.value ? await reorderSiteConfigItems(activeSiteModule.value, ids) : await reorderHomeByKind(activeHomeKind.value, ids);
|
||||
if (isSiteModule.value) siteItems.value[activeSiteModule.value] = result.items as SiteConfigItem[];
|
||||
else homeItems.value[activeHomeKind.value] = result.items as typeof activeHomeItems.value;
|
||||
ElMessage.success("展示顺序已更新");
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : "排序更新失败");
|
||||
} finally {
|
||||
reorderingId.value = null;
|
||||
}
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : "排序更新失败"); } finally { reorderingId.value = null; }
|
||||
}
|
||||
|
||||
function replaceItem(saved: SiteConfigItem) {
|
||||
const items = moduleItems.value[activeModule.value];
|
||||
const index = items.findIndex((item) => item.id === saved.id);
|
||||
moduleItems.value[activeModule.value] = index < 0
|
||||
? [...items, saved].sort(sortByOrder)
|
||||
: items.map((item) => item.id === saved.id ? saved : item).sort(sortByOrder);
|
||||
function reorderHomeByKind(kind: HomeContentKind, ids: string[]) {
|
||||
if (kind === "recommendation") return reorderHomePlayRecommendations(ids);
|
||||
if (kind === "team") return reorderHomeTeamBuildings(ids);
|
||||
return reorderHomeWildArchives(ids);
|
||||
}
|
||||
|
||||
function sortByOrder(left: SiteConfigItem, right: SiteConfigItem) {
|
||||
return ("sortOrder" in left ? left.sortOrder : 0) - ("sortOrder" in right ? right.sortOrder : 0);
|
||||
}
|
||||
|
||||
function itemTitle(item: SiteConfigItem) {
|
||||
if ("introTitle" in item) return item.introTitle;
|
||||
if ("submitLabel" in item) return item.submitLabel;
|
||||
function itemTitle(item: SiteConfigItem | HomeWanfaRecommendation | HomeTeamBuilding | HomeWildArchive) {
|
||||
if ("categoryLabel" in item) return item.categoryLabel;
|
||||
if ("tag" in item) return item.title;
|
||||
return item.title;
|
||||
}
|
||||
|
||||
function itemDescription(item: SiteConfigItem) {
|
||||
if ("introTitle" in item) return item.intro;
|
||||
if ("submitLabel" in item) return item.chips.join("、") || "需求表单字段配置";
|
||||
return "description" in item ? item.description : item.kicker;
|
||||
function itemDescription(item: SiteConfigItem | HomeWanfaRecommendation | HomeTeamBuilding | HomeWildArchive) {
|
||||
if ("categoryLabel" in item) return `${item.routeCount} 条路线`;
|
||||
if ("tag" in item) return item.description;
|
||||
if ("demandKeyword" in item) return item.demandKeyword;
|
||||
return "";
|
||||
}
|
||||
|
||||
function itemImage(item: SiteConfigItem) {
|
||||
function itemImage(item: SiteConfigItem | HomeWanfaRecommendation | HomeTeamBuilding | HomeWildArchive) {
|
||||
return "image" in item ? item.image : null;
|
||||
}
|
||||
|
||||
function itemStatus(item: SiteConfigItem) {
|
||||
return item.isActive ? "启用中" : "已停用";
|
||||
}
|
||||
|
||||
onMounted(loadConfig);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-stack">
|
||||
<div class="page-intro site-config-intro">
|
||||
<div>
|
||||
<p class="eyebrow">CONTENT / HOME</p>
|
||||
<h2>首页配置</h2>
|
||||
<p>迁移首页站点配置的读取、写入、删除和排序能力,保存后会直接影响对应前台模块。</p>
|
||||
</div>
|
||||
<div class="page-intro-actions"><el-button v-permission="'admin:media:read'" plain @click="$router.push('/media')">媒体素材</el-button><el-button v-permission="'admin:site-config'" plain @click="$router.push('/tools')">发布与重置</el-button><el-button v-permission="'admin:site-config'" type="primary" :disabled="!canCreate" @click="startCreate">新增{{ activeDefinition.label }}</el-button></div>
|
||||
</div>
|
||||
|
||||
<el-alert v-if="!loading && !loadError" title="站点配置模块均已接入统一写入入口;玩法路线与详情内容在“玩法与路线”页面单独维护。" type="info" :closable="false" show-icon />
|
||||
<div class="page-intro site-config-intro"><div><p class="eyebrow">CONTENT / HOME</p><h2>首页配置</h2><p>统一维护顶部轮播、玩法推荐、万趣用车、团队共创和极境视界;保存后直接同步首页展示。</p></div></div>
|
||||
<el-skeleton v-if="loading" :rows="10" animated />
|
||||
<el-result v-else-if="loadError" icon="error" title="首页配置加载失败" :sub-title="loadError">
|
||||
<template #extra><el-button type="primary" @click="loadConfig">重试</el-button></template>
|
||||
</el-result>
|
||||
|
||||
<el-result v-else-if="loadError" icon="error" title="首页配置加载失败" :sub-title="loadError"><template #extra><el-button type="primary" @click="loadConfig">重试</el-button></template></el-result>
|
||||
<template v-else>
|
||||
<el-tabs v-model="activeModule" class="site-config-tabs" @tab-change="closeEditor">
|
||||
<el-tab-pane v-for="definition in siteModuleDefinitions" :key="definition.id" :name="definition.id" :label="definition.label">
|
||||
<div class="site-config-panel">
|
||||
<div class="panel-heading">
|
||||
<div>
|
||||
<strong>{{ definition.label }}</strong>
|
||||
<span>{{ definition.description }}</span>
|
||||
</div>
|
||||
<el-button v-permission="'admin:site-config'" plain type="primary" :disabled="definition.singleton && activeItems.length > 0" @click="startCreate">新增配置</el-button>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="!activeItems.length" description="暂无配置记录">
|
||||
<el-button v-permission="'admin:site-config'" type="primary" @click="startCreate">创建第一条配置</el-button>
|
||||
</el-empty>
|
||||
<el-table v-else :data="activeItems" stripe>
|
||||
<el-table-column label="内容" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<div class="site-item-content">
|
||||
<el-image v-if="itemImage(row)" class="site-item-thumb" :src="itemImage(row) ?? undefined" fit="cover" />
|
||||
<div>
|
||||
<strong>{{ itemTitle(row) || "未命名配置" }}</strong>
|
||||
<small>{{ itemDescription(row) || "暂无补充说明" }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="110">
|
||||
<template #default="{ row }"><el-tag :type="row.isActive ? 'success' : 'info'" effect="plain">{{ itemStatus(row) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="definition.ordered" prop="sortOrder" label="排序" width="80" />
|
||||
<el-table-column label="操作" width="260" fixed="right">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button v-permission="'admin:site-config'" link type="primary" @click="startEdit(row)">编辑</el-button>
|
||||
<template v-if="definition.ordered"><el-button v-permission="'admin:site-config'" link :disabled="$index === 0 || reorderingId === row.id" :loading="reorderingId === row.id" @click="moveItem(row, -1)">上移</el-button><el-button v-permission="'admin:site-config'" link :disabled="$index === activeItems.length - 1 || reorderingId === row.id" @click="moveItem(row, 1)">下移</el-button></template>
|
||||
<el-button v-permission="'admin:site-config'" link type="danger" :loading="deletingId === row.id" @click="removeItem(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="heroSlides" label="顶部轮播" />
|
||||
<el-tab-pane name="recommendation" label="玩法推荐" />
|
||||
<el-tab-pane name="vehicleOptions" label="万趣用车" />
|
||||
<el-tab-pane name="team" label="团队共创" />
|
||||
<el-tab-pane name="wild" label="极境视界" />
|
||||
</el-tabs>
|
||||
|
||||
<el-drawer v-model="editorOpen" class="site-config-drawer" size="min(560px, 100%)" :with-header="false" :close-on-press-escape="!saving" :show-close="false">
|
||||
<SiteConfigEditor
|
||||
v-if="editorOpen"
|
||||
:module="activeModule"
|
||||
:draft="draft"
|
||||
:creating="isCreating"
|
||||
:saving="saving"
|
||||
@update:draft="draft = $event"
|
||||
@save="saveItem"
|
||||
@cancel="closeEditor"
|
||||
/>
|
||||
</el-drawer>
|
||||
<el-card shadow="never" class="panel-card home-content-panel">
|
||||
<div class="panel-heading"><div><strong>{{ isSiteModule ? activeDefinition.label : ({ recommendation: "玩法推荐", team: "团队共创", wild: "极境视界" } as Record<HomeContentKind, string>)[activeHomeKind] }}</strong><span>{{ activeItems.length }} 条配置</span></div><el-button v-permission="'admin:site-config'" type="primary" plain @click="startCreate">新增配置</el-button></div>
|
||||
<el-empty v-if="!activeItems.length" description="暂无配置记录"><el-button v-permission="'admin:site-config'" type="primary" @click="startCreate">创建第一条配置</el-button></el-empty>
|
||||
<el-table v-else :data="activeItems" stripe>
|
||||
<el-table-column label="内容" min-width="300"><template #default="{ row }"><div class="site-item-content"><el-image v-if="itemImage(row)" class="site-item-thumb" :src="itemImage(row) ?? undefined" fit="cover" /><div><strong>{{ itemTitle(row) }}</strong><small>{{ itemDescription(row) }}</small></div></div></template></el-table-column>
|
||||
<el-table-column v-if="activeModule === 'recommendation'" prop="routeCount" label="路线数" width="90" />
|
||||
<el-table-column v-if="activeModule !== 'recommendation'" prop="sortOrder" label="排序" width="80" />
|
||||
<el-table-column label="状态" width="100"><template #default="{ row }"><el-tag :type="row.isActive ? 'success' : 'info'" effect="plain">{{ row.isActive ? "启用中" : "已停用" }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="操作" width="250" fixed="right"><template #default="{ row, $index }"><el-button v-permission="'admin:site-config'" link type="primary" @click="startEdit(row)">编辑</el-button><el-button v-permission="'admin:site-config'" link :disabled="$index === 0 || reorderingId === row.id" @click="moveItem(row, -1)">上移</el-button><el-button v-permission="'admin:site-config'" link :disabled="$index === activeItems.length - 1 || reorderingId === row.id" @click="moveItem(row, 1)">下移</el-button><el-button v-permission="'admin:site-config'" link type="danger" :loading="deletingId === row.id" @click="removeItem(row)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<el-drawer v-model="siteEditorOpen" class="site-config-drawer" size="min(560px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!saving"><SiteConfigEditor v-if="siteEditorOpen" :module="activeSiteModule" :draft="siteDraft" :creating="creating" :saving="saving" @update:draft="siteDraft = $event" @save="saveSiteItem" @cancel="closeEditor" /></el-drawer>
|
||||
<el-drawer v-model="editorOpen" class="site-config-drawer" size="min(560px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!saving"><HomeContentEditor v-if="editorOpen" :kind="activeHomeKind" :draft="homeDraft" :categories="categories" :creating="creating" :saving="saving" @update:draft="homeDraft = $event" @save="saveHomeItem" @cancel="closeEditor" /></el-drawer>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user