发布 v1.10 #5

Merged
czm merged 147 commits from develop into main 2026-08-20 11:36:27 +08:00
8 changed files with 197 additions and 15 deletions
Showing only changes of commit 1404c2ddc5 - Show all commits

View File

@@ -0,0 +1,75 @@
<script setup lang="ts">
import { computed, watch } from 'vue';
import { AuthenticationLoginExpiredModal } from '@easyflow/common-ui';
import { useWatermark } from '@easyflow/hooks';
import { LockScreen } from '@easyflow/layouts';
import { preferences } from '@easyflow/preferences';
import { useAccessStore, useUserStore } from '@easyflow/stores';
import { useAuthStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';
const accessStore = useAccessStore();
const userStore = useUserStore();
const authStore = useAuthStore();
const { destroyWatermark, updateWatermark } = useWatermark();
const avatar = computed(
() => userStore.userInfo?.avatar ?? preferences.app.defaultAvatar,
);
async function handleLogout() {
await authStore.logout(false);
}
watch(
() => ({
enable: preferences.app.watermark,
content: preferences.app.watermarkContent,
}),
async ({ enable, content }) => {
if (enable) {
await updateWatermark({
content:
content ||
`${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
});
} else {
destroyWatermark();
}
},
{
immediate: true,
},
);
</script>
<template>
<div class="authenticated-standalone-layout">
<slot></slot>
<AuthenticationLoginExpiredModal
v-model:open="accessStore.loginExpired"
:avatar
>
<LoginForm />
</AuthenticationLoginExpiredModal>
<Transition v-if="preferences.widget.lockScreen" name="slide-up">
<LockScreen
v-if="accessStore.isLockScreen"
:avatar
@to-login="handleLogout"
/>
</Transition>
</div>
</template>
<style scoped>
.authenticated-standalone-layout {
width: 100%;
height: 100vh;
min-height: 0;
overflow: hidden;
background-color: var(--el-bg-color);
}
</style>

View File

@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest';
import agentDesignRoutes from '../routes/external/agent-design';
import agentRoutes from '../routes/modules/agent';
describe('agent design route', () => {
it('keeps the standalone designer outside dynamic menu routes', () => {
const designRoute = agentDesignRoutes.find(
(route) => route.name === 'AgentDesigner',
);
expect(agentRoutes.some((route) => route.name === 'AgentDesigner')).toBe(
false,
);
expect(designRoute).toBeDefined();
expect(designRoute?.path).toBe('/ai/agents/designer/:id');
expect(designRoute?.meta).toMatchObject({
activePath: '/ai/agents',
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
noBasicLayout: true,
openInNewWindow: true,
});
expect(designRoute?.meta?.ignoreAccess).not.toBe(true);
expect(designRoute?.component).toBeTypeOf('function');
});
});

View File

@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest';
import workflowRoutes from '../routes/modules/workflow';
describe('workflow design route', () => {
it('uses the authenticated standalone shell without exposing a menu or tab', () => {
const designRoute = workflowRoutes.find(
(route) => route.name === 'WorkflowDesign',
);
expect(designRoute).toBeDefined();
expect(designRoute?.path).toBe('/ai/workflow/design');
expect(designRoute?.meta).toMatchObject({
activePath: '/ai/workflow',
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
noBasicLayout: true,
});
expect(designRoute?.meta?.ignoreAccess).not.toBe(true);
expect(designRoute?.component).toBeTypeOf('function');
});
});

View File

@@ -0,0 +1,23 @@
import type { RouteRecordRaw } from 'vue-router';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
name: 'AgentDesigner',
path: '/ai/agents/designer/:id',
component: () =>
import('#/views/ai/agents/AgentDesignerStandaloneView.vue'),
meta: {
title: $t('menus.ai.agents'),
openInNewWindow: true,
hideInMenu: true,
hideInTab: true,
hideInBreadcrumb: true,
noBasicLayout: true,
activePath: '/ai/agents',
},
},
];
export default routes;

View File

@@ -1,19 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import {$t} from '#/locales';
const routes: RouteRecordRaw[] = [
{
name: 'AgentDesigner',
path: '/ai/agents/designer/:id',
component: () => import('#/views/ai/agents/AgentDesigner.vue'),
meta: {
title: $t('menus.ai.agents'),
openInNewWindow: true,
hideInMenu: true,
activePath: '/ai/agents',
},
},
{
name: 'AgentChat',
path: '/ai/agent-chat',

View File

@@ -8,11 +8,15 @@ const routes: RouteRecordRaw[] = [
icon: 'ant-design:apartment-outlined',
title: '工作流设计',
hideInMenu: true,
hideInTab: true,
hideInBreadcrumb: true,
noBasicLayout: true,
activePath: '/ai/workflow',
},
name: 'WorkflowDesign',
path: '/ai/workflow/design',
component: () => import('#/views/ai/workflow/WorkflowDesign.vue'),
component: () =>
import('#/views/ai/workflow/WorkflowDesignStandaloneView.vue'),
},
{
meta: {

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import AuthenticatedStandaloneLayout from '#/layouts/AuthenticatedStandaloneLayout.vue';
import AgentDesigner from './AgentDesigner.vue';
</script>
<template>
<AuthenticatedStandaloneLayout>
<AgentDesigner />
</AuthenticatedStandaloneLayout>
</template>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
import AuthenticatedStandaloneLayout from '#/layouts/AuthenticatedStandaloneLayout.vue';
import WorkflowDesign from './WorkflowDesign.vue';
</script>
<template>
<AuthenticatedStandaloneLayout class="workflow-design-standalone">
<WorkflowDesign />
</AuthenticatedStandaloneLayout>
</template>
<style scoped>
.workflow-design-standalone :deep(.head-div) {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
overflow: hidden;
}
.workflow-design-standalone :deep(.agentsflow) {
height: 100% !important;
}
.workflow-design-standalone :deep(.tiny-flow-container) {
flex: 1;
height: auto;
min-height: 0;
}
</style>