feat(admin): 品牌卡套视觉基线(Batch 1 收口)
- 品牌表单 ElCard → ConfigCard(图标/副标题/信息条/footer/danger 行); 表单双列 grid(标题|主色、WhatsApp|邮箱 同排;头像/欢迎语/话题/占位/隐私 span-2); 保存备注移 footer-right、重置出厂移 #danger(既有 resetting/onResetBrand 逻辑不动) - 预览卡头加副标题「本地简化预览(真机效果以嵌入站点为准)」(cc-subtitle 观感) - CredsPane 挂载处补 @navigate=onGroupSelect 接线;email 卡 info 去「带 • 的」前缀 (push/ai 卡核实无该串);头注释同步收口 - 剪除 pane 条件链尾部 v-else 占位死分支(activeGroupLabel/ALL_NAV_ITEMS/ .placeholder-card/.save-bar 等配套同清) - form-grid 样式与 CredsPane 同款规则就地复用(不建共用文件) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@
|
|||||||
前缀被其他租户占用的 400 detail 内联展示
|
前缀被其他租户占用的 400 detail 内联展示
|
||||||
- Task 5 视觉基线:三卡套 ConfigCard(图标/副标题/信息条/footer/danger
|
- Task 5 视觉基线:三卡套 ConfigCard(图标/副标题/信息条/footer/danger
|
||||||
行);footer-left「出问题?回滚 →」emit navigate('versions')(父层
|
行);footer-left「出问题?回滚 →」emit navigate('versions')(父层
|
||||||
接线在后续任务);表单区双列 grid
|
index.vue 已接线);表单区双列 grid
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="creds-pane">
|
<div class="creds-pane">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
icon="solar:letter-linear"
|
icon="solar:letter-linear"
|
||||||
title="邮件通知"
|
title="邮件通知"
|
||||||
subtitle="客户留下线索后,系统用这组配置发通知邮件"
|
subtitle="客户留下线索后,系统用这组配置发通知邮件"
|
||||||
info="整组保存 · 立即生效 · 产生新版本可回滚;带 • 的字段留空 = 沿用平台默认"
|
info="整组保存 · 立即生效 · 产生新版本可回滚;字段留空 = 沿用平台默认"
|
||||||
danger-label="恢复出厂:把邮件配置重置为初始档案值"
|
danger-label="恢复出厂:把邮件配置重置为初始档案值"
|
||||||
>
|
>
|
||||||
<ElAlert
|
<ElAlert
|
||||||
|
|||||||
@@ -22,10 +22,14 @@
|
|||||||
详情抽屉整份 JSON(密钥 '__SET__' 掩码);勾两版并排对比(spec §7.3
|
详情抽屉整份 JSON(密钥 '__SET__' 掩码);勾两版并排对比(spec §7.3
|
||||||
最低形态,范围可聚焦单组);回滚确认(明示产生新版本/历史不变)→
|
最低形态,范围可聚焦单组);回滚确认(明示产生新版本/历史不变)→
|
||||||
POST rollback → 响应内新快照直接回写本页配置态
|
POST rollback → 响应内新快照直接回写本页配置态
|
||||||
- 组级重置出厂(Task 7d):各编辑卡头部危险色小按钮(品牌在本页;
|
- 组级重置出厂(Task 7d):各编辑卡危险行小按钮(品牌在本页;
|
||||||
提示词/词表/凭据三组在各 pane)→ 确认框明示组名与「恢复出厂档案值」
|
提示词/词表/凭据三组在各 pane)→ 确认框明示组名与「恢复出厂档案值」
|
||||||
→ POST config/{group}/reset → 该组表单/版本刷新(合法组清单 =
|
→ POST config/{group}/reset → 该组表单/版本刷新(合法组清单 =
|
||||||
后端 CONFIG_GROUPS 九组,reset.ts 的 RESET_GROUP_LABELS 镜像)
|
后端 CONFIG_GROUPS 九组,reset.ts 的 RESET_GROUP_LABELS 镜像)
|
||||||
|
- Task 6 视觉基线(feature/config-ux Batch 1 收口):品牌表单卡套
|
||||||
|
ConfigCard(表单双列 grid + footer 回滚/备注/保存 + danger 重置行);
|
||||||
|
预览卡头加副标题;CredsPane @navigate 接线 onGroupSelect;
|
||||||
|
8 组全实装后 v-else 占位死分支剪除(activeGroupLabel/ALL_NAV_ITEMS 同清)
|
||||||
- 挂件实时预览:**本地简化预览组件**(标题/颜色气泡/头像/话题/占位的
|
- 挂件实时预览:**本地简化预览组件**(标题/颜色气泡/头像/话题/占位的
|
||||||
静态模拟,零后端依赖、即时反映表单值;真机预览留给嵌入代码上真站)
|
静态模拟,零后端依赖、即时反映表单值;真机预览留给嵌入代码上真站)
|
||||||
- 嵌入代码:域名取租户详情 sites[].domains(超管可枚举全租户,租户管理
|
- 嵌入代码:域名取租户详情 sites[].domains(超管可枚举全租户,租户管理
|
||||||
@@ -127,141 +131,150 @@
|
|||||||
</ElAlert>
|
</ElAlert>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- 品牌表单 -->
|
<!-- 品牌表单(Task 6:套 ConfigCard 视觉基线——表单双列 grid + footer/danger 行) -->
|
||||||
<ElCard shadow="never" class="form-card">
|
<ConfigCard
|
||||||
<template #header>
|
class="form-card"
|
||||||
<div class="card-header">
|
icon="solar:palette-round-line-duotone"
|
||||||
<span class="card-title">品牌(brand 组 · 整组替换语义)</span>
|
title="品牌与外观"
|
||||||
<div class="header-side">
|
subtitle="客户看到的聊天窗口长什么样"
|
||||||
<span class="card-sub">保存即写新版本并热生效</span>
|
info="整组保存 · 立即生效;图片与链接留空 = 不展示"
|
||||||
<ElButton
|
danger-label="恢复出厂:把品牌配置重置为初始档案值"
|
||||||
size="small"
|
>
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
:loading="resetting"
|
|
||||||
@click="onResetBrand"
|
|
||||||
>
|
|
||||||
重置出厂
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElForm label-width="110px" @submit.prevent>
|
<ElForm label-width="110px" @submit.prevent>
|
||||||
<ElFormItem label="标题 title">
|
<div class="form-grid">
|
||||||
<ElInput
|
<ElFormItem label="标题 title">
|
||||||
v-model="form.title"
|
|
||||||
maxlength="128"
|
|
||||||
placeholder="挂件标题栏文案(如 HESUPIPE Support)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="主题色 color">
|
|
||||||
<div class="color-row">
|
|
||||||
<ElColorPicker v-model="form.color" />
|
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="form.color"
|
v-model="form.title"
|
||||||
placeholder="#066fc0(3/6 位十六进制,留空取挂件默认)"
|
maxlength="128"
|
||||||
class="color-input"
|
placeholder="挂件标题栏文案(如 HESUPIPE Support)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="主题色 color">
|
||||||
<ElFormItem label="头像 avatar">
|
<div class="color-row">
|
||||||
<div class="avatar-row">
|
<ElColorPicker v-model="form.color" />
|
||||||
<div class="avatar-box">
|
<ElInput
|
||||||
<img
|
v-model="form.color"
|
||||||
v-if="avatarUrl && !avatarImgFailed"
|
placeholder="#066fc0(3/6 位十六进制,留空取挂件默认)"
|
||||||
:src="avatarUrl"
|
class="color-input"
|
||||||
alt="品牌头像"
|
|
||||||
@error="avatarImgFailed = true"
|
|
||||||
/>
|
/>
|
||||||
<span
|
|
||||||
v-else
|
|
||||||
class="avatar-letter"
|
|
||||||
:style="{ background: previewColor }"
|
|
||||||
>
|
|
||||||
{{ avatarLetter }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-actions">
|
</ElFormItem>
|
||||||
<ElButton size="small" :loading="uploading" @click="pickAvatar">
|
<ElFormItem label="头像 avatar" class="span-2">
|
||||||
上传新头像
|
<div class="avatar-row">
|
||||||
</ElButton>
|
<div class="avatar-box">
|
||||||
<p class="avatar-tip">png / jpg / webp,≤2MB(后端按文件内容校验)</p>
|
<img
|
||||||
<p v-if="form.avatar" class="avatar-value mono">{{ form.avatar }}</p>
|
v-if="avatarUrl && !avatarImgFailed"
|
||||||
|
:src="avatarUrl"
|
||||||
|
alt="品牌头像"
|
||||||
|
@error="avatarImgFailed = true"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="avatar-letter"
|
||||||
|
:style="{ background: previewColor }"
|
||||||
|
>
|
||||||
|
{{ avatarLetter }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="avatar-actions">
|
||||||
|
<ElButton size="small" :loading="uploading" @click="pickAvatar">
|
||||||
|
上传新头像
|
||||||
|
</ElButton>
|
||||||
|
<p class="avatar-tip">png / jpg / webp,≤2MB(后端按文件内容校验)</p>
|
||||||
|
<p v-if="form.avatar" class="avatar-value mono">{{ form.avatar }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<input
|
||||||
<input
|
ref="avatarInput"
|
||||||
ref="avatarInput"
|
type="file"
|
||||||
type="file"
|
accept=".png,.jpg,.jpeg,.webp,image/png,image/jpeg,image/webp"
|
||||||
accept=".png,.jpg,.jpeg,.webp,image/png,image/jpeg,image/webp"
|
hidden
|
||||||
hidden
|
@change="onAvatarChange"
|
||||||
@change="onAvatarChange"
|
/>
|
||||||
/>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="欢迎语 welcome" class="span-2">
|
||||||
<ElFormItem label="欢迎语 welcome">
|
<ElInput
|
||||||
<ElInput
|
v-model="form.welcome"
|
||||||
v-model="form.welcome"
|
type="textarea"
|
||||||
type="textarea"
|
:rows="2"
|
||||||
:rows="2"
|
maxlength="500"
|
||||||
maxlength="500"
|
placeholder="挂件打开时的第一条消息"
|
||||||
placeholder="挂件打开时的第一条消息"
|
/>
|
||||||
/>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="快捷话题 topics" class="span-2">
|
||||||
<ElFormItem label="快捷话题 topics">
|
<ElSelect
|
||||||
<ElSelect
|
v-model="form.topics"
|
||||||
v-model="form.topics"
|
multiple
|
||||||
multiple
|
filterable
|
||||||
filterable
|
allow-create
|
||||||
allow-create
|
default-first-option
|
||||||
default-first-option
|
:reserve-keyword="false"
|
||||||
:reserve-keyword="false"
|
placeholder="输入后回车添加(字符串数组,随品牌下发)"
|
||||||
placeholder="输入后回车添加(字符串数组,随品牌下发)"
|
class="topics-select"
|
||||||
class="topics-select"
|
/>
|
||||||
/>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="输入框占位" class="span-2">
|
||||||
<ElFormItem label="输入框占位">
|
<ElInput
|
||||||
<ElInput
|
v-model="form.placeholder"
|
||||||
v-model="form.placeholder"
|
maxlength="200"
|
||||||
maxlength="200"
|
placeholder="聊天输入框 placeholder 文案"
|
||||||
placeholder="聊天输入框 placeholder 文案"
|
/>
|
||||||
/>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="WhatsApp">
|
||||||
<ElFormItem label="WhatsApp">
|
<ElInput v-model="form.whatsapp" placeholder="+8613900000000 或 wa.me 链接" />
|
||||||
<ElInput v-model="form.whatsapp" placeholder="+8613900000000 或 wa.me 链接" />
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="邮箱 email">
|
||||||
<ElFormItem label="邮箱 email">
|
<ElInput v-model="form.email" placeholder="页脚展示邮箱" />
|
||||||
<ElInput v-model="form.email" placeholder="页脚展示邮箱" />
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="隐私政策 URL" class="span-2">
|
||||||
<ElFormItem label="隐私政策 URL">
|
<ElInput v-model="form.privacy_url" placeholder="https://example.com/privacy" />
|
||||||
<ElInput v-model="form.privacy_url" placeholder="https://example.com/privacy" />
|
</ElFormItem>
|
||||||
</ElFormItem>
|
</div>
|
||||||
<ElFormItem label="保存备注">
|
|
||||||
<ElInput
|
|
||||||
v-model="saveComment"
|
|
||||||
maxlength="200"
|
|
||||||
placeholder="可选;写入版本历史 comment"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<div class="save-bar">
|
<template #footer-left>
|
||||||
|
<ElButton link type="primary" size="small" @click="onGroupSelect('versions')">
|
||||||
|
出问题?回滚 →
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
<template #footer-right>
|
||||||
|
<ElInput
|
||||||
|
v-model="saveComment"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="备注(选填)"
|
||||||
|
class="comment-input"
|
||||||
|
/>
|
||||||
<ElButton
|
<ElButton
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="saving"
|
:loading="saving"
|
||||||
:disabled="!configLoaded"
|
:disabled="!configLoaded"
|
||||||
@click="onSave"
|
@click="onSave"
|
||||||
>
|
>
|
||||||
保存品牌组(整组提交,热生效)
|
保存
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</template>
|
||||||
</ElCard>
|
<template #danger>
|
||||||
|
<ElButton
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:loading="resetting"
|
||||||
|
@click="onResetBrand"
|
||||||
|
>
|
||||||
|
重置出厂…
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ConfigCard>
|
||||||
|
|
||||||
<!-- 右列:本地预览 + 嵌入代码 -->
|
<!-- 右列:本地预览 + 嵌入代码 -->
|
||||||
<div class="side-col">
|
<div class="side-col">
|
||||||
<ElCard shadow="never" class="preview-card">
|
<ElCard shadow="never" class="preview-card">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="card-title">挂件预览</span>
|
<div class="card-heading">
|
||||||
|
<span class="card-title">挂件预览</span>
|
||||||
|
<p class="card-subtitle">本地简化预览(真机效果以嵌入站点为准)</p>
|
||||||
|
</div>
|
||||||
<span class="card-sub">本地简化模拟,随表单即时变化</span>
|
<span class="card-sub">本地简化模拟,随表单即时变化</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -457,6 +470,7 @@
|
|||||||
:push-group="snapshot?.push"
|
:push-group="snapshot?.push"
|
||||||
:ai-group="snapshot?.ai"
|
:ai-group="snapshot?.ai"
|
||||||
@saved="onPaneSaved"
|
@saved="onPaneSaved"
|
||||||
|
@navigate="onGroupSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -494,11 +508,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 其余组占位 -->
|
|
||||||
<ElCard v-else shadow="never" class="placeholder-card">
|
|
||||||
<ElEmpty :description="`${activeGroupLabel}——后续任务实装`" />
|
|
||||||
</ElCard>
|
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -515,6 +524,7 @@
|
|||||||
uploadBrandAvatar,
|
uploadBrandAvatar,
|
||||||
type BrandGroupValue
|
type BrandGroupValue
|
||||||
} from '@/api/config'
|
} from '@/api/config'
|
||||||
|
import ConfigCard from './ConfigCard.vue'
|
||||||
import PromptsPane from './PromptsPane.vue'
|
import PromptsPane from './PromptsPane.vue'
|
||||||
import WallPane from './WallPane.vue'
|
import WallPane from './WallPane.vue'
|
||||||
import CredsPane from './CredsPane.vue'
|
import CredsPane from './CredsPane.vue'
|
||||||
@@ -575,12 +585,6 @@
|
|||||||
sub: '改坏了?这里一键回滚'
|
sub: '改坏了?这里一键回滚'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 全部导航项扁平表(activeGroupLabel / 查找用) */
|
|
||||||
const ALL_NAV_ITEMS: NavItem[] = [
|
|
||||||
...NAV_SECTIONS.flatMap((s) => s.items),
|
|
||||||
VERSIONS_NAV
|
|
||||||
]
|
|
||||||
|
|
||||||
/** 通用 JSON 组(终审 I-1:contact/engine/misc 走 JsonGroupPane 一份实现) */
|
/** 通用 JSON 组(终审 I-1:contact/engine/misc 走 JsonGroupPane 一份实现) */
|
||||||
const JSON_GROUPS = new Set<GroupKey>(['contact', 'engine', 'misc'])
|
const JSON_GROUPS = new Set<GroupKey>(['contact', 'engine', 'misc'])
|
||||||
|
|
||||||
@@ -597,10 +601,6 @@
|
|||||||
activeGroup.value = index as GroupKey
|
activeGroup.value = index as GroupKey
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeGroupLabel = computed(
|
|
||||||
() => ALL_NAV_ITEMS.find((i) => i.key === activeGroup.value)?.label ?? ''
|
|
||||||
)
|
|
||||||
|
|
||||||
// ---- 租户上下文(超管可切换;租户管理员固定自己——useTenantOptions 收拢) ----------------
|
// ---- 租户上下文(超管可切换;租户管理员固定自己——useTenantOptions 收拢) ----------------
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
@@ -1056,32 +1056,23 @@
|
|||||||
.form-card {
|
.form-card {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.card-header {
|
// 表单区双列 grid(span-2 = 独占整行:头像/多选/长文本)——与 CredsPane 同款规则
|
||||||
display: flex;
|
.form-grid {
|
||||||
align-items: baseline;
|
display: grid;
|
||||||
justify-content: space-between;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 12px;
|
column-gap: 16px;
|
||||||
|
|
||||||
.card-title {
|
.span-2 {
|
||||||
font-weight: 600;
|
grid-column: span 2;
|
||||||
}
|
|
||||||
|
|
||||||
.card-sub {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--art-gray-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 头部右列:副标题 + 重置出厂小按钮(Task 7d)
|
|
||||||
.header-side {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment-input {
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
.color-row {
|
.color-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -1152,12 +1143,6 @@
|
|||||||
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 右列:预览 + 嵌入 --------------------------------------------------------------
|
// ---- 右列:预览 + 嵌入 --------------------------------------------------------------
|
||||||
|
|
||||||
.side-col {
|
.side-col {
|
||||||
@@ -1179,6 +1164,13 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预览卡标题下方副标题(Task 6——观感同 ConfigCard 的 cc-subtitle)
|
||||||
|
.card-subtitle {
|
||||||
|
margin: 2px 0 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--art-gray-500, var(--el-text-color-secondary));
|
||||||
|
}
|
||||||
|
|
||||||
.card-sub {
|
.card-sub {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--art-gray-400);
|
color: var(--art-gray-400);
|
||||||
@@ -1373,10 +1365,6 @@
|
|||||||
color: var(--art-gray-400);
|
color: var(--art-gray-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-card {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- 提示词/词表 pane 容器(Task 7b) ------------------------------------------------
|
// ---- 提示词/词表 pane 容器(Task 7b) ------------------------------------------------
|
||||||
|
|
||||||
.group-pane {
|
.group-pane {
|
||||||
|
|||||||
Reference in New Issue
Block a user