refactor(admin-ui): 统一重构抽屉编辑器布局,修复废弃API用法
为所有抽屉式编辑器组件添加统一的样式规范,实现内容区域可滚动、操作栏固定于底部的布局,适配移动端安全区域间距。替换布局设置抽屉中el-radio-button的废弃label属性为value属性以消除组件警告。重构首页配置页面的编辑器关闭逻辑,拆分状态重置与关闭操作优化代码复用。新增单元测试用例验证抽屉编辑器的布局一致性。
This commit is contained in:
1 parent
e74ecea6aa
commit
b322c930fa
11 files changed
+97
-20
No files matched your search
@@ -37,7 +37,8 @@ function toDraft(advisor?: ConciergeAdvisor): ConciergeAdvisorCreate {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form class="concierge-editor" label-position="top" @submit.prevent="save">
|
<el-form class="concierge-editor drawer-editor-form" label-position="top" @submit.prevent="save">
|
||||||
|
<div class="drawer-editor-form__content">
|
||||||
<div class="editor-heading"><div><p class="eyebrow">{{ editing ? "EDIT" : "CREATE" }} / CONCIERGE</p><h3>{{ editing ? "编辑管家顾问" : "新增管家顾问" }}</h3><p>维护顾问卡片、服务详情和二维码,保存后直接同步前台展示。</p></div></div>
|
<div class="editor-heading"><div><p class="eyebrow">{{ editing ? "EDIT" : "CREATE" }} / CONCIERGE</p><h3>{{ editing ? "编辑管家顾问" : "新增管家顾问" }}</h3><p>维护顾问卡片、服务详情和二维码,保存后直接同步前台展示。</p></div></div>
|
||||||
<el-alert v-if="error" :title="error" type="error" :closable="false" show-icon />
|
<el-alert v-if="error" :title="error" type="error" :closable="false" show-icon />
|
||||||
<el-form-item label="顾问姓名" required><el-input :model-value="draft.name" :disabled="saving" maxlength="80" show-word-limit @update:model-value="update('name', $event)" /></el-form-item>
|
<el-form-item label="顾问姓名" required><el-input :model-value="draft.name" :disabled="saving" maxlength="80" show-word-limit @update:model-value="update('name', $event)" /></el-form-item>
|
||||||
@@ -45,6 +46,7 @@ function toDraft(advisor?: ConciergeAdvisor): ConciergeAdvisorCreate {
|
|||||||
<div class="editor-options"><SingleMediaEditor :model-value="draft.avatar" group="concierge" label="顾问头像" :disabled="saving" @update:model-value="update('avatar', $event)" /><SingleMediaEditor :model-value="draft.qrImage" group="concierge" label="添加管家二维码" :disabled="saving" @update:model-value="update('qrImage', $event)" /></div>
|
<div class="editor-options"><SingleMediaEditor :model-value="draft.avatar" group="concierge" label="顾问头像" :disabled="saving" @update:model-value="update('avatar', $event)" /><SingleMediaEditor :model-value="draft.qrImage" group="concierge" label="添加管家二维码" :disabled="saving" @update:model-value="update('qrImage', $event)" /></div>
|
||||||
<ConciergeDetailEditor :model-value="draft.details as ConciergeDetail[]" :disabled="saving" @update:model-value="update('details', $event)" />
|
<ConciergeDetailEditor :model-value="draft.details as ConciergeDetail[]" :disabled="saving" @update:model-value="update('details', $event)" />
|
||||||
<el-form-item label="启用状态"><el-switch :model-value="draft.isActive ?? true" :disabled="saving" active-text="启用" inactive-text="停用" @update:model-value="update('isActive', $event)" /></el-form-item>
|
<el-form-item label="启用状态"><el-switch :model-value="draft.isActive ?? true" :disabled="saving" active-text="启用" inactive-text="停用" @update:model-value="update('isActive', $event)" /></el-form-item>
|
||||||
|
</div>
|
||||||
<div class="editor-actions"><el-button :disabled="saving" @click="emit('cancel')">取消</el-button><el-button type="primary" native-type="submit" :loading="saving">{{ editing ? "保存修改" : "创建顾问" }}</el-button></div>
|
<div class="editor-actions"><el-button :disabled="saving" @click="emit('cancel')">取消</el-button><el-button type="primary" native-type="submit" :loading="saving">{{ editing ? "保存修改" : "创建顾问" }}</el-button></div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
@@ -45,7 +45,8 @@ function updateLines(field: "detailParagraphs", value: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form class="home-content-editor" label-position="top" @submit.prevent="emit('save')">
|
<el-form class="home-content-editor drawer-editor-form" label-position="top" @submit.prevent="emit('save')">
|
||||||
|
<div class="drawer-editor-form__content">
|
||||||
<div class="editor-heading"><div><p class="eyebrow">{{ creating ? "CREATE" : "EDIT" }} / HOME CONTENT</p><h3>{{ creating ? "新增" : "编辑" }}{{ title[kind] }}</h3><p>{{ subtitle[kind] }}</p></div></div>
|
<div class="editor-heading"><div><p class="eyebrow">{{ creating ? "CREATE" : "EDIT" }} / HOME CONTENT</p><h3>{{ creating ? "新增" : "编辑" }}{{ title[kind] }}</h3><p>{{ subtitle[kind] }}</p></div></div>
|
||||||
|
|
||||||
<template v-if="kind === 'recommendation'">
|
<template v-if="kind === 'recommendation'">
|
||||||
@@ -70,6 +71,7 @@ function updateLines(field: "detailParagraphs", value: string) {
|
|||||||
|
|
||||||
<el-form-item v-if="kind !== 'recommendation'" class="editor-sort-item" label="排序值"><el-input-number :model-value="draft.sortOrder ?? 0" :min="0" :disabled="saving" controls-position="right" @update:model-value="update('sortOrder', $event ?? 0)" /></el-form-item>
|
<el-form-item v-if="kind !== 'recommendation'" class="editor-sort-item" label="排序值"><el-input-number :model-value="draft.sortOrder ?? 0" :min="0" :disabled="saving" controls-position="right" @update:model-value="update('sortOrder', $event ?? 0)" /></el-form-item>
|
||||||
<div class="home-content-options"><el-form-item label="启用状态"><el-switch :model-value="draft.isActive" :disabled="saving" active-text="启用" inactive-text="停用" @update:model-value="update('isActive', $event)" /></el-form-item></div>
|
<div class="home-content-options"><el-form-item label="启用状态"><el-switch :model-value="draft.isActive" :disabled="saving" active-text="启用" inactive-text="停用" @update:model-value="update('isActive', $event)" /></el-form-item></div>
|
||||||
|
</div>
|
||||||
<div class="editor-actions"><el-button :disabled="saving" @click="emit('cancel')">取消</el-button><el-button type="primary" native-type="submit" :loading="saving">保存{{ title[kind] }}</el-button></div>
|
<div class="editor-actions"><el-button :disabled="saving" @click="emit('cancel')">取消</el-button><el-button type="primary" native-type="submit" :loading="saving">保存{{ title[kind] }}</el-button></div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,6 +18,7 @@ function reset() {
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<el-drawer
|
||||||
:model-value="layout.settingsDrawerOpen"
|
:model-value="layout.settingsDrawerOpen"
|
||||||
|
class="settings-drawer"
|
||||||
title="布局设置"
|
title="布局设置"
|
||||||
size="320px"
|
size="320px"
|
||||||
append-to-body
|
append-to-body
|
||||||
@@ -26,15 +27,15 @@ function reset() {
|
|||||||
<el-form label-position="top" class="settings-form">
|
<el-form label-position="top" class="settings-form">
|
||||||
<el-form-item label="导航模式">
|
<el-form-item label="导航模式">
|
||||||
<el-radio-group :model-value="layout.settings.navType" @update:model-value="update('navType', $event as NavigationType)">
|
<el-radio-group :model-value="layout.settings.navType" @update:model-value="update('navType', $event as NavigationType)">
|
||||||
<el-radio-button label="left">左侧菜单</el-radio-button>
|
<el-radio-button value="left">左侧菜单</el-radio-button>
|
||||||
<el-radio-button label="mix">混合菜单</el-radio-button>
|
<el-radio-button value="mix">混合菜单</el-radio-button>
|
||||||
<el-radio-button label="top">顶部菜单</el-radio-button>
|
<el-radio-button value="top">顶部菜单</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="侧栏主题">
|
<el-form-item label="侧栏主题">
|
||||||
<el-radio-group :model-value="layout.settings.sideTheme" @update:model-value="update('sideTheme', $event as SideTheme)">
|
<el-radio-group :model-value="layout.settings.sideTheme" @update:model-value="update('sideTheme', $event as SideTheme)">
|
||||||
<el-radio-button label="theme-dark">深色</el-radio-button>
|
<el-radio-button value="theme-dark">深色</el-radio-button>
|
||||||
<el-radio-button label="theme-light">浅色</el-radio-button>
|
<el-radio-button value="theme-light">浅色</el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="主题色">
|
<el-form-item label="主题色">
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ function updateField<K extends keyof SiteItemPatch>(field: K, value: SiteItemPat
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form class="site-config-editor" :model="draft" label-position="top" @submit.prevent="emit('save')">
|
<el-form class="site-config-editor drawer-editor-form" :model="draft" label-position="top" @submit.prevent="emit('save')">
|
||||||
|
<div class="drawer-editor-form__content">
|
||||||
<div class="editor-heading">
|
<div class="editor-heading">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">{{ creating ? "CREATE" : "EDIT" }} / SITE CONFIG</p>
|
<p class="eyebrow">{{ creating ? "CREATE" : "EDIT" }} / SITE CONFIG</p>
|
||||||
@@ -69,6 +70,7 @@ function updateField<K extends keyof SiteItemPatch>(field: K, value: SiteItemPat
|
|||||||
<el-form-item label="启用状态">
|
<el-form-item label="启用状态">
|
||||||
<el-switch :model-value="draft.isActive ?? true" :disabled="saving" active-text="启用" inactive-text="停用" @update:model-value="updateField('isActive', $event)" />
|
<el-switch :model-value="draft.isActive ?? true" :disabled="saving" active-text="启用" inactive-text="停用" @update:model-value="updateField('isActive', $event)" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
<el-button :disabled="saving" @click="emit('cancel')">取消</el-button>
|
<el-button :disabled="saving" @click="emit('cancel')">取消</el-button>
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ function submit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form class="wanfa-editor" label-position="top" @submit.prevent="submit">
|
<el-form class="wanfa-editor drawer-editor-form" label-position="top" @submit.prevent="submit">
|
||||||
|
<div class="drawer-editor-form__content">
|
||||||
<div class="editor-heading">
|
<div class="editor-heading">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">{{ editing ? "EDIT" : "CREATE" }} / PLAY CATEGORY</p>
|
<p class="eyebrow">{{ editing ? "EDIT" : "CREATE" }} / PLAY CATEGORY</p>
|
||||||
@@ -32,6 +33,7 @@ function submit() {
|
|||||||
<el-input v-model="value" maxlength="40" show-word-limit :disabled="saving" placeholder="例如:亲子路线" autofocus />
|
<el-input v-model="value" maxlength="40" show-word-limit :disabled="saving" placeholder="例如:亲子路线" autofocus />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<p class="field-hint">保存前会自动去除首尾空格。</p>
|
<p class="field-hint">保存前会自动去除首尾空格。</p>
|
||||||
|
</div>
|
||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
<el-button :disabled="saving" @click="emit('cancel')">取消</el-button>
|
<el-button :disabled="saving" @click="emit('cancel')">取消</el-button>
|
||||||
<el-button type="primary" native-type="submit" :loading="saving">{{ editing ? "保存修改" : "创建分类" }}</el-button>
|
<el-button type="primary" native-type="submit" :loading="saving">{{ editing ? "保存修改" : "创建分类" }}</el-button>
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ function submit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form class="wanfa-editor route-editor" label-position="top" @submit.prevent="submit">
|
<el-form class="wanfa-editor route-editor drawer-editor-form" label-position="top" @submit.prevent="submit">
|
||||||
|
<div class="drawer-editor-form__content">
|
||||||
<div class="editor-heading">
|
<div class="editor-heading">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">{{ editing ? "EDIT" : "CREATE" }} / PLAY ROUTE</p>
|
<p class="eyebrow">{{ editing ? "EDIT" : "CREATE" }} / PLAY ROUTE</p>
|
||||||
@@ -81,6 +82,7 @@ function submit() {
|
|||||||
|
|
||||||
<DetailFields v-model="detailDraft" :advisors="advisors" :disabled="saving" />
|
<DetailFields v-model="detailDraft" :advisors="advisors" :disabled="saving" />
|
||||||
<el-alert v-if="error" :title="error" type="error" :closable="false" show-icon />
|
<el-alert v-if="error" :title="error" type="error" :closable="false" show-icon />
|
||||||
|
</div>
|
||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
<el-button :disabled="saving" @click="emit('cancel')">取消</el-button>
|
<el-button :disabled="saving" @click="emit('cancel')">取消</el-button>
|
||||||
<el-button type="primary" native-type="submit" :loading="saving">{{ editing ? "保存修改" : "创建路线" }}</el-button>
|
<el-button type="primary" native-type="submit" :loading="saving">{{ editing ? "保存修改" : "创建路线" }}</el-button>
|
||||||
|
|||||||
@@ -116,6 +116,6 @@ onMounted(load);
|
|||||||
<el-skeleton v-if="loading" :rows="7" animated />
|
<el-skeleton v-if="loading" :rows="7" animated />
|
||||||
<el-empty v-else-if="!advisors.length" description="暂无管家顾问"><el-button v-permission="'admin:concierge:read'" type="primary" @click="openCreate">创建第一位顾问</el-button></el-empty>
|
<el-empty v-else-if="!advisors.length" description="暂无管家顾问"><el-button v-permission="'admin:concierge:read'" type="primary" @click="openCreate">创建第一位顾问</el-button></el-empty>
|
||||||
<el-card v-else shadow="never" class="panel-card"><div class="concierge-list"><div v-for="(advisor, index) in advisors" :key="advisor.id" class="concierge-row"><div class="concierge-summary"><el-avatar :size="48" :src="resolveAdminMediaUrl(advisor.avatar)">{{ advisor.name.slice(0, 1) }}</el-avatar><div><strong>{{ advisor.name }}</strong><span>{{ advisor.role }} · {{ advisor.details?.length ?? 0 }} 条服务详情</span></div></div><div class="concierge-actions"><el-tag :type="advisor.isActive ? 'success' : 'info'" effect="plain">{{ advisor.isActive ? "启用" : "停用" }}</el-tag><el-button v-permission="'admin:concierge:read'" link :disabled="index === 0 || Boolean(savingKey)" @click="move(advisor, -1)">上移</el-button><el-button v-permission="'admin:concierge:read'" link :disabled="index === advisors.length - 1 || Boolean(savingKey)" @click="move(advisor, 1)">下移</el-button><el-button v-permission="'admin:concierge:read'" link type="primary" @click="openEdit(advisor)">编辑</el-button><el-button v-permission="'admin:concierge:read'" link type="danger" :disabled="Boolean(savingKey)" @click="remove(advisor)">删除</el-button></div></div></div></el-card>
|
<el-card v-else shadow="never" class="panel-card"><div class="concierge-list"><div v-for="(advisor, index) in advisors" :key="advisor.id" class="concierge-row"><div class="concierge-summary"><el-avatar :size="48" :src="resolveAdminMediaUrl(advisor.avatar)">{{ advisor.name.slice(0, 1) }}</el-avatar><div><strong>{{ advisor.name }}</strong><span>{{ advisor.role }} · {{ advisor.details?.length ?? 0 }} 条服务详情</span></div></div><div class="concierge-actions"><el-tag :type="advisor.isActive ? 'success' : 'info'" effect="plain">{{ advisor.isActive ? "启用" : "停用" }}</el-tag><el-button v-permission="'admin:concierge:read'" link :disabled="index === 0 || Boolean(savingKey)" @click="move(advisor, -1)">上移</el-button><el-button v-permission="'admin:concierge:read'" link :disabled="index === advisors.length - 1 || Boolean(savingKey)" @click="move(advisor, 1)">下移</el-button><el-button v-permission="'admin:concierge:read'" link type="primary" @click="openEdit(advisor)">编辑</el-button><el-button v-permission="'admin:concierge:read'" link type="danger" :disabled="Boolean(savingKey)" @click="remove(advisor)">删除</el-button></div></div></div></el-card>
|
||||||
<el-drawer v-model="editorOpen" size="min(720px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!savingKey" @close="closeEditor"><ConciergeAdvisorEditor v-if="editorOpen" :advisor="editing" :editing="Boolean(editing)" :saving="savingKey === 'advisor'" @save="save" @cancel="closeEditor" /></el-drawer>
|
<el-drawer v-model="editorOpen" class="editor-drawer" size="min(720px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!savingKey" @close="closeEditor"><ConciergeAdvisorEditor v-if="editorOpen" :advisor="editing" :editing="Boolean(editing)" :saving="savingKey === 'advisor'" @save="save" @cancel="closeEditor" /></el-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -81,8 +81,7 @@ async function loadConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeEditor() {
|
function resetEditorState() {
|
||||||
if (saving.value) return;
|
|
||||||
editorOpen.value = false;
|
editorOpen.value = false;
|
||||||
siteEditorOpen.value = false;
|
siteEditorOpen.value = false;
|
||||||
editingId.value = null;
|
editingId.value = null;
|
||||||
@@ -90,6 +89,15 @@ function closeEditor() {
|
|||||||
siteDraft.value = {};
|
siteDraft.value = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeEditor() {
|
||||||
|
if (saving.value) return;
|
||||||
|
resetEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditorAfterSave() {
|
||||||
|
resetEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
function startCreate() {
|
function startCreate() {
|
||||||
if (!auth.hasPermission("admin:site-config")) return;
|
if (!auth.hasPermission("admin:site-config")) return;
|
||||||
editingId.value = null;
|
editingId.value = null;
|
||||||
@@ -135,7 +143,7 @@ async function saveSiteItem() {
|
|||||||
const next = siteItems.value[activeSiteModule.value].filter((item) => item.id !== saved.id);
|
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));
|
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 ? "配置已保存" : "配置已新增");
|
ElMessage.success(editingId.value ? "配置已保存" : "配置已新增");
|
||||||
closeEditor();
|
closeEditorAfterSave();
|
||||||
} 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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +157,7 @@ async function saveHomeItem() {
|
|||||||
const saved = await saveHomeByKind(kind);
|
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;
|
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 ? "首页配置已保存" : "首页配置已新增");
|
ElMessage.success(editingId.value ? "首页配置已保存" : "首页配置已新增");
|
||||||
closeEditor();
|
closeEditorAfterSave();
|
||||||
} 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; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,8 +256,8 @@ onMounted(loadConfig);
|
|||||||
<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-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-table>
|
||||||
</el-card>
|
</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="siteEditorOpen" class="site-config-drawer editor-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>
|
<el-drawer v-model="editorOpen" class="site-config-drawer editor-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>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -278,7 +278,7 @@ onMounted(loadData);
|
|||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-drawer v-model="categoryDrawer" size="min(480px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!savingKey" @close="closeCategoryDrawer"><WanfaCategoryEditor :label="categoryEditing?.label ?? ''" :editing="Boolean(categoryEditing)" :saving="savingKey === 'category'" @save="saveCategory" @cancel="closeCategoryDrawer" /></el-drawer>
|
<el-drawer v-model="categoryDrawer" class="editor-drawer" size="min(480px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!savingKey" @close="closeCategoryDrawer"><WanfaCategoryEditor :label="categoryEditing?.label ?? ''" :editing="Boolean(categoryEditing)" :saving="savingKey === 'category'" @save="saveCategory" @cancel="closeCategoryDrawer" /></el-drawer>
|
||||||
<el-drawer v-model="routeDrawer" size="min(700px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!savingKey" @close="closeRouteDrawer"><WanfaRouteEditor v-if="routeDrawer && selectedCategory" :category-label="selectedCategory.label" :route="routeEditing ?? undefined" :detail="selectedDetail" :advisors="advisors" :saving="savingKey === 'route'" @save="saveRoute" @cancel="closeRouteDrawer" /></el-drawer>
|
<el-drawer v-model="routeDrawer" class="editor-drawer" size="min(700px, 100%)" :with-header="false" :show-close="false" :close-on-press-escape="!savingKey" @close="closeRouteDrawer"><WanfaRouteEditor v-if="routeDrawer && selectedCategory" :category-label="selectedCategory.label" :route="routeEditing ?? undefined" :detail="selectedDetail" :advisors="advisors" :saving="savingKey === 'route'" @save="saveRoute" @cancel="closeRouteDrawer" /></el-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const readSource = (path: string) => readFileSync(new URL(path, import.meta.url), "utf8");
|
||||||
|
|
||||||
|
const homePage = readSource("./HomePage.vue");
|
||||||
|
const wanfaPage = readSource("./WanfaPage.vue");
|
||||||
|
const conciergePage = readSource("./ConciergePage.vue");
|
||||||
|
const settingsDrawer = readSource("../components/layout/SettingsDrawer.vue");
|
||||||
|
const styles = readSource("../styles.css");
|
||||||
|
|
||||||
|
const editorSources = [
|
||||||
|
readSource("../components/site-config/SiteConfigEditor.vue"),
|
||||||
|
readSource("../components/home/HomeContentEditor.vue"),
|
||||||
|
readSource("../components/wanfa/WanfaCategoryEditor.vue"),
|
||||||
|
readSource("../components/wanfa/WanfaRouteEditor.vue"),
|
||||||
|
readSource("../components/concierge/ConciergeAdvisorEditor.vue"),
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("side drawer editor contracts", () => {
|
||||||
|
it("keeps editor content scrollable and action buttons in a bottom action bar", () => {
|
||||||
|
for (const source of editorSources) {
|
||||||
|
expect(source).toMatch(/<el-form[^>]*class="[^"]*drawer-editor-form[^"]*"/);
|
||||||
|
expect(source).toContain('class="drawer-editor-form__content"');
|
||||||
|
expect(source).toContain('class="editor-actions"');
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(styles).toMatch(/\.drawer-editor-form\s*\{[^}]*display:\s*flex/);
|
||||||
|
expect(styles).toMatch(/\.drawer-editor-form__content\s*\{[^}]*overflow-y:\s*auto/);
|
||||||
|
expect(styles).toMatch(/\.editor-actions\s*\{[^}]*margin-top:\s*0/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("closes both home configuration drawers after a successful save", () => {
|
||||||
|
expect(homePage).toMatch(/async function saveSiteItem\(\)[\s\S]*?closeEditorAfterSave\(\);/);
|
||||||
|
expect(homePage).toMatch(/async function saveHomeItem\(\)[\s\S]*?closeEditorAfterSave\(\);/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps successful save closure wired for route and concierge drawers", () => {
|
||||||
|
expect(wanfaPage).toMatch(/async function saveCategory\(label: string\)[\s\S]*?categoryDrawer\.value = false;/);
|
||||||
|
expect(wanfaPage).toMatch(/async function saveRoute\([\s\S]*?details\.value = [\s\S]*?routeDrawer\.value = false;/);
|
||||||
|
expect(conciergePage).toMatch(/async function save\(input: ConciergeAdvisorCreate\)[\s\S]*?editorOpen\.value = false;/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the non-deprecated value API in the settings drawer", () => {
|
||||||
|
expect(settingsDrawer).toContain('<el-radio-button value="left">');
|
||||||
|
expect(settingsDrawer).toContain('<el-radio-button value="mix">');
|
||||||
|
expect(settingsDrawer).toContain('<el-radio-button value="top">');
|
||||||
|
expect(settingsDrawer).toContain('<el-radio-button value="theme-dark">');
|
||||||
|
expect(settingsDrawer).toContain('<el-radio-button value="theme-light">');
|
||||||
|
expect(settingsDrawer).not.toContain('<el-radio-button label=');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -131,7 +131,13 @@ button, input { font: inherit; }
|
|||||||
.site-image-preview { display: block; width: 100%; max-width: 260px; height: 132px; margin-top: 12px; border-radius: 9px; background: #f1f5f9; }
|
.site-image-preview { display: block; width: 100%; max-width: 260px; height: 132px; margin-top: 12px; border-radius: 9px; background: #f1f5f9; }
|
||||||
.editor-options { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
.editor-options { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||||
.editor-sort-item { margin-top: 20px; }
|
.editor-sort-item { margin-top: 20px; }
|
||||||
.editor-actions { position: sticky; bottom: 0; z-index: 2; display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; padding: 14px 0 4px; border-top: 1px solid var(--line); background: var(--surface); }
|
.editor-actions { display: flex; flex: 0 0 auto; justify-content: flex-end; gap: 10px; margin-top: 0; padding: 14px 8px calc(4px + env(safe-area-inset-bottom)); border-top: 1px solid var(--line); background: var(--surface); }
|
||||||
|
.editor-drawer .el-drawer__body { display: flex; min-height: 0; flex-direction: column; padding: 0; overflow: hidden; }
|
||||||
|
.drawer-editor-form { display: flex; height: 100%; min-height: 0; flex-direction: column; overflow: hidden; padding: 4px 4px 0; }
|
||||||
|
.drawer-editor-form__content { min-height: 0; flex: 1 1 auto; overflow-y: auto; padding: 0 8px 20px; scrollbar-gutter: stable; }
|
||||||
|
.settings-drawer .el-drawer__body { display: flex; min-height: 0; flex-direction: column; }
|
||||||
|
.settings-drawer .settings-form { min-height: 0; flex: 1 1 auto; overflow-y: auto; }
|
||||||
|
.settings-drawer .el-drawer__footer { flex: 0 0 auto; }
|
||||||
.home-content-panel .panel-heading { margin-bottom: 18px; }
|
.home-content-panel .panel-heading { margin-bottom: 18px; }
|
||||||
.home-content-panel .panel-heading > div { display: flex; align-items: baseline; gap: 10px; }
|
.home-content-panel .panel-heading > div { display: flex; align-items: baseline; gap: 10px; }
|
||||||
.home-content-panel .panel-heading span { color: var(--muted); font-size: 12px; font-weight: 400; }
|
.home-content-panel .panel-heading span { color: var(--muted); font-size: 12px; font-weight: 400; }
|
||||||
|
|||||||
Reference in new issue
Block a user