feat: 统一管理端弹窗与内容区交互样式
- 收敛管理端公共 Modal 链路,新增表单弹窗与普通内容弹窗包装\n- 迁移 Bot、知识库、插件、工作流、资源、MCP、数据中枢与系统管理页面级弹窗\n- 统一内容区工具栏、列表容器、导航与顶部按钮的视觉密度和交互节奏
This commit is contained in:
@@ -8,12 +8,11 @@ import type { ActionButton } from '#/components/page/CardList.vue';
|
||||
import { computed, markRaw, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { EasyFlowFormModal } from '@easyflow/common-ui';
|
||||
import { $t } from '@easyflow/locales';
|
||||
|
||||
import { Delete, Edit, Plus, Setting } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
@@ -299,16 +298,21 @@ const getSideList = async () => {
|
||||
<!-- 创建&编辑Bot弹窗 -->
|
||||
<Modal ref="modalRef" @success="pageDataRef.setQuery({})" />
|
||||
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
<EasyFlowFormModal
|
||||
v-model:open="dialogVisible"
|
||||
:closable="!saveLoading"
|
||||
:title="formData.id ? `${$t('button.edit')}` : `${$t('button.add')}`"
|
||||
:close-on-click-modal="false"
|
||||
:confirm-loading="saveLoading"
|
||||
:confirm-text="$t('button.confirm')"
|
||||
:submitting="saveLoading"
|
||||
@confirm="handleSubmit"
|
||||
>
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
label-position="top"
|
||||
class="easyflow-modal-form easyflow-modal-form--compact"
|
||||
>
|
||||
<!-- 动态生成表单项 -->
|
||||
<ElFormItem
|
||||
@@ -330,15 +334,6 @@ const getSideList = async () => {
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="dialogVisible = false">
|
||||
{{ $t('button.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit" :loading="saveLoading">
|
||||
{{ $t('button.confirm') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</EasyFlowFormModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,18 +3,12 @@ import type { BotInfo } from '@easyflow/types';
|
||||
|
||||
import type { SaveBotParams, UpdateBotParams } from '#/api/ai/bot';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { EasyFlowFormModal } from '@easyflow/common-ui';
|
||||
import { $t } from '@easyflow/locales';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElDialog,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElMessage,
|
||||
} from 'element-plus';
|
||||
import { ElForm, ElFormItem, ElInput, ElMessage } from 'element-plus';
|
||||
import { tryit } from 'radash';
|
||||
|
||||
import { saveBot, updateBotApi } from '#/api/ai/bot';
|
||||
@@ -23,98 +17,334 @@ import UploadAvatar from '#/components/upload/UploadAvatar.vue';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const initialFormData = {
|
||||
icon: '',
|
||||
title: '',
|
||||
const createInitialFormData = (): SaveBotParams => ({
|
||||
alias: '',
|
||||
description: '',
|
||||
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>(initialFormData);
|
||||
const rules = {
|
||||
title: [{ required: true, message: $t('message.required'), trigger: 'blur' }],
|
||||
alias: [{ required: true, message: $t('message.required'), trigger: 'blur' }],
|
||||
};
|
||||
const formData = ref<SaveBotParams | UpdateBotParams>(createInitialFormData());
|
||||
const loading = ref(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
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 any);
|
||||
)(formData.value as SaveBotParams & UpdateBotParams);
|
||||
|
||||
if (!err && res.errorCode === 0) {
|
||||
emit('success');
|
||||
ElMessage.success($t('message.saveOkMessage'));
|
||||
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(type: typeof dialogType.value, bot?: BotInfo) {
|
||||
formData.value = bot
|
||||
? {
|
||||
id: bot.id,
|
||||
icon: bot.icon,
|
||||
title: bot.title,
|
||||
alias: bot.alias,
|
||||
description: bot.description,
|
||||
categoryId: bot.categoryId,
|
||||
status: bot.status,
|
||||
}
|
||||
: initialFormData;
|
||||
dialogType.value = type;
|
||||
dialogVisible.value = true;
|
||||
},
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="`${$t(`button.${dialogType}`)}${$t('bot.chatAssistant')}`"
|
||||
draggable
|
||||
align-center
|
||||
<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" label-width="150px">
|
||||
<ElFormItem :label="$t('common.avatar')" prop="icon">
|
||||
<UploadAvatar v-model="formData.icon" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="categoryId" :label="$t('aiWorkflow.categoryId')">
|
||||
<DictSelect v-model="formData.categoryId" dict-code="aiBotCategory" />
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('aiWorkflow.title')" prop="title">
|
||||
<ElInput v-model="formData.title" />
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('plugin.alias')" prop="alias">
|
||||
<ElInput v-model="formData.alias" />
|
||||
</ElFormItem>
|
||||
<ElFormItem :label="$t('plugin.description')" prop="description">
|
||||
<ElInput type="textarea" :rows="3" v-model="formData.description" />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop="status" :label="$t('aiWorkflow.status')">
|
||||
<DictSelect v-model="formData.status" dict-code="showOrNot" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<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>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="dialogVisible = false">
|
||||
{{ $t('button.cancel') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ $t('button.save') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
<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>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { EasyFlowPanelModal } from '@easyflow/common-ui';
|
||||
import { $t } from '@easyflow/locales';
|
||||
|
||||
import { ElButton, ElDialog, ElForm, ElFormItem, ElInput } from 'element-plus';
|
||||
import { ElButton, ElForm, ElFormItem, ElInput } from 'element-plus';
|
||||
|
||||
import { sseClient } from '#/api/request';
|
||||
|
||||
@@ -58,14 +59,21 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
<EasyFlowPanelModal
|
||||
v-model:open="dialogVisible"
|
||||
:title="$t('bot.aiOptimizedPrompts')"
|
||||
draggable
|
||||
align-center
|
||||
width="550px"
|
||||
:centered="true"
|
||||
:show-cancel-button="false"
|
||||
:show-confirm-button="false"
|
||||
>
|
||||
<ElForm ref="formRef" :model="formData" :rules="rules">
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="easyflow-modal-form easyflow-modal-form--compact"
|
||||
>
|
||||
<ElFormItem prop="prompt">
|
||||
<ElInput type="textarea" :rows="20" v-model="formData.prompt" />
|
||||
</ElFormItem>
|
||||
@@ -87,5 +95,5 @@ defineExpose({
|
||||
{{ loading ? $t('button.optimizing') : $t('button.regenerate') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</EasyFlowPanelModal>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user