feat(admin): 凭据页套视觉基线+SecretField 四件套
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -7,38 +7,30 @@
|
|||||||
- AI(ai 组):provider / API Key / base_url / model
|
- AI(ai 组):provider / API Key / base_url / model
|
||||||
- 密钥哨兵交互(mask.ts):GET 密钥为 '__SET__' → 输入框留空 + 占位符
|
- 密钥哨兵交互(mask.ts):GET 密钥为 '__SET__' → 输入框留空 + 占位符
|
||||||
「已设置,输入新值覆盖」;不动保存回传哨兵保持库值;「清除」按钮 =
|
「已设置,输入新值覆盖」;不动保存回传哨兵保持库值;「清除」按钮 =
|
||||||
显式清空意图(提交空串),可再点「恢复保持」撤销
|
显式清空意图(提交空串),可再点「恢复保持」撤销——交互由 SecretField
|
||||||
|
四件套组件承载(Task 5 起),提交值仍由父层 build*Payload 计算
|
||||||
- SMTP 测试邮件:收件人 + 按钮 → POST email/test——用**已保存**配置真发
|
- SMTP 测试邮件:收件人 + 按钮 → POST email/test——用**已保存**配置真发
|
||||||
一封(先保存再测试);未配置 400 / 发送失败 sent=false 均内联展示
|
一封(先保存再测试);未配置 400 / 发送失败 sent=false 均内联展示
|
||||||
- 推送连通性探测:POST push/test(后端发空 messages 批量,零线索数据)
|
- 推送连通性探测:POST push/test(后端发空 messages 批量,零线索数据)
|
||||||
——三态展示:不可达(dns/timeout/connect 原因分类)/ 可达但鉴权失败
|
——三态展示:不可达(dns/timeout/connect 原因分类)/ 可达但鉴权失败
|
||||||
(401·403)/ 正常(2xx);其余状态码 = 可达但鉴权不定
|
(401·403)/ 正常(2xx);其余状态码 = 可达但鉴权不定
|
||||||
- 重置出厂(Task 7d):三卡头部危险色小按钮 → 确认框 → POST
|
- 重置出厂(Task 7d):卡底危险行小按钮 → 确认框 → POST
|
||||||
config/{group}/reset → 表单/密钥态回档案值 + emit saved;push 出厂
|
config/{group}/reset → 表单/密钥态回档案值 + emit saved;push 出厂
|
||||||
前缀被其他租户占用的 400 detail 内联展示
|
前缀被其他租户占用的 400 detail 内联展示
|
||||||
|
- Task 5 视觉基线:三卡套 ConfigCard(图标/副标题/信息条/footer/danger
|
||||||
|
行);footer-left「出问题?回滚 →」emit navigate('versions')(父层
|
||||||
|
接线在后续任务);表单区双列 grid
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div class="creds-pane">
|
<div class="creds-pane">
|
||||||
<!-- 邮件 -->
|
<!-- 邮件 -->
|
||||||
<ElCard shadow="never" class="form-card">
|
<ConfigCard
|
||||||
<template #header>
|
icon="solar:letter-linear"
|
||||||
<div class="card-header">
|
title="邮件通知"
|
||||||
<span class="card-title">邮件(email 组 · SMTP 发信)</span>
|
subtitle="客户留下线索后,系统用这组配置发通知邮件"
|
||||||
<div class="header-side">
|
info="整组保存 · 立即生效 · 产生新版本可回滚;带 • 的字段留空 = 沿用平台默认"
|
||||||
<span class="card-sub">空槽 = 未设置 → 回退平台环境默认;保存即写新版本并热生效</span>
|
danger-label="恢复出厂:把邮件配置重置为初始档案值"
|
||||||
<ElButton
|
>
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
:loading="resettingEmail"
|
|
||||||
@click="onResetGroup('email')"
|
|
||||||
>
|
|
||||||
重置出厂
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElAlert
|
<ElAlert
|
||||||
v-if="emailError"
|
v-if="emailError"
|
||||||
type="error"
|
type="error"
|
||||||
@@ -49,81 +41,54 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ElForm label-width="130px" @submit.prevent>
|
<ElForm label-width="130px" @submit.prevent>
|
||||||
<ElFormItem label="SMTP 服务器">
|
<div class="form-grid">
|
||||||
<ElInput
|
<ElFormItem label="SMTP 服务器">
|
||||||
v-model="emailForm.smtpHost"
|
|
||||||
placeholder="如 smtp.exmail.qq.com(留空 = 平台默认)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="SMTP 端口">
|
|
||||||
<ElInput
|
|
||||||
v-model="emailForm.smtpPort"
|
|
||||||
placeholder="如 465(1-65535 整数,留空 = 平台默认)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="SMTP 账号">
|
|
||||||
<ElInput
|
|
||||||
v-model="emailForm.smtpUser"
|
|
||||||
placeholder="发件邮箱账号(留空 = 平台默认)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="SMTP 密码">
|
|
||||||
<div class="secret-row">
|
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="emailForm.smtpPassword"
|
v-model="emailForm.smtpHost"
|
||||||
type="password"
|
placeholder="如 smtp.exmail.qq.com(留空 = 平台默认)"
|
||||||
show-password
|
|
||||||
autocomplete="new-password"
|
|
||||||
:placeholder="emailPasswordPlaceholder"
|
|
||||||
class="secret-input"
|
|
||||||
/>
|
/>
|
||||||
<ElButton
|
</ElFormItem>
|
||||||
v-if="emailPasswordMasked === MASKED_SENTINEL"
|
<ElFormItem label="SMTP 端口">
|
||||||
size="small"
|
<ElInput
|
||||||
:type="emailPasswordCleared ? 'default' : 'danger'"
|
v-model="emailForm.smtpPort"
|
||||||
plain
|
placeholder="如 465(1-65535 整数,留空 = 平台默认)"
|
||||||
@click="emailPasswordCleared = !emailPasswordCleared"
|
/>
|
||||||
>
|
</ElFormItem>
|
||||||
{{ emailPasswordCleared ? '恢复保持' : '清除' }}
|
<ElFormItem label="SMTP 账号">
|
||||||
</ElButton>
|
<ElInput
|
||||||
</div>
|
v-model="emailForm.smtpUser"
|
||||||
<p v-if="emailPasswordCleared" class="field-warn">
|
placeholder="发件邮箱账号(留空 = 平台默认)"
|
||||||
保存后将清除已存密码(空槽回退平台默认)
|
/>
|
||||||
</p>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="发件人名称">
|
||||||
<ElFormItem label="发件人名称">
|
<ElInput
|
||||||
<ElInput
|
v-model="emailForm.senderName"
|
||||||
v-model="emailForm.senderName"
|
placeholder='邮件【发件人名称】展示(留空 = "HESUPIPE")'
|
||||||
placeholder='邮件【发件人名称】展示(留空 = "HESUPIPE")'
|
/>
|
||||||
/>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="SMTP 密码" class="span-2">
|
||||||
<ElFormItem label="通知邮箱">
|
<SecretField
|
||||||
<ElSelect
|
v-model="emailForm.smtpPassword"
|
||||||
v-model="emailForm.notifyEmails"
|
:masked="emailPasswordMasked"
|
||||||
multiple
|
:cleared="emailPasswordCleared"
|
||||||
filterable
|
@update:cleared="emailPasswordCleared = $event"
|
||||||
allow-create
|
/>
|
||||||
default-first-option
|
</ElFormItem>
|
||||||
:reserve-keyword="false"
|
<ElFormItem label="通知邮箱" class="span-2">
|
||||||
placeholder="输入邮箱后回车添加(新询盘通知收件人,留空 = 平台默认)"
|
<ElSelect
|
||||||
class="wide-control"
|
v-model="emailForm.notifyEmails"
|
||||||
/>
|
multiple
|
||||||
</ElFormItem>
|
filterable
|
||||||
<ElFormItem label="保存备注">
|
allow-create
|
||||||
<ElInput
|
default-first-option
|
||||||
v-model="emailComment"
|
:reserve-keyword="false"
|
||||||
maxlength="200"
|
placeholder="输入邮箱后回车添加(新询盘通知收件人,留空 = 平台默认)"
|
||||||
placeholder="可选;写入版本历史 comment"
|
class="wide-control"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</div>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<div class="save-bar">
|
|
||||||
<ElButton type="primary" :loading="emailSaving" @click="onSaveEmail">
|
|
||||||
保存邮件组(整组提交,热生效)
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ElDivider class="test-divider" align="left">连通性验证(用已保存配置)</ElDivider>
|
<ElDivider class="test-divider" align="left">连通性验证(用已保存配置)</ElDivider>
|
||||||
<div class="test-row">
|
<div class="test-row">
|
||||||
<ElInput
|
<ElInput
|
||||||
@@ -132,11 +97,11 @@
|
|||||||
class="test-input"
|
class="test-input"
|
||||||
/>
|
/>
|
||||||
<ElButton :loading="emailTesting" :disabled="!slug" @click="onTestEmail">
|
<ElButton :loading="emailTesting" :disabled="!slug" @click="onTestEmail">
|
||||||
发测试邮件
|
发送测试邮件(真发一封)
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<p class="test-hint">
|
<p class="test-hint">
|
||||||
后端用**已保存**的邮件配置真发一封(主题「[all-kefu] SMTP 测试邮件」)
|
系统用**已保存**的邮件配置真发一封(主题「[all-kefu] SMTP 测试邮件」)
|
||||||
——改完先保存再测试;SMTP 未配置时返回 400。
|
——改完先保存再测试;SMTP 未配置时返回 400。
|
||||||
</p>
|
</p>
|
||||||
<ElAlert
|
<ElAlert
|
||||||
@@ -148,28 +113,42 @@
|
|||||||
class="test-result"
|
class="test-result"
|
||||||
@close="emailTestResult = null"
|
@close="emailTestResult = null"
|
||||||
/>
|
/>
|
||||||
</ElCard>
|
|
||||||
|
<template #footer-left>
|
||||||
|
<ElButton link type="primary" size="small" @click="$emit('navigate', 'versions')">
|
||||||
|
出问题?回滚 →
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
<template #footer-right>
|
||||||
|
<ElInput
|
||||||
|
v-model="emailComment"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="备注(选填)"
|
||||||
|
class="comment-input"
|
||||||
|
/>
|
||||||
|
<ElButton type="primary" :loading="emailSaving" @click="onSaveEmail">保存</ElButton>
|
||||||
|
</template>
|
||||||
|
<template #danger>
|
||||||
|
<ElButton
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:loading="resettingEmail"
|
||||||
|
@click="onResetGroup('email')"
|
||||||
|
>
|
||||||
|
重置出厂…
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ConfigCard>
|
||||||
|
|
||||||
<!-- 推送 -->
|
<!-- 推送 -->
|
||||||
<ElCard shadow="never" class="form-card">
|
<ConfigCard
|
||||||
<template #header>
|
icon="solar:upload-square-line-duotone"
|
||||||
<div class="card-header">
|
title="线索推送"
|
||||||
<span class="card-title">推送(push 组 · 线索推送 UniService)</span>
|
subtitle="新线索实时推送到你的业务系统(UniService)"
|
||||||
<div class="header-side">
|
info="数值留空 = 平台默认;前缀全局唯一(跨租户查重)——整组保存 · 立即生效 · 可回滚"
|
||||||
<span class="card-sub">数值留空 = 平台默认;前缀全局唯一(跨租户查重)</span>
|
danger-label="恢复出厂:把推送配置重置为初始档案值"
|
||||||
<ElButton
|
>
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
:loading="resettingPush"
|
|
||||||
@click="onResetGroup('push')"
|
|
||||||
>
|
|
||||||
重置出厂
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElAlert
|
<ElAlert
|
||||||
v-if="pushError"
|
v-if="pushError"
|
||||||
type="error"
|
type="error"
|
||||||
@@ -180,79 +159,52 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ElForm label-width="130px" @submit.prevent>
|
<ElForm label-width="130px" @submit.prevent>
|
||||||
<ElFormItem label="启用推送">
|
<div class="form-grid">
|
||||||
<ElSwitch v-model="pushForm.enabled" />
|
<ElFormItem label="启用推送" class="span-2">
|
||||||
<span class="switch-hint">关闭后新线索不推送(只落库)</span>
|
<ElSwitch v-model="pushForm.enabled" />
|
||||||
</ElFormItem>
|
<span class="switch-hint">关闭后新线索不推送(只落库)</span>
|
||||||
<ElFormItem label="推送 URL">
|
</ElFormItem>
|
||||||
<ElInput
|
<ElFormItem label="推送 URL">
|
||||||
v-model="pushForm.url"
|
|
||||||
placeholder="https://push.example/open/v1/inquiry/push(留空 = 平台默认)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="API Key">
|
|
||||||
<div class="secret-row">
|
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="pushForm.apiKey"
|
v-model="pushForm.url"
|
||||||
type="password"
|
placeholder="https://push.example/open/v1/inquiry/push(留空 = 平台默认)"
|
||||||
show-password
|
|
||||||
autocomplete="new-password"
|
|
||||||
:placeholder="pushApiKeyPlaceholder"
|
|
||||||
class="secret-input"
|
|
||||||
/>
|
/>
|
||||||
<ElButton
|
</ElFormItem>
|
||||||
v-if="pushApiKeyMasked === MASKED_SENTINEL"
|
<ElFormItem label="线索前缀">
|
||||||
size="small"
|
<ElInput
|
||||||
:type="pushApiKeyCleared ? 'default' : 'danger'"
|
v-model="pushForm.prefix"
|
||||||
plain
|
placeholder="external_key 前缀(全局唯一;留空 = 平台默认)"
|
||||||
@click="pushApiKeyCleared = !pushApiKeyCleared"
|
/>
|
||||||
>
|
</ElFormItem>
|
||||||
{{ pushApiKeyCleared ? '恢复保持' : '清除' }}
|
<ElFormItem label="API Key" class="span-2">
|
||||||
</ElButton>
|
<SecretField
|
||||||
</div>
|
v-model="pushForm.apiKey"
|
||||||
<p v-if="pushApiKeyCleared" class="field-warn">
|
:masked="pushApiKeyMasked"
|
||||||
保存后将清除已存 API Key(空槽回退平台默认)
|
:cleared="pushApiKeyCleared"
|
||||||
</p>
|
@update:cleared="pushApiKeyCleared = $event"
|
||||||
</ElFormItem>
|
/>
|
||||||
<ElFormItem label="线索前缀">
|
</ElFormItem>
|
||||||
<ElInput
|
<ElFormItem label="超时(秒)">
|
||||||
v-model="pushForm.prefix"
|
<ElInput
|
||||||
placeholder="external_key 前缀(全局唯一;留空 = 平台默认)"
|
v-model="pushForm.timeout"
|
||||||
/>
|
placeholder="正数,如 10(留空 = 平台默认)"
|
||||||
</ElFormItem>
|
/>
|
||||||
<ElFormItem label="超时(秒)">
|
</ElFormItem>
|
||||||
<ElInput
|
<ElFormItem label="最大尝试次数">
|
||||||
v-model="pushForm.timeout"
|
<ElInput
|
||||||
placeholder="正数,如 10(留空 = 平台默认)"
|
v-model="pushForm.maxAttempts"
|
||||||
/>
|
placeholder="1-10 整数,如 3(留空 = 平台默认)"
|
||||||
</ElFormItem>
|
/>
|
||||||
<ElFormItem label="最大尝试次数">
|
</ElFormItem>
|
||||||
<ElInput
|
<ElFormItem label="退避基数(秒)">
|
||||||
v-model="pushForm.maxAttempts"
|
<ElInput
|
||||||
placeholder="1-10 整数,如 3(留空 = 平台默认)"
|
v-model="pushForm.backoffBase"
|
||||||
/>
|
placeholder="正数,如 1(留空 = 平台默认)"
|
||||||
</ElFormItem>
|
/>
|
||||||
<ElFormItem label="退避基数(秒)">
|
</ElFormItem>
|
||||||
<ElInput
|
</div>
|
||||||
v-model="pushForm.backoffBase"
|
|
||||||
placeholder="正数,如 1(留空 = 平台默认)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="保存备注">
|
|
||||||
<ElInput
|
|
||||||
v-model="pushComment"
|
|
||||||
maxlength="200"
|
|
||||||
placeholder="可选;写入版本历史 comment"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<div class="save-bar">
|
|
||||||
<ElButton type="primary" :loading="pushSaving" @click="onSavePush">
|
|
||||||
保存推送组(整组提交,热生效)
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ElDivider class="test-divider" align="left">连通性探测(用已保存配置)</ElDivider>
|
<ElDivider class="test-divider" align="left">连通性探测(用已保存配置)</ElDivider>
|
||||||
<div class="test-row">
|
<div class="test-row">
|
||||||
<ElButton :loading="pushProbing" :disabled="!slug" @click="onProbePush">
|
<ElButton :loading="pushProbing" :disabled="!slug" @click="onProbePush">
|
||||||
@@ -304,28 +256,42 @@
|
|||||||
:description="pushProbe.error || undefined"
|
:description="pushProbe.error || undefined"
|
||||||
@close="pushProbe = null"
|
@close="pushProbe = null"
|
||||||
/>
|
/>
|
||||||
</ElCard>
|
|
||||||
|
<template #footer-left>
|
||||||
|
<ElButton link type="primary" size="small" @click="$emit('navigate', 'versions')">
|
||||||
|
出问题?回滚 →
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
<template #footer-right>
|
||||||
|
<ElInput
|
||||||
|
v-model="pushComment"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="备注(选填)"
|
||||||
|
class="comment-input"
|
||||||
|
/>
|
||||||
|
<ElButton type="primary" :loading="pushSaving" @click="onSavePush">保存</ElButton>
|
||||||
|
</template>
|
||||||
|
<template #danger>
|
||||||
|
<ElButton
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:loading="resettingPush"
|
||||||
|
@click="onResetGroup('push')"
|
||||||
|
>
|
||||||
|
重置出厂…
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ConfigCard>
|
||||||
|
|
||||||
<!-- AI -->
|
<!-- AI -->
|
||||||
<ElCard shadow="never" class="form-card">
|
<ConfigCard
|
||||||
<template #header>
|
icon="solar:cpu-bolt-line-duotone"
|
||||||
<div class="card-header">
|
title="AI 服务"
|
||||||
<span class="card-title">AI(ai 组 · 模型服务)</span>
|
subtitle="对话引擎调用的大模型服务凭据"
|
||||||
<div class="header-side">
|
info="整组保存 · 立即生效 · 可回滚;留空 = 平台默认"
|
||||||
<span class="card-sub">密钥空槽 = 未设置 → 回退平台集团凭据(spec §5.5)</span>
|
danger-label="恢复出厂:把 AI 配置重置为初始档案值"
|
||||||
<ElButton
|
>
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
plain
|
|
||||||
:loading="resettingAi"
|
|
||||||
@click="onResetGroup('ai')"
|
|
||||||
>
|
|
||||||
重置出厂
|
|
||||||
</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElAlert
|
<ElAlert
|
||||||
v-if="aiError"
|
v-if="aiError"
|
||||||
type="error"
|
type="error"
|
||||||
@@ -336,63 +302,62 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ElForm label-width="130px" @submit.prevent>
|
<ElForm label-width="130px" @submit.prevent>
|
||||||
<ElFormItem label="Provider">
|
<div class="form-grid">
|
||||||
<ElInput
|
<ElFormItem label="Provider">
|
||||||
v-model="aiForm.provider"
|
|
||||||
placeholder="如 glm(留空 = 平台默认)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="API Key">
|
|
||||||
<div class="secret-row">
|
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="aiForm.apiKey"
|
v-model="aiForm.provider"
|
||||||
type="password"
|
placeholder="如 glm(留空 = 平台默认)"
|
||||||
show-password
|
|
||||||
autocomplete="new-password"
|
|
||||||
:placeholder="aiApiKeyPlaceholder"
|
|
||||||
class="secret-input"
|
|
||||||
/>
|
/>
|
||||||
<ElButton
|
</ElFormItem>
|
||||||
v-if="aiApiKeyMasked === MASKED_SENTINEL"
|
<ElFormItem label="模型">
|
||||||
size="small"
|
<ElInput
|
||||||
:type="aiApiKeyCleared ? 'default' : 'danger'"
|
v-model="aiForm.model"
|
||||||
plain
|
placeholder="如 glm-4-flash(留空 = 平台默认)"
|
||||||
@click="aiApiKeyCleared = !aiApiKeyCleared"
|
/>
|
||||||
>
|
</ElFormItem>
|
||||||
{{ aiApiKeyCleared ? '恢复保持' : '清除' }}
|
<ElFormItem label="Base URL" class="span-2">
|
||||||
</ElButton>
|
<ElInput
|
||||||
</div>
|
v-model="aiForm.baseUrl"
|
||||||
<p v-if="aiApiKeyCleared" class="field-warn">
|
placeholder="如 https://open.bigmodel.cn/api/paas/v4(留空 = 平台默认)"
|
||||||
保存后将清除已存 API Key(空槽回退平台集团凭据)
|
/>
|
||||||
</p>
|
</ElFormItem>
|
||||||
</ElFormItem>
|
<ElFormItem label="API Key" class="span-2">
|
||||||
<ElFormItem label="Base URL">
|
<SecretField
|
||||||
<ElInput
|
v-model="aiForm.apiKey"
|
||||||
v-model="aiForm.baseUrl"
|
:masked="aiApiKeyMasked"
|
||||||
placeholder="如 https://open.bigmodel.cn/api/paas/v4(留空 = 平台默认)"
|
:cleared="aiApiKeyCleared"
|
||||||
/>
|
@update:cleared="aiApiKeyCleared = $event"
|
||||||
</ElFormItem>
|
/>
|
||||||
<ElFormItem label="模型">
|
</ElFormItem>
|
||||||
<ElInput
|
</div>
|
||||||
v-model="aiForm.model"
|
|
||||||
placeholder="如 glm-4-flash(留空 = 平台默认)"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem label="保存备注">
|
|
||||||
<ElInput
|
|
||||||
v-model="aiComment"
|
|
||||||
maxlength="200"
|
|
||||||
placeholder="可选;写入版本历史 comment"
|
|
||||||
/>
|
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<div class="save-bar">
|
<template #footer-left>
|
||||||
<ElButton type="primary" :loading="aiSaving" @click="onSaveAi">
|
<ElButton link type="primary" size="small" @click="$emit('navigate', 'versions')">
|
||||||
保存 AI 组(整组提交,热生效)
|
出问题?回滚 →
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</template>
|
||||||
</ElCard>
|
<template #footer-right>
|
||||||
|
<ElInput
|
||||||
|
v-model="aiComment"
|
||||||
|
maxlength="200"
|
||||||
|
placeholder="备注(选填)"
|
||||||
|
class="comment-input"
|
||||||
|
/>
|
||||||
|
<ElButton type="primary" :loading="aiSaving" @click="onSaveAi">保存</ElButton>
|
||||||
|
</template>
|
||||||
|
<template #danger>
|
||||||
|
<ElButton
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:loading="resettingAi"
|
||||||
|
@click="onResetGroup('ai')"
|
||||||
|
>
|
||||||
|
重置出厂…
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
</ConfigCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -413,8 +378,9 @@
|
|||||||
type PushCredsForm
|
type PushCredsForm
|
||||||
} from './model'
|
} from './model'
|
||||||
import { putConfigGroup, testEmail, testPush, type PushTestResponse } from '@/api/config'
|
import { putConfigGroup, testEmail, testPush, type PushTestResponse } from '@/api/config'
|
||||||
import { MASKED_SENTINEL, secretPlaceholder } from '@/api/mask'
|
|
||||||
import { confirmAndResetGroup } from './reset'
|
import { confirmAndResetGroup } from './reset'
|
||||||
|
import ConfigCard from './ConfigCard.vue'
|
||||||
|
import SecretField from './SecretField.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'CredsPane' })
|
defineOptions({ name: 'CredsPane' })
|
||||||
|
|
||||||
@@ -439,6 +405,8 @@
|
|||||||
config: Record<string, unknown>
|
config: Record<string, unknown>
|
||||||
}
|
}
|
||||||
): void
|
): void
|
||||||
|
/** footer 回滚入口:请求父层切到对应组('versions' = 版本历史) */
|
||||||
|
(e: 'navigate', group: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// ---- 编辑态(组引用变化 = 保存回写/租户切换/重载 → 重置表单与密钥态) ----------------
|
// ---- 编辑态(组引用变化 = 保存回写/租户切换/重载 → 重置表单与密钥态) ----------------
|
||||||
@@ -513,20 +481,6 @@
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const emailPasswordPlaceholder = computed(() =>
|
|
||||||
emailPasswordCleared.value
|
|
||||||
? secretPlaceholder('')
|
|
||||||
: secretPlaceholder(emailPasswordMasked.value)
|
|
||||||
)
|
|
||||||
const pushApiKeyPlaceholder = computed(() =>
|
|
||||||
pushApiKeyCleared.value
|
|
||||||
? secretPlaceholder('')
|
|
||||||
: secretPlaceholder(pushApiKeyMasked.value)
|
|
||||||
)
|
|
||||||
const aiApiKeyPlaceholder = computed(() =>
|
|
||||||
aiApiKeyCleared.value ? secretPlaceholder('') : secretPlaceholder(aiApiKeyMasked.value)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ---- 保存(前端预检 → 整组 PUT;密钥不动回哨兵保持库值) ------------------------------
|
// ---- 保存(前端预检 → 整组 PUT;密钥不动回哨兵保持库值) ------------------------------
|
||||||
|
|
||||||
async function onSaveEmail(): Promise<void> {
|
async function onSaveEmail(): Promise<void> {
|
||||||
@@ -698,7 +652,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 400「SMTP 未配置」等后端预检拒绝(ApiError.message = detail)
|
// 400「SMTP 未配置」/ 429「测试端点调用过于频繁」等后端预检拒绝
|
||||||
|
// (ApiError.message = detail,白话文案直接内联展示)
|
||||||
emailTestResult.value = {
|
emailTestResult.value = {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
text: error instanceof Error ? error.message : '测试发送失败'
|
text: error instanceof Error ? error.message : '测试发送失败'
|
||||||
@@ -724,7 +679,7 @@
|
|||||||
try {
|
try {
|
||||||
pushProbe.value = await testPush(props.slug, { showErrorMessage: false })
|
pushProbe.value = await testPush(props.slug, { showErrorMessage: false })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 400「推送 URL 未配置」等后端预检拒绝
|
// 400「推送 URL 未配置」/ 429「测试端点调用过于频繁」等后端预检拒绝
|
||||||
pushProbeError.value = error instanceof Error ? error.message : '探测失败'
|
pushProbeError.value = error instanceof Error ? error.message : '探测失败'
|
||||||
} finally {
|
} finally {
|
||||||
pushProbing.value = false
|
pushProbing.value = false
|
||||||
@@ -742,52 +697,21 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pane-error {
|
.pane-error {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secret-row {
|
// 表单区双列 grid(span-2 = 独占整行:密钥/长控件/开关)
|
||||||
display: flex;
|
.form-grid {
|
||||||
gap: 8px;
|
display: grid;
|
||||||
align-items: center;
|
grid-template-columns: 1fr 1fr;
|
||||||
width: 100%;
|
column-gap: 16px;
|
||||||
|
|
||||||
.secret-input {
|
.span-2 {
|
||||||
flex: 1;
|
grid-column: span 2;
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-warn {
|
|
||||||
margin: 4px 0 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-color-danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-hint {
|
.switch-hint {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -798,10 +722,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-bar {
|
.comment-input {
|
||||||
display: flex;
|
width: 220px;
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-divider {
|
.test-divider {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
{{ cleared ? '恢复保持' : '清除' }}
|
{{ cleared ? '恢复保持' : '清除' }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="state.badge === 'pending-clear'" class="field-warn">
|
<p v-if="state.badge === 'pending-clear'" role="status" class="field-warn">
|
||||||
保存后将清除已存密钥(空槽回退平台默认)
|
保存后将清除已存密钥(空槽回退平台默认)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,6 +67,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
|
:deep(.el-button) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.secret-input {
|
.secret-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user