- 收敛管理端公共 Modal 链路,新增表单弹窗与普通内容弹窗包装\n- 迁移 Bot、知识库、插件、工作流、资源、MCP、数据中枢与系统管理页面级弹窗\n- 统一内容区工具栏、列表容器、导航与顶部按钮的视觉密度和交互节奏
351 lines
8.8 KiB
Vue
351 lines
8.8 KiB
Vue
<script setup lang="ts">
|
|
import type { BotInfo } from '@easyflow/types';
|
|
|
|
import type { SaveBotParams, UpdateBotParams } from '#/api/ai/bot';
|
|
|
|
import { computed, nextTick, ref } from 'vue';
|
|
|
|
import { EasyFlowFormModal } from '@easyflow/common-ui';
|
|
import { $t } from '@easyflow/locales';
|
|
|
|
import { ElForm, ElFormItem, ElInput, ElMessage } from 'element-plus';
|
|
import { tryit } from 'radash';
|
|
|
|
import { saveBot, updateBotApi } from '#/api/ai/bot';
|
|
import DictSelect from '#/components/dict/DictSelect.vue';
|
|
import UploadAvatar from '#/components/upload/UploadAvatar.vue';
|
|
|
|
const emit = defineEmits(['success']);
|
|
|
|
const createInitialFormData = (): SaveBotParams => ({
|
|
alias: '',
|
|
categoryId: '',
|
|
description: '',
|
|
icon: '',
|
|
status: 1,
|
|
title: '',
|
|
});
|
|
|
|
const dialogVisible = ref(false);
|
|
const dialogType = ref<'create' | 'edit'>('create');
|
|
const formRef = ref<InstanceType<typeof ElForm>>();
|
|
const formData = ref<SaveBotParams | UpdateBotParams>(createInitialFormData());
|
|
const loading = ref(false);
|
|
|
|
const rules = {
|
|
alias: [{ required: true, message: $t('message.required'), trigger: 'blur' }],
|
|
title: [{ required: true, message: $t('message.required'), trigger: 'blur' }],
|
|
};
|
|
|
|
const dialogTitle = computed(() => {
|
|
return `${$t(`button.${dialogType.value}`)}${$t('bot.chatAssistant')}`;
|
|
});
|
|
|
|
const dialogDescription = computed(() => {
|
|
return dialogType.value === 'create'
|
|
? $t('bot.modal.createDescription')
|
|
: $t('bot.modal.editDescription');
|
|
});
|
|
|
|
async function handleSubmit() {
|
|
const valid = await formRef.value?.validate().catch(() => false);
|
|
if (!valid) {
|
|
return;
|
|
}
|
|
|
|
loading.value = true;
|
|
|
|
const [err, res] = await tryit(
|
|
dialogType.value === 'create' ? saveBot : updateBotApi,
|
|
)(formData.value as SaveBotParams & UpdateBotParams);
|
|
|
|
if (!err && res.errorCode === 0) {
|
|
emit('success');
|
|
ElMessage.success(res.message || $t('message.saveOkMessage'));
|
|
dialogVisible.value = false;
|
|
loading.value = false;
|
|
return;
|
|
}
|
|
|
|
ElMessage.error(res?.message || $t('message.saveFail'));
|
|
loading.value = false;
|
|
}
|
|
|
|
async function open(type: 'create' | 'edit', bot?: BotInfo) {
|
|
formData.value = bot
|
|
? {
|
|
...createInitialFormData(),
|
|
alias: bot.alias,
|
|
categoryId: bot.categoryId,
|
|
description: bot.description,
|
|
icon: bot.icon,
|
|
id: bot.id,
|
|
status: bot.status,
|
|
title: bot.title,
|
|
}
|
|
: createInitialFormData();
|
|
dialogType.value = type;
|
|
dialogVisible.value = true;
|
|
|
|
await nextTick();
|
|
formRef.value?.clearValidate();
|
|
}
|
|
|
|
defineExpose({
|
|
open,
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<EasyFlowFormModal
|
|
v-model:open="dialogVisible"
|
|
:confirm-loading="loading"
|
|
:confirm-text="$t('button.save')"
|
|
:description="dialogDescription"
|
|
:submitting="loading"
|
|
:title="dialogTitle"
|
|
width="lg"
|
|
@confirm="handleSubmit"
|
|
>
|
|
<ElForm
|
|
ref="formRef"
|
|
:model="formData"
|
|
:rules="rules"
|
|
class="bot-modal-form"
|
|
label-position="top"
|
|
status-icon
|
|
>
|
|
<section class="bot-modal-section">
|
|
<header class="bot-modal-section__header">
|
|
<div>
|
|
<h4 class="bot-modal-section__title">
|
|
{{ $t('bot.modal.sectionAppearanceTitle') }}
|
|
</h4>
|
|
<p class="bot-modal-section__description">
|
|
{{ $t('bot.modal.sectionAppearanceDescription') }}
|
|
</p>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="bot-modal-appearance-grid">
|
|
<ElFormItem
|
|
class="bot-modal-avatar-field"
|
|
:label="$t('common.avatar')"
|
|
prop="icon"
|
|
>
|
|
<UploadAvatar
|
|
v-model="formData.icon"
|
|
:size="88"
|
|
theme="soft-panel"
|
|
/>
|
|
</ElFormItem>
|
|
|
|
<div class="bot-modal-appearance-fields">
|
|
<ElFormItem :label="$t('aiWorkflow.title')" prop="title">
|
|
<ElInput v-model="formData.title" />
|
|
</ElFormItem>
|
|
|
|
<ElFormItem :label="$t('aiWorkflow.categoryId')" prop="categoryId">
|
|
<DictSelect
|
|
v-model="formData.categoryId"
|
|
dict-code="aiBotCategory"
|
|
/>
|
|
</ElFormItem>
|
|
|
|
<ElFormItem
|
|
class="bot-modal-grid__full"
|
|
:label="$t('plugin.alias')"
|
|
prop="alias"
|
|
>
|
|
<ElInput v-model="formData.alias" />
|
|
<div class="bot-modal-field-tip">
|
|
{{ $t('bot.modal.aliasHint') }}
|
|
</div>
|
|
</ElFormItem>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="bot-modal-section bot-modal-section--compact">
|
|
<header class="bot-modal-section__header">
|
|
<div>
|
|
<h4 class="bot-modal-section__title">
|
|
{{ $t('bot.modal.sectionPublishTitle') }}
|
|
</h4>
|
|
<p class="bot-modal-section__description">
|
|
{{ $t('bot.modal.sectionPublishDescription') }}
|
|
</p>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="bot-modal-publish-grid">
|
|
<ElFormItem :label="$t('plugin.description')" prop="description">
|
|
<ElInput v-model="formData.description" :rows="3" type="textarea" />
|
|
<div class="bot-modal-field-tip">
|
|
{{ $t('bot.modal.descriptionHint') }}
|
|
</div>
|
|
</ElFormItem>
|
|
|
|
<ElFormItem
|
|
class="bot-modal-publish-field"
|
|
:label="$t('aiWorkflow.status')"
|
|
prop="status"
|
|
>
|
|
<DictSelect v-model="formData.status" dict-code="showOrNot" />
|
|
<div class="bot-modal-field-tip">
|
|
{{ $t('bot.modal.statusHint') }}
|
|
</div>
|
|
</ElFormItem>
|
|
</div>
|
|
</section>
|
|
</ElForm>
|
|
</EasyFlowFormModal>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.bot-modal-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.bot-modal-section {
|
|
border-radius: 14px;
|
|
border: 1px solid hsl(var(--modal-divider));
|
|
background: hsl(var(--modal-content-surface-strong));
|
|
box-shadow: none;
|
|
padding: 14px;
|
|
}
|
|
|
|
.bot-modal-section--compact {
|
|
padding-bottom: 12px;
|
|
}
|
|
|
|
.bot-modal-section__header {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.bot-modal-section__title {
|
|
margin: 0;
|
|
color: hsl(var(--text-strong));
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.bot-modal-section__description {
|
|
margin: 3px 0 0;
|
|
color: hsl(var(--text-muted));
|
|
font-size: 11px;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.bot-modal-appearance-grid {
|
|
display: grid;
|
|
grid-template-columns: 112px minmax(0, 1fr);
|
|
gap: 14px 16px;
|
|
align-items: start;
|
|
}
|
|
|
|
.bot-modal-appearance-fields {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 14px 16px;
|
|
}
|
|
|
|
.bot-modal-grid__full {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.bot-modal-publish-grid {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 220px);
|
|
gap: 14px 16px;
|
|
align-items: start;
|
|
}
|
|
|
|
.bot-modal-publish-field {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.bot-modal-avatar-field {
|
|
align-items: flex-start;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.bot-modal-avatar-field :deep(.el-form-item__content) {
|
|
display: block;
|
|
}
|
|
|
|
.bot-modal-field-tip {
|
|
margin-top: 4px;
|
|
color: hsl(var(--text-muted));
|
|
font-size: 11px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-form-item) {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-form-item__label) {
|
|
padding-bottom: 6px;
|
|
color: hsl(var(--text-strong));
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-input__wrapper),
|
|
.bot-modal-form :deep(.el-select__wrapper),
|
|
.bot-modal-form :deep(.el-textarea__inner) {
|
|
border-radius: 12px;
|
|
background: hsl(var(--input-background));
|
|
box-shadow: inset 0 0 0 1px hsl(var(--input) / 0.92);
|
|
transition:
|
|
box-shadow 0.2s ease,
|
|
transform 0.2s ease,
|
|
border-color 0.2s ease;
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-input__wrapper),
|
|
.bot-modal-form :deep(.el-select__wrapper) {
|
|
min-height: 42px;
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-input__wrapper:hover),
|
|
.bot-modal-form :deep(.el-select__wrapper:hover),
|
|
.bot-modal-form :deep(.el-textarea__inner:hover) {
|
|
box-shadow: inset 0 0 0 1px hsl(var(--primary) / 0.18);
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-input__wrapper.is-focus),
|
|
.bot-modal-form :deep(.el-select__wrapper.is-focused),
|
|
.bot-modal-form :deep(.el-textarea__inner:focus) {
|
|
box-shadow:
|
|
inset 0 0 0 1px hsl(var(--primary) / 0.72),
|
|
0 0 0 4px hsl(var(--primary) / 0.12);
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-textarea__inner) {
|
|
min-height: 88px;
|
|
padding: 10px 12px;
|
|
}
|
|
|
|
.bot-modal-form :deep(.el-form-item.is-error .el-input__wrapper),
|
|
.bot-modal-form :deep(.el-form-item.is-error .el-select__wrapper),
|
|
.bot-modal-form :deep(.el-form-item.is-error .el-textarea__inner) {
|
|
box-shadow:
|
|
inset 0 0 0 1px hsl(var(--destructive) / 0.8),
|
|
0 0 0 4px hsl(var(--destructive) / 0.08);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.bot-modal-appearance-grid,
|
|
.bot-modal-appearance-fields,
|
|
.bot-modal-publish-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|