Restore-ZQ-admin-quality-while-trimming-AI-routes
This commit is contained in:
@@ -17,8 +17,33 @@ from core.menu.schema import MenuCreate, MenuUpdate
|
||||
menu_cache = CacheManager(prefix="menu:")
|
||||
|
||||
# 缓存key
|
||||
MENU_TREE_CACHE_KEY = "tree"
|
||||
USER_ROUTE_CACHE_PREFIX = "user_route:"
|
||||
MENU_TREE_CACHE_KEY = "tree:ai_agent_admin"
|
||||
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]):
|
||||
@@ -239,7 +264,7 @@ class MenuService(BaseService[Menu, MenuCreate, MenuUpdate]):
|
||||
Menu.is_deleted == False # noqa: E712
|
||||
).order_by(Menu.order)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
return _filter_ai_agent_admin_menus(list(result.scalars().all()))
|
||||
|
||||
@classmethod
|
||||
async def build_tree(
|
||||
@@ -613,6 +638,8 @@ class MenuService(BaseService[Menu, MenuCreate, MenuUpdate]):
|
||||
).order_by(Menu.order)
|
||||
)
|
||||
menus = list(result.scalars().all())
|
||||
|
||||
menus = _filter_ai_agent_admin_menus(menus)
|
||||
|
||||
# 构建路由树,传递 application_code 用于添加路径前缀
|
||||
# 开发模式使用 /app-dev/{code} 前缀,正常模式使用 /app/{code} 前缀
|
||||
|
||||
@@ -3,10 +3,27 @@ import type { DesignPreviewData } from './types';
|
||||
|
||||
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<{
|
||||
basicInfo?: Record<string, any>;
|
||||
data?: Record<string, any>;
|
||||
design?: DesignPreviewData | Record<string, any>;
|
||||
preview?: DesignPreviewData;
|
||||
publishData?: Record<string, any>;
|
||||
settings?: Record<string, any>;
|
||||
title?: string;
|
||||
visible: boolean;
|
||||
}>();
|
||||
@@ -19,15 +36,56 @@ const emit = defineEmits<{
|
||||
|
||||
const draftText = ref('{}');
|
||||
|
||||
const dialogTitle = computed(
|
||||
() => props.preview?.title || props.title || '确认内容',
|
||||
);
|
||||
const sourceData = computed(() => {
|
||||
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(
|
||||
() => [props.visible, props.preview] as const,
|
||||
() => [props.visible, sourceData.value] as const,
|
||||
() => {
|
||||
if (props.visible) {
|
||||
draftText.value = JSON.stringify(props.preview?.data || {}, null, 2);
|
||||
draftText.value = JSON.stringify(payload.value || {}, null, 2);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -48,6 +106,11 @@ function handleConfirm() {
|
||||
}
|
||||
}
|
||||
|
||||
async function copyJson() {
|
||||
await navigator.clipboard?.writeText(draftText.value);
|
||||
ElMessage.success('已复制');
|
||||
}
|
||||
|
||||
function handleVisibleChange(visible: boolean) {
|
||||
if (!visible) handleClose();
|
||||
}
|
||||
@@ -57,25 +120,160 @@ function handleVisibleChange(visible: boolean) {
|
||||
<ElDialog
|
||||
:model-value="visible"
|
||||
:title="dialogTitle"
|
||||
width="720px"
|
||||
width="860px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="ai-design-preview-dialog"
|
||||
@update:model-value="handleVisibleChange"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="text-muted-foreground text-sm">
|
||||
{{ preview?.type || 'design_preview' }}
|
||||
</div>
|
||||
<ElInput
|
||||
v-model="draftText"
|
||||
type="textarea"
|
||||
:rows="18"
|
||||
resize="vertical"
|
||||
/>
|
||||
<div class="ai-design-preview">
|
||||
<section class="preview-panel">
|
||||
<div class="panel-title">方案概要</div>
|
||||
<ElDescriptions v-if="summaryItems.length" :column="2" border size="small">
|
||||
<ElDescriptionsItem
|
||||
v-for="item in summaryItems"
|
||||
:key="item.key"
|
||||
:label="item.key"
|
||||
>
|
||||
{{ 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>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="handleClose">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleConfirm">确认</ElButton>
|
||||
<ElButton :icon="X" @click="handleClose">取消</ElButton>
|
||||
<ElButton type="primary" :icon="Check" @click="handleConfirm">
|
||||
确认采用
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</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>
|
||||
|
||||
@@ -28,7 +28,6 @@ const modules = import.meta.glob([
|
||||
'./langs/*/announcement.json',
|
||||
'./langs/*/apiToken.json',
|
||||
'./langs/*/authentication.json',
|
||||
'./langs/*/chat.json',
|
||||
'./langs/*/common.json',
|
||||
'./langs/*/loginLog.json',
|
||||
'./langs/*/menu.json',
|
||||
@@ -40,6 +39,7 @@ const modules = import.meta.glob([
|
||||
'./langs/*/system-config.json',
|
||||
'./langs/*/system.json',
|
||||
'./langs/*/ui.json',
|
||||
'./langs/*/ui-config.json',
|
||||
'./langs/*/user-avatar.json',
|
||||
'./langs/*/user.json',
|
||||
]);
|
||||
|
||||
@@ -22,16 +22,14 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
'../views/_core/announcement/index.vue',
|
||||
'../views/_core/announcement/list.vue',
|
||||
'../views/_core/authentication/**/*.vue',
|
||||
'../views/_core/chat/**/*.vue',
|
||||
'../views/_core/fallback/**/*.vue',
|
||||
'../views/_core/file-preview/index.vue',
|
||||
'../views/_core/login-log/index.vue',
|
||||
'../views/_core/menu/index.vue',
|
||||
'../views/_core/message/index.vue',
|
||||
'../views/_core/mobile-signature/index.vue',
|
||||
'../views/_core/permission/index.vue',
|
||||
'../views/_core/role/index.vue',
|
||||
'../views/_core/system-config/index.vue',
|
||||
'../views/_core/ui-config/index.vue',
|
||||
'../views/_core/user/index.vue',
|
||||
'../views/ai-platform/agent/editor/index.vue',
|
||||
'../views/ai-platform/agent/index.vue',
|
||||
|
||||
@@ -97,28 +97,6 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||
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 };
|
||||
|
||||
@@ -41,16 +41,14 @@ const componentKeys: string[] = Object.keys(
|
||||
'../../views/_core/announcement/index.vue',
|
||||
'../../views/_core/announcement/list.vue',
|
||||
'../../views/_core/authentication/**/*.vue',
|
||||
'../../views/_core/chat/**/*.vue',
|
||||
'../../views/_core/fallback/**/*.vue',
|
||||
'../../views/_core/file-preview/index.vue',
|
||||
'../../views/_core/login-log/index.vue',
|
||||
'../../views/_core/menu/index.vue',
|
||||
'../../views/_core/message/index.vue',
|
||||
'../../views/_core/mobile-signature/index.vue',
|
||||
'../../views/_core/permission/index.vue',
|
||||
'../../views/_core/role/index.vue',
|
||||
'../../views/_core/system-config/index.vue',
|
||||
'../../views/_core/ui-config/index.vue',
|
||||
'../../views/_core/user/index.vue',
|
||||
'../../views/ai-platform/agent/editor/index.vue',
|
||||
'../../views/ai-platform/agent/index.vue',
|
||||
|
||||
@@ -291,9 +291,12 @@ async function handlePreviewDoc(doc: KnowledgeDocumentListItem) {
|
||||
docPreviewUrlList.value = [url];
|
||||
docPreviewVisible.value = true;
|
||||
} else {
|
||||
const ext = getDocFileExt(doc);
|
||||
const query = new URLSearchParams({ name: doc.name || '', ext });
|
||||
window.open(`/file-preview/${doc.file_id}?${query.toString()}`, '_blank');
|
||||
try {
|
||||
const url = await getFileUrl(doc.file_id);
|
||||
window.open(url, '_blank');
|
||||
} catch {
|
||||
ElMessage.error($t('ai-platform.knowledge.chunkPreview.error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
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 { Eye, RefreshCw, Search } from '@vben/icons';
|
||||
import { Eye } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElOption,
|
||||
ElPagination,
|
||||
ElSelect,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElTag,
|
||||
} from 'element-plus';
|
||||
import { ElButton, ElTag } from 'element-plus';
|
||||
|
||||
import {
|
||||
getAllWorkflowRunsApi,
|
||||
getWorkflowListApi,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
import { getAllWorkflowRunsApi } from '#/api/ai-platform/ai-platform';
|
||||
import { useZqTable } from '#/components/zq-table';
|
||||
|
||||
import RunStatusTag from './components/RunStatusTag.vue';
|
||||
import {
|
||||
@@ -31,245 +18,97 @@ import {
|
||||
formatTime,
|
||||
getTagLabel,
|
||||
getTagType,
|
||||
getStatusOptions,
|
||||
getTriggerOptions,
|
||||
useSearchFormSchema,
|
||||
useZqTableColumns,
|
||||
} from './data';
|
||||
import DetailDialog from './modules/detail-dialog.vue';
|
||||
|
||||
defineOptions({ name: 'WorkflowRunHistory' });
|
||||
|
||||
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 rowIndex = computed(() => (currentPage.value - 1) * pageSize.value);
|
||||
|
||||
async function loadWorkflowOptions() {
|
||||
const res = await getWorkflowListApi({ page: 1, pageSize: 200 });
|
||||
workflowOptions.value = (res.items || []).map((item) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
}
|
||||
const fetchRunList = async (params: any) => {
|
||||
const res = await getAllWorkflowRunsApi({
|
||||
page: params.page.currentPage,
|
||||
pageSize: params.page.pageSize,
|
||||
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() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getAllWorkflowRunsApi({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
workflowId: filters.value.workflowId || undefined,
|
||||
status: filters.value.status || undefined,
|
||||
triggerType: filters.value.triggerType || undefined,
|
||||
});
|
||||
rows.value = res.items || [];
|
||||
total.value = res.total || 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
currentPage.value = 1;
|
||||
loadRuns();
|
||||
}
|
||||
|
||||
function handleSizeChange(size: number) {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1;
|
||||
loadRuns();
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
currentPage.value = page;
|
||||
loadRuns();
|
||||
}
|
||||
const [Grid] = useZqTable({
|
||||
gridOptions: {
|
||||
columns: useZqTableColumns(),
|
||||
border: true,
|
||||
stripe: true,
|
||||
showIndex: true,
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
ajax: {
|
||||
query: fetchRunList,
|
||||
},
|
||||
},
|
||||
pagerConfig: {
|
||||
enabled: true,
|
||||
pageSize: 20,
|
||||
},
|
||||
toolbarConfig: {
|
||||
search: true,
|
||||
refresh: true,
|
||||
zoom: true,
|
||||
custom: true,
|
||||
},
|
||||
},
|
||||
formOptions: {
|
||||
schema: useSearchFormSchema(),
|
||||
showCollapseButton: true,
|
||||
submitOnChange: true,
|
||||
},
|
||||
});
|
||||
|
||||
function openDetail(row: WorkflowRunListItem) {
|
||||
detailRef.value?.open(row.id);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadWorkflowOptions();
|
||||
loadRuns();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<DetailDialog ref="detailRef" />
|
||||
|
||||
<ElCard shadow="never" class="h-full">
|
||||
<ElForm :model="filters" inline class="mb-3">
|
||||
<ElFormItem :label="$t('ai-platform.workflowRuns.filters.workflow')">
|
||||
<ElSelect
|
||||
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>
|
||||
<Grid>
|
||||
<template #cell-status="{ row }">
|
||||
<RunStatusTag :status="row.status" />
|
||||
</template>
|
||||
|
||||
<ElTable
|
||||
v-loading="loading"
|
||||
:data="rows"
|
||||
border
|
||||
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 #cell-trigger_type="{ row }">
|
||||
<ElTag
|
||||
:type="getTagType(row.trigger_type, triggerOptions)"
|
||||
size="small"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<RunStatusTag :status="row.status" />
|
||||
</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>
|
||||
{{ getTagLabel(row.trigger_type, triggerOptions) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<ElPagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
<template #cell-elapsed_time="{ row }">
|
||||
{{ formatDuration(row.elapsed_time) }}
|
||||
</template>
|
||||
|
||||
<template #cell-started_at="{ row }">
|
||||
{{ formatTime(row.started_at) }}
|
||||
</template>
|
||||
|
||||
<template #cell-actions="{ row }">
|
||||
<ElButton link type="primary" :icon="Eye" @click.stop="openDetail(row)">
|
||||
{{ $t('common.view') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
@@ -8,15 +8,9 @@ import {
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
ElOption,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
ElSwitch,
|
||||
} from 'element-plus';
|
||||
|
||||
import { getFormListApi } from '#/api/online-dev/form-manager';
|
||||
|
||||
import SmartInput from '../components/SmartInput.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -96,30 +90,6 @@ const form = ref({
|
||||
...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(
|
||||
form,
|
||||
(val) => {
|
||||
@@ -166,48 +136,17 @@ const showField = (field: string) => {
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem :label="$t('ai-platform.workflow.panels.formData.formCode')">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<ElRadioGroup v-model="formInputMode" size="small">
|
||||
<ElRadioButton value="variable">{{ $t('ai-platform.workflow.panels.formData.variableInput') }}</ElRadioButton>
|
||||
<ElRadioButton value="select">{{ $t('ai-platform.workflow.panels.formData.selectFromList') }}</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
<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 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>
|
||||
|
||||
<!-- 记录ID(读取/更新/删除) -->
|
||||
|
||||
@@ -17,7 +17,7 @@ const defaultPreferences: Preferences = {
|
||||
contentPaddingTop: 0,
|
||||
defaultAvatar:
|
||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||
defaultHomePath: '/page-render/main_home',
|
||||
defaultHomePath: '/ai-platform/agent',
|
||||
dynamicTitle: true,
|
||||
enableCheckUpdates: true,
|
||||
enablePreferences: true,
|
||||
|
||||
Reference in New Issue
Block a user