Restore-ZQ-admin-quality-while-trimming-AI-routes

This commit is contained in:
Codex
2026-06-08 20:39:22 +08:00
parent 2e7099cb6d
commit 4bb3593fc8
10 changed files with 342 additions and 362 deletions
+30 -3
View File
@@ -17,8 +17,33 @@ from core.menu.schema import MenuCreate, MenuUpdate
menu_cache = CacheManager(prefix="menu:") menu_cache = CacheManager(prefix="menu:")
# 缓存key # 缓存key
MENU_TREE_CACHE_KEY = "tree" MENU_TREE_CACHE_KEY = "tree:ai_agent_admin"
USER_ROUTE_CACHE_PREFIX = "user_route:" USER_ROUTE_CACHE_PREFIX = "user_route:ai_agent_admin:"
AI_AGENT_ADMIN_MENU_NAMES = {
"AIPlatform",
"AIAgent",
"AIWorkflow",
"KnowledgeBase",
"Codex",
"SystemConfigManager",
"SystemManagement",
"SystemPermission",
"UserManagement",
"SystemMenu",
"SystemRole",
"Message",
"AnnouncementList",
"AnnouncementManage",
"userCenter",
"userSettings",
"LoginLog",
}
def _filter_ai_agent_admin_menus(menus: List[Menu]) -> List[Menu]:
"""Keep only the lightweight admin and AI modules for this product."""
return [menu for menu in menus if menu.name in AI_AGENT_ADMIN_MENU_NAMES]
class MenuService(BaseService[Menu, MenuCreate, MenuUpdate]): class MenuService(BaseService[Menu, MenuCreate, MenuUpdate]):
@@ -239,7 +264,7 @@ class MenuService(BaseService[Menu, MenuCreate, MenuUpdate]):
Menu.is_deleted == False # noqa: E712 Menu.is_deleted == False # noqa: E712
).order_by(Menu.order) ).order_by(Menu.order)
) )
return list(result.scalars().all()) return _filter_ai_agent_admin_menus(list(result.scalars().all()))
@classmethod @classmethod
async def build_tree( async def build_tree(
@@ -613,6 +638,8 @@ class MenuService(BaseService[Menu, MenuCreate, MenuUpdate]):
).order_by(Menu.order) ).order_by(Menu.order)
) )
menus = list(result.scalars().all()) menus = list(result.scalars().all())
menus = _filter_ai_agent_admin_menus(menus)
# 构建路由树,传递 application_code 用于添加路径前缀 # 构建路由树,传递 application_code 用于添加路径前缀
# 开发模式使用 /app-dev/{code} 前缀,正常模式使用 /app/{code} 前缀 # 开发模式使用 /app-dev/{code} 前缀,正常模式使用 /app/{code} 前缀
@@ -3,10 +3,27 @@ import type { DesignPreviewData } from './types';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { ElButton, ElDialog, ElInput, ElMessage } from 'element-plus'; import { Check, Copy, X } from '@vben/icons';
import {
ElButton,
ElDescriptions,
ElDescriptionsItem,
ElDialog,
ElDivider,
ElInput,
ElMessage,
ElScrollbar,
ElTag,
} from 'element-plus';
const props = defineProps<{ const props = defineProps<{
basicInfo?: Record<string, any>;
data?: Record<string, any>;
design?: DesignPreviewData | Record<string, any>;
preview?: DesignPreviewData; preview?: DesignPreviewData;
publishData?: Record<string, any>;
settings?: Record<string, any>;
title?: string; title?: string;
visible: boolean; visible: boolean;
}>(); }>();
@@ -19,15 +36,56 @@ const emit = defineEmits<{
const draftText = ref('{}'); const draftText = ref('{}');
const dialogTitle = computed( const sourceData = computed(() => {
() => props.preview?.title || props.title || '确认内容', return (
); props.preview ||
props.design ||
props.basicInfo ||
props.settings ||
props.publishData ||
props.data ||
{}
);
});
const payload = computed(() => {
const source = sourceData.value as Record<string, any>;
if ('data' in source && source.data && typeof source.data === 'object') {
return source.data;
}
return source;
});
const dialogTitle = computed(() => {
const source = sourceData.value as Record<string, any>;
return source.title || props.title || 'AI 方案确认';
});
const summaryItems = computed(() => {
const source = payload.value as Record<string, any>;
const keys = ['name', 'title', 'description', 'code', 'type', 'category'];
return keys
.filter((key) => source[key] !== undefined && source[key] !== null)
.map((key) => ({ key, value: String(source[key]) }));
});
const fieldItems = computed(() => {
const source = payload.value as Record<string, any>;
const fields = source.form_fields || source.fields || source.schema?.fields;
return Array.isArray(fields) ? fields.slice(0, 12) : [];
});
const tableItems = computed(() => {
const source = payload.value as Record<string, any>;
const tables = source.table_configs || source.tables || source.data_sources;
return Array.isArray(tables) ? tables.slice(0, 8) : [];
});
watch( watch(
() => [props.visible, props.preview] as const, () => [props.visible, sourceData.value] as const,
() => { () => {
if (props.visible) { if (props.visible) {
draftText.value = JSON.stringify(props.preview?.data || {}, null, 2); draftText.value = JSON.stringify(payload.value || {}, null, 2);
} }
}, },
{ immediate: true }, { immediate: true },
@@ -48,6 +106,11 @@ function handleConfirm() {
} }
} }
async function copyJson() {
await navigator.clipboard?.writeText(draftText.value);
ElMessage.success('已复制');
}
function handleVisibleChange(visible: boolean) { function handleVisibleChange(visible: boolean) {
if (!visible) handleClose(); if (!visible) handleClose();
} }
@@ -57,25 +120,160 @@ function handleVisibleChange(visible: boolean) {
<ElDialog <ElDialog
:model-value="visible" :model-value="visible"
:title="dialogTitle" :title="dialogTitle"
width="720px" width="860px"
append-to-body append-to-body
destroy-on-close destroy-on-close
class="ai-design-preview-dialog"
@update:model-value="handleVisibleChange" @update:model-value="handleVisibleChange"
> >
<div class="space-y-3"> <div class="ai-design-preview">
<div class="text-muted-foreground text-sm"> <section class="preview-panel">
{{ preview?.type || 'design_preview' }} <div class="panel-title">方案概要</div>
</div> <ElDescriptions v-if="summaryItems.length" :column="2" border size="small">
<ElInput <ElDescriptionsItem
v-model="draftText" v-for="item in summaryItems"
type="textarea" :key="item.key"
:rows="18" :label="item.key"
resize="vertical" >
/> {{ item.value }}
</ElDescriptionsItem>
</ElDescriptions>
<div v-else class="empty-hint">当前方案未提供概要字段</div>
<ElDivider v-if="fieldItems.length || tableItems.length" />
<template v-if="fieldItems.length">
<div class="panel-subtitle">字段设计</div>
<div class="tag-grid">
<ElTag
v-for="(field, index) in fieldItems"
:key="field.id || field.name || index"
effect="plain"
>
{{ field.label || field.title || field.name || `字段 ${index + 1}` }}
</ElTag>
</div>
</template>
<template v-if="tableItems.length">
<div class="panel-subtitle">数据表配置</div>
<div class="table-list">
<div
v-for="(table, index) in tableItems"
:key="table.id || table.name || index"
class="table-item"
>
<span>{{ table.title || table.name || `数据表 ${index + 1}` }}</span>
<small>{{ table.code || table.table_name || table.type || '' }}</small>
</div>
</div>
</template>
</section>
<section class="editor-panel">
<div class="panel-title-row">
<span class="panel-title">结构化数据</span>
<ElButton size="small" :icon="Copy" @click="copyJson">复制</ElButton>
</div>
<ElScrollbar height="390px">
<ElInput
v-model="draftText"
type="textarea"
:autosize="{ minRows: 18, maxRows: 24 }"
resize="none"
class="json-editor"
/>
</ElScrollbar>
</section>
</div> </div>
<template #footer> <template #footer>
<ElButton @click="handleClose">取消</ElButton> <ElButton :icon="X" @click="handleClose">取消</ElButton>
<ElButton type="primary" @click="handleConfirm">确认</ElButton> <ElButton type="primary" :icon="Check" @click="handleConfirm">
确认采用
</ElButton>
</template> </template>
</ElDialog> </ElDialog>
</template> </template>
<style scoped>
.ai-design-preview {
display: grid;
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
gap: 16px;
}
.preview-panel,
.editor-panel {
min-width: 0;
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
padding: 14px;
background: var(--el-bg-color);
}
.panel-title,
.panel-title-row {
font-size: 14px;
font-weight: 600;
margin-bottom: 12px;
}
.panel-title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.panel-subtitle {
font-size: 13px;
font-weight: 600;
margin: 12px 0 8px;
}
.empty-hint {
color: var(--el-text-color-secondary);
font-size: 13px;
padding: 24px 0;
text-align: center;
}
.tag-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.table-list {
display: grid;
gap: 8px;
}
.table-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
padding: 8px 10px;
}
.table-item small {
color: var(--el-text-color-secondary);
}
.json-editor :deep(textarea) {
font-family:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
'Courier New', monospace;
font-size: 12px;
line-height: 1.55;
}
@media (max-width: 900px) {
.ai-design-preview {
grid-template-columns: 1fr;
}
}
</style>
+1 -1
View File
@@ -28,7 +28,6 @@ const modules = import.meta.glob([
'./langs/*/announcement.json', './langs/*/announcement.json',
'./langs/*/apiToken.json', './langs/*/apiToken.json',
'./langs/*/authentication.json', './langs/*/authentication.json',
'./langs/*/chat.json',
'./langs/*/common.json', './langs/*/common.json',
'./langs/*/loginLog.json', './langs/*/loginLog.json',
'./langs/*/menu.json', './langs/*/menu.json',
@@ -40,6 +39,7 @@ const modules = import.meta.glob([
'./langs/*/system-config.json', './langs/*/system-config.json',
'./langs/*/system.json', './langs/*/system.json',
'./langs/*/ui.json', './langs/*/ui.json',
'./langs/*/ui-config.json',
'./langs/*/user-avatar.json', './langs/*/user-avatar.json',
'./langs/*/user.json', './langs/*/user.json',
]); ]);
+1 -3
View File
@@ -22,16 +22,14 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
'../views/_core/announcement/index.vue', '../views/_core/announcement/index.vue',
'../views/_core/announcement/list.vue', '../views/_core/announcement/list.vue',
'../views/_core/authentication/**/*.vue', '../views/_core/authentication/**/*.vue',
'../views/_core/chat/**/*.vue',
'../views/_core/fallback/**/*.vue', '../views/_core/fallback/**/*.vue',
'../views/_core/file-preview/index.vue',
'../views/_core/login-log/index.vue', '../views/_core/login-log/index.vue',
'../views/_core/menu/index.vue', '../views/_core/menu/index.vue',
'../views/_core/message/index.vue', '../views/_core/message/index.vue',
'../views/_core/mobile-signature/index.vue',
'../views/_core/permission/index.vue', '../views/_core/permission/index.vue',
'../views/_core/role/index.vue', '../views/_core/role/index.vue',
'../views/_core/system-config/index.vue', '../views/_core/system-config/index.vue',
'../views/_core/ui-config/index.vue',
'../views/_core/user/index.vue', '../views/_core/user/index.vue',
'../views/ai-platform/agent/editor/index.vue', '../views/ai-platform/agent/editor/index.vue',
'../views/ai-platform/agent/index.vue', '../views/ai-platform/agent/index.vue',
@@ -97,28 +97,6 @@ const coreRoutes: RouteRecordRaw[] = [
title: 'OAuth Callback', title: 'OAuth Callback',
}, },
}, },
{
name: 'MobileSignature',
path: '/mobile-signature/:token',
component: () => import('#/views/_core/mobile-signature/index.vue'),
meta: {
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
title: 'Mobile Signature',
},
},
{
name: 'FilePreview',
path: '/file-preview/:id',
component: () => import('#/views/_core/file-preview/index.vue'),
meta: {
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
title: 'File Preview',
},
},
]; ];
export { coreRoutes, fallbackNotFoundRoute }; export { coreRoutes, fallbackNotFoundRoute };
+1 -3
View File
@@ -41,16 +41,14 @@ const componentKeys: string[] = Object.keys(
'../../views/_core/announcement/index.vue', '../../views/_core/announcement/index.vue',
'../../views/_core/announcement/list.vue', '../../views/_core/announcement/list.vue',
'../../views/_core/authentication/**/*.vue', '../../views/_core/authentication/**/*.vue',
'../../views/_core/chat/**/*.vue',
'../../views/_core/fallback/**/*.vue', '../../views/_core/fallback/**/*.vue',
'../../views/_core/file-preview/index.vue',
'../../views/_core/login-log/index.vue', '../../views/_core/login-log/index.vue',
'../../views/_core/menu/index.vue', '../../views/_core/menu/index.vue',
'../../views/_core/message/index.vue', '../../views/_core/message/index.vue',
'../../views/_core/mobile-signature/index.vue',
'../../views/_core/permission/index.vue', '../../views/_core/permission/index.vue',
'../../views/_core/role/index.vue', '../../views/_core/role/index.vue',
'../../views/_core/system-config/index.vue', '../../views/_core/system-config/index.vue',
'../../views/_core/ui-config/index.vue',
'../../views/_core/user/index.vue', '../../views/_core/user/index.vue',
'../../views/ai-platform/agent/editor/index.vue', '../../views/ai-platform/agent/editor/index.vue',
'../../views/ai-platform/agent/index.vue', '../../views/ai-platform/agent/index.vue',
@@ -291,9 +291,12 @@ async function handlePreviewDoc(doc: KnowledgeDocumentListItem) {
docPreviewUrlList.value = [url]; docPreviewUrlList.value = [url];
docPreviewVisible.value = true; docPreviewVisible.value = true;
} else { } else {
const ext = getDocFileExt(doc); try {
const query = new URLSearchParams({ name: doc.name || '', ext }); const url = await getFileUrl(doc.file_id);
window.open(`/file-preview/${doc.file_id}?${query.toString()}`, '_blank'); window.open(url, '_blank');
} catch {
ElMessage.error($t('ai-platform.knowledge.chunkPreview.error'));
}
} }
} }
@@ -1,29 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { WorkflowRunListItem } from '#/api/ai-platform/ai-platform'; import type { WorkflowRunListItem } from '#/api/ai-platform/ai-platform';
import { computed, onMounted, ref } from 'vue'; import { ref } from 'vue';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { Eye, RefreshCw, Search } from '@vben/icons'; import { Eye } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { import { ElButton, ElTag } from 'element-plus';
ElButton,
ElCard,
ElForm,
ElFormItem,
ElOption,
ElPagination,
ElSelect,
ElTable,
ElTableColumn,
ElTag,
} from 'element-plus';
import { import { getAllWorkflowRunsApi } from '#/api/ai-platform/ai-platform';
getAllWorkflowRunsApi, import { useZqTable } from '#/components/zq-table';
getWorkflowListApi,
} from '#/api/ai-platform/ai-platform';
import RunStatusTag from './components/RunStatusTag.vue'; import RunStatusTag from './components/RunStatusTag.vue';
import { import {
@@ -31,245 +18,97 @@ import {
formatTime, formatTime,
getTagLabel, getTagLabel,
getTagType, getTagType,
getStatusOptions,
getTriggerOptions, getTriggerOptions,
useSearchFormSchema,
useZqTableColumns,
} from './data'; } from './data';
import DetailDialog from './modules/detail-dialog.vue'; import DetailDialog from './modules/detail-dialog.vue';
defineOptions({ name: 'WorkflowRunHistory' }); defineOptions({ name: 'WorkflowRunHistory' });
const detailRef = ref<InstanceType<typeof DetailDialog>>(); const detailRef = ref<InstanceType<typeof DetailDialog>>();
const loading = ref(false);
const rows = ref<WorkflowRunListItem[]>([]);
const total = ref(0);
const currentPage = ref(1);
const pageSize = ref(20);
const workflowOptions = ref<Array<{ label: string; value: string }>>([]);
const filters = ref({
workflowId: '',
status: '',
triggerType: '',
});
const statusOptions = getStatusOptions();
const triggerOptions = getTriggerOptions(); const triggerOptions = getTriggerOptions();
const rowIndex = computed(() => (currentPage.value - 1) * pageSize.value);
async function loadWorkflowOptions() { const fetchRunList = async (params: any) => {
const res = await getWorkflowListApi({ page: 1, pageSize: 200 }); const res = await getAllWorkflowRunsApi({
workflowOptions.value = (res.items || []).map((item) => ({ page: params.page.currentPage,
label: item.name, pageSize: params.page.pageSize,
value: item.id, workflowId: params.form?.workflowId || undefined,
})); status: params.form?.status || undefined,
} triggerType: params.form?.triggerType || undefined,
});
return {
items: res.items,
total: res.total,
};
};
async function loadRuns() { const [Grid] = useZqTable({
loading.value = true; gridOptions: {
try { columns: useZqTableColumns(),
const res = await getAllWorkflowRunsApi({ border: true,
page: currentPage.value, stripe: true,
pageSize: pageSize.value, showIndex: true,
workflowId: filters.value.workflowId || undefined, proxyConfig: {
status: filters.value.status || undefined, autoLoad: true,
triggerType: filters.value.triggerType || undefined, ajax: {
}); query: fetchRunList,
rows.value = res.items || []; },
total.value = res.total || 0; },
} finally { pagerConfig: {
loading.value = false; enabled: true,
} pageSize: 20,
} },
toolbarConfig: {
function handleSearch() { search: true,
currentPage.value = 1; refresh: true,
loadRuns(); zoom: true,
} custom: true,
},
function handleSizeChange(size: number) { },
pageSize.value = size; formOptions: {
currentPage.value = 1; schema: useSearchFormSchema(),
loadRuns(); showCollapseButton: true,
} submitOnChange: true,
},
function handlePageChange(page: number) { });
currentPage.value = page;
loadRuns();
}
function openDetail(row: WorkflowRunListItem) { function openDetail(row: WorkflowRunListItem) {
detailRef.value?.open(row.id); detailRef.value?.open(row.id);
} }
onMounted(() => {
loadWorkflowOptions();
loadRuns();
});
</script> </script>
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<DetailDialog ref="detailRef" /> <DetailDialog ref="detailRef" />
<ElCard shadow="never" class="h-full"> <Grid>
<ElForm :model="filters" inline class="mb-3"> <template #cell-status="{ row }">
<ElFormItem :label="$t('ai-platform.workflowRuns.filters.workflow')"> <RunStatusTag :status="row.status" />
<ElSelect </template>
v-model="filters.workflowId"
clearable
filterable
style="width: 220px"
:placeholder="$t('ai-platform.workflowRuns.filters.allWorkflows')"
>
<ElOption
v-for="item in workflowOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('ai-platform.workflowRuns.filters.status')">
<ElSelect
v-model="filters.status"
clearable
style="width: 150px"
:placeholder="$t('ai-platform.workflowRuns.filters.allStatus')"
>
<ElOption
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('ai-platform.workflowRuns.filters.trigger')">
<ElSelect
v-model="filters.triggerType"
clearable
style="width: 160px"
:placeholder="$t('ai-platform.workflowRuns.filters.allTriggers')"
>
<ElOption
v-for="item in triggerOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" :icon="Search" @click="handleSearch">
{{ $t('common.search') }}
</ElButton>
<ElButton :icon="RefreshCw" @click="loadRuns">
{{ $t('common.refresh') }}
</ElButton>
</ElFormItem>
</ElForm>
<ElTable <template #cell-trigger_type="{ row }">
v-loading="loading" <ElTag
:data="rows" :type="getTagType(row.trigger_type, triggerOptions)"
border size="small"
stripe
height="calc(100vh - 285px)"
>
<ElTableColumn type="index" width="60" :index="rowIndex + 1" />
<ElTableColumn
prop="workflow_name"
:label="$t('ai-platform.workflowRuns.columns.workflow')"
min-width="160"
show-overflow-tooltip
/>
<ElTableColumn
:label="$t('ai-platform.workflowRuns.columns.status')"
width="110"
align="center"
> >
<template #default="{ row }"> {{ getTagLabel(row.trigger_type, triggerOptions) }}
<RunStatusTag :status="row.status" /> </ElTag>
</template> </template>
</ElTableColumn>
<ElTableColumn
:label="$t('ai-platform.workflowRuns.columns.trigger')"
width="140"
align="center"
>
<template #default="{ row }">
<ElTag
:type="getTagType(row.trigger_type, triggerOptions)"
size="small"
>
{{ getTagLabel(row.trigger_type, triggerOptions) }}
</ElTag>
</template>
</ElTableColumn>
<ElTableColumn
prop="total_steps"
:label="$t('ai-platform.workflowRuns.columns.steps')"
width="80"
align="center"
/>
<ElTableColumn
prop="total_tokens"
:label="$t('ai-platform.workflowRuns.columns.tokens')"
width="90"
align="center"
/>
<ElTableColumn
:label="$t('ai-platform.workflowRuns.columns.duration')"
width="100"
align="center"
>
<template #default="{ row }">
{{ formatDuration(row.elapsed_time) }}
</template>
</ElTableColumn>
<ElTableColumn
:label="$t('ai-platform.workflowRuns.columns.startedAt')"
width="180"
>
<template #default="{ row }">
{{ formatTime(row.started_at) }}
</template>
</ElTableColumn>
<ElTableColumn
prop="error_message"
:label="$t('ai-platform.workflowRuns.columns.error')"
min-width="180"
show-overflow-tooltip
/>
<ElTableColumn
:label="$t('ai-platform.workflowRuns.columns.actions')"
width="120"
fixed="right"
align="center"
>
<template #default="{ row }">
<ElButton
link
type="primary"
:icon="Eye"
@click.stop="openDetail(row)"
>
{{ $t('common.view') }}
</ElButton>
</template>
</ElTableColumn>
</ElTable>
<div class="mt-4 flex justify-end"> <template #cell-elapsed_time="{ row }">
<ElPagination {{ formatDuration(row.elapsed_time) }}
v-model:current-page="currentPage" </template>
v-model:page-size="pageSize"
background <template #cell-started_at="{ row }">
layout="total, sizes, prev, pager, next, jumper" {{ formatTime(row.started_at) }}
:page-sizes="[10, 20, 50, 100]" </template>
:total="total"
@current-change="handlePageChange" <template #cell-actions="{ row }">
@size-change="handleSizeChange" <ElButton link type="primary" :icon="Eye" @click.stop="openDetail(row)">
/> {{ $t('common.view') }}
</div> </ElButton>
</ElCard> </template>
</Grid>
</Page> </Page>
</template> </template>
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
@@ -8,15 +8,9 @@ import {
ElFormItem, ElFormItem,
ElInput, ElInput,
ElInputNumber, ElInputNumber,
ElOption,
ElRadioButton,
ElRadioGroup,
ElSelect,
ElSwitch, ElSwitch,
} from 'element-plus'; } from 'element-plus';
import { getFormListApi } from '#/api/online-dev/form-manager';
import SmartInput from '../components/SmartInput.vue'; import SmartInput from '../components/SmartInput.vue';
const props = defineProps<{ const props = defineProps<{
@@ -96,30 +90,6 @@ const form = ref({
...props.data, ...props.data,
}); });
// 表单输入模式:variable(变量输入)或 select(下拉选择)
const formInputMode = ref<'select' | 'variable'>('variable');
// 表单列表
const formList = ref<any[]>([]);
const formLoading = ref(false);
// 加载表单列表
const loadFormList = async () => {
try {
formLoading.value = true;
const res = await getFormListApi({ page: 1, pageSize: 100, status: 'published' });
formList.value = res.items || [];
} catch (error) {
console.error('加载表单列表失败:', error);
} finally {
formLoading.value = false;
}
};
onMounted(() => {
loadFormList();
});
watch( watch(
form, form,
(val) => { (val) => {
@@ -166,48 +136,17 @@ const showField = (field: string) => {
</ElFormItem> </ElFormItem>
<ElFormItem :label="$t('ai-platform.workflow.panels.formData.formCode')"> <ElFormItem :label="$t('ai-platform.workflow.panels.formData.formCode')">
<div class="flex items-center gap-2 mb-2"> <SmartInput
<ElRadioGroup v-model="formInputMode" size="small"> v-model="form.form_code"
<ElRadioButton value="variable">{{ $t('ai-platform.workflow.panels.formData.variableInput') }}</ElRadioButton> :current-node-id="nodeId"
<ElRadioButton value="select">{{ $t('ai-platform.workflow.panels.formData.selectFromList') }}</ElRadioButton> :placeholder="
</ElRadioGroup> $t('ai-platform.workflow.panels.formData.formCodeInputPlaceholder') +
' {{form_code}}'
"
/>
<div class="text-muted-foreground mt-1 text-xs">
{{ $t('ai-platform.workflow.panels.formData.formCodeInputHint') }}
</div> </div>
<!-- 变量输入模式 -->
<template v-if="formInputMode === 'variable'">
<SmartInput
v-model="form.form_code"
:current-node-id="nodeId"
:placeholder="
$t('ai-platform.workflow.panels.formData.formCodeInputPlaceholder') +
' {{form_code}}'
"
/>
<div class="text-muted-foreground mt-1 text-xs">
{{ $t('ai-platform.workflow.panels.formData.formCodeInputHint') }}
</div>
</template>
<!-- 下拉选择模式 -->
<template v-else>
<ElSelect
v-model="form.form_code"
:placeholder="$t('ai-platform.workflow.panels.formData.selectFormPlaceholder')"
filterable
:loading="formLoading"
class="w-full"
>
<ElOption
v-for="f in formList"
:key="f.code"
:label="`${f.name} (${f.code})`"
:value="f.code"
/>
</ElSelect>
<div class="text-muted-foreground mt-1 text-xs">
{{ $t('ai-platform.workflow.panels.formData.selectedFormHint', { code: form.form_code || '-' }) }}
</div>
</template>
</ElFormItem> </ElFormItem>
<!-- 记录ID读取/更新/删除 --> <!-- 记录ID读取/更新/删除 -->
+1 -1
View File
@@ -17,7 +17,7 @@ const defaultPreferences: Preferences = {
contentPaddingTop: 0, contentPaddingTop: 0,
defaultAvatar: defaultAvatar:
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp', 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
defaultHomePath: '/page-render/main_home', defaultHomePath: '/ai-platform/agent',
dynamicTitle: true, dynamicTitle: true,
enableCheckUpdates: true, enableCheckUpdates: true,
enablePreferences: true, enablePreferences: true,