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