perf: 卡片入口视觉效果重做
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
|||||||
ElAvatar,
|
ElAvatar,
|
||||||
ElButton,
|
ElButton,
|
||||||
ElCard,
|
ElCard,
|
||||||
ElDivider,
|
|
||||||
ElDropdown,
|
ElDropdown,
|
||||||
ElDropdownItem,
|
ElDropdownItem,
|
||||||
ElDropdownMenu,
|
ElDropdownMenu,
|
||||||
@@ -17,14 +16,31 @@ import {
|
|||||||
ElText,
|
ElText,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
|
export type ActionPlacement = 'inline' | 'menu';
|
||||||
|
export type ActionTone = 'danger' | 'default';
|
||||||
|
|
||||||
export interface ActionButton {
|
export interface ActionButton {
|
||||||
icon: any;
|
icon?: any;
|
||||||
text: string;
|
text: string;
|
||||||
className: string;
|
className?: string;
|
||||||
permission: string;
|
permission?: string;
|
||||||
|
placement?: ActionPlacement;
|
||||||
|
tone?: ActionTone;
|
||||||
onClick: (row: any) => void;
|
onClick: (row: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CardPrimaryAction {
|
||||||
|
icon?: any;
|
||||||
|
text: string;
|
||||||
|
permission?: string;
|
||||||
|
onClick: (row: any) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResolvedActionButton extends ActionButton {
|
||||||
|
placement: ActionPlacement;
|
||||||
|
tone: ActionTone;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CardListProps {
|
export interface CardListProps {
|
||||||
iconField?: string;
|
iconField?: string;
|
||||||
titleField?: string;
|
titleField?: string;
|
||||||
@@ -32,53 +48,97 @@ export interface CardListProps {
|
|||||||
actions?: ActionButton[];
|
actions?: ActionButton[];
|
||||||
defaultIcon: any;
|
defaultIcon: any;
|
||||||
data: any[];
|
data: any[];
|
||||||
|
primaryAction?: CardPrimaryAction;
|
||||||
tagField?: string;
|
tagField?: string;
|
||||||
tagMap?: Record<string, string>;
|
tagMap?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<CardListProps>(), {
|
const props = withDefaults(defineProps<CardListProps>(), {
|
||||||
iconField: 'icon',
|
iconField: 'icon',
|
||||||
titleField: 'title',
|
titleField: 'title',
|
||||||
descField: 'description',
|
descField: 'description',
|
||||||
actions: () => [],
|
actions: () => [],
|
||||||
|
primaryAction: undefined,
|
||||||
tagField: '',
|
tagField: '',
|
||||||
tagMap: () => ({}),
|
tagMap: () => ({}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
const { hasAccessByCodes } = useAccess();
|
||||||
const filterActions = computed(() => {
|
|
||||||
return props.actions.filter((action) => {
|
function hasPermission(permission?: string) {
|
||||||
return hasAccessByCodes([action.permission]);
|
return !permission || hasAccessByCodes([permission]);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const resolvedPrimaryAction = computed(() => {
|
||||||
|
if (!props.primaryAction || !hasPermission(props.primaryAction.permission)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return props.primaryAction;
|
||||||
});
|
});
|
||||||
const visibleActions = computed(() => {
|
|
||||||
return filterActions.value.length <= 3
|
const resolvedActions = computed<ResolvedActionButton[]>(() => {
|
||||||
? filterActions.value
|
return props.actions
|
||||||
: filterActions.value.slice(0, 3);
|
.filter((action) => hasPermission(action.permission))
|
||||||
|
.map((action, index) => ({
|
||||||
|
...action,
|
||||||
|
placement:
|
||||||
|
action.placement ||
|
||||||
|
(resolvedPrimaryAction.value ? 'menu' : index < 3 ? 'inline' : 'menu'),
|
||||||
|
tone:
|
||||||
|
action.tone ||
|
||||||
|
(action.className?.includes('danger') ? 'danger' : 'default'),
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
const hiddenActions = computed(() => {
|
|
||||||
return filterActions.value.length > 3 ? filterActions.value.slice(3) : [];
|
const inlineActions = computed(() => {
|
||||||
|
return resolvedActions.value.filter((action) => action.placement === 'inline');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const menuActions = computed(() => {
|
||||||
|
return resolvedActions.value.filter((action) => action.placement === 'menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
const showFooter = computed(() => {
|
||||||
|
return Boolean(
|
||||||
|
resolvedPrimaryAction.value ||
|
||||||
|
inlineActions.value.length ||
|
||||||
|
menuActions.value.length,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handlePrimaryAction(item: any) {
|
||||||
|
resolvedPrimaryAction.value?.onClick(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleActionClick(event: Event, action: ActionButton, item: any) {
|
||||||
|
event.stopPropagation();
|
||||||
|
action.onClick(item);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
<ElCard
|
<ElCard
|
||||||
v-for="(item, index) in props.data"
|
v-for="(item, index) in props.data"
|
||||||
:key="index"
|
:key="item.id ?? index"
|
||||||
shadow="hover"
|
shadow="never"
|
||||||
footer-class="foot-c"
|
:class="['card-item', { 'card-item--interactive': resolvedPrimaryAction }]"
|
||||||
:style="{
|
:role="resolvedPrimaryAction ? 'button' : undefined"
|
||||||
'--el-box-shadow-light': '0px 2px 12px 0px rgb(100 121 153 10%)',
|
:tabindex="resolvedPrimaryAction ? 0 : undefined"
|
||||||
}"
|
@click="handlePrimaryAction(item)"
|
||||||
|
@keydown.enter.prevent="handlePrimaryAction(item)"
|
||||||
|
@keydown.space.prevent="handlePrimaryAction(item)"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="card-content">
|
||||||
<div class="flex items-center gap-3">
|
<div class="card-header">
|
||||||
<ElAvatar
|
<ElAvatar
|
||||||
class="shrink-0"
|
class="card-avatar shrink-0"
|
||||||
:src="item[iconField] || defaultIcon"
|
:src="item[iconField] || defaultIcon"
|
||||||
:size="36"
|
:size="44"
|
||||||
/>
|
/>
|
||||||
|
<div class="card-meta">
|
||||||
<div class="title-row">
|
<div class="title-row">
|
||||||
<ElText truncated size="large" class="font-medium">
|
<ElText truncated size="large" class="card-title">
|
||||||
{{ item[titleField] }}
|
{{ item[titleField] }}
|
||||||
</ElText>
|
</ElText>
|
||||||
<ElTag
|
<ElTag
|
||||||
@@ -90,136 +150,268 @@ const hiddenActions = computed(() => {
|
|||||||
{{ tagMap[item[tagField]] || item[tagField] }}
|
{{ tagMap[item[tagField]] || item[tagField] }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<ElText line-clamp="2" class="item-desc w-full">
|
<ElText line-clamp="2" class="item-desc w-full">
|
||||||
{{ item[descField] }}
|
{{ item[descField] }}
|
||||||
</ElText>
|
</ElText>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
</div>
|
||||||
<div :class="visibleActions.length > 2 ? 'footer-div' : ''">
|
</div>
|
||||||
<template v-for="(action, idx) in visibleActions" :key="idx">
|
|
||||||
|
<template v-if="showFooter" #footer>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div v-if="resolvedPrimaryAction" class="card-primary-hint">
|
||||||
|
<div class="primary-label">
|
||||||
|
<ElIcon v-if="resolvedPrimaryAction.icon" class="primary-icon">
|
||||||
|
<IconifyIcon
|
||||||
|
v-if="typeof resolvedPrimaryAction.icon === 'string'"
|
||||||
|
:icon="resolvedPrimaryAction.icon"
|
||||||
|
/>
|
||||||
|
<component v-else :is="resolvedPrimaryAction.icon" />
|
||||||
|
</ElIcon>
|
||||||
|
<span class="primary-text">{{ resolvedPrimaryAction.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="inlineActions.length || menuActions.length"
|
||||||
|
class="card-actions"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
<ElButton
|
<ElButton
|
||||||
|
v-for="action in inlineActions"
|
||||||
|
:key="action.text"
|
||||||
:icon="typeof action.icon === 'string' ? undefined : action.icon"
|
:icon="typeof action.icon === 'string' ? undefined : action.icon"
|
||||||
size="small"
|
size="small"
|
||||||
:style="{
|
class="card-action-btn"
|
||||||
'--el-button-text-color': 'hsl(220deg 9.68% 63.53%)',
|
:class="{ 'card-action-btn--danger': action.tone === 'danger' }"
|
||||||
'--el-button-font-weight': 400,
|
|
||||||
}"
|
|
||||||
link
|
link
|
||||||
@click="action.onClick(item)"
|
@click.stop="handleActionClick($event, action, item)"
|
||||||
>
|
>
|
||||||
<template v-if="typeof action.icon === 'string'" #icon>
|
<template v-if="typeof action.icon === 'string'" #icon>
|
||||||
<IconifyIcon :icon="action.icon" />
|
<IconifyIcon :icon="action.icon" />
|
||||||
</template>
|
</template>
|
||||||
{{ action.text }}
|
{{ action.text }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElDivider
|
|
||||||
v-if="
|
|
||||||
filterActions.length <= 3
|
|
||||||
? idx < filterActions.length - 1
|
|
||||||
: true
|
|
||||||
"
|
|
||||||
direction="vertical"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ElDropdown v-if="hiddenActions.length > 0" trigger="click">
|
<ElDropdown
|
||||||
|
v-if="menuActions.length > 0"
|
||||||
|
trigger="click"
|
||||||
|
placement="bottom-end"
|
||||||
|
>
|
||||||
<ElButton
|
<ElButton
|
||||||
:style="{
|
class="card-action-btn card-action-btn--menu"
|
||||||
'--el-button-text-color': 'hsl(220deg 9.68% 63.53%)',
|
|
||||||
'--el-button-font-weight': 400,
|
|
||||||
}"
|
|
||||||
:icon="MoreFilled"
|
:icon="MoreFilled"
|
||||||
link
|
link
|
||||||
|
@click.stop
|
||||||
/>
|
/>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<ElDropdownMenu>
|
||||||
<ElDropdownItem
|
<ElDropdownItem
|
||||||
v-for="(action, idx) in hiddenActions"
|
v-for="action in menuActions"
|
||||||
:key="idx"
|
:key="action.text"
|
||||||
|
:class="{ 'card-menu-item--danger': action.tone === 'danger' }"
|
||||||
@click="action.onClick(item)"
|
@click="action.onClick(item)"
|
||||||
>
|
>
|
||||||
<template #default>
|
<div class="menu-action-content">
|
||||||
<div :class="`${action.className} handle-div`">
|
|
||||||
<ElIcon v-if="action.icon">
|
<ElIcon v-if="action.icon">
|
||||||
<component :is="action.icon" />
|
<IconifyIcon
|
||||||
|
v-if="typeof action.icon === 'string'"
|
||||||
|
:icon="action.icon"
|
||||||
|
/>
|
||||||
|
<component v-else :is="action.icon" />
|
||||||
</ElIcon>
|
</ElIcon>
|
||||||
{{ action.text }}
|
<span>{{ action.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
</ElDropdownMenu>
|
</ElDropdownMenu>
|
||||||
</template>
|
</template>
|
||||||
</ElDropdown>
|
</ElDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 响应式调整 */
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.card-grid {
|
.card-grid {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.card-grid {
|
.card-grid {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.card-grid {
|
.card-grid {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
grid-template-columns: minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-card__footer) {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-div {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 8px 20px;
|
|
||||||
background-color: hsl(var(--background-deep));
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.handle-div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid {
|
.card-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
min-width: max(100%, 600px); /* 确保至少显示2个卡片 */
|
min-width: max(100%, 600px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-desc {
|
.card-item {
|
||||||
height: 40px;
|
background: hsl(var(--card));
|
||||||
font-size: clamp(8px, 1vw, 14px);
|
border: 1px solid hsl(var(--border));
|
||||||
line-height: 20px;
|
border-radius: 18px;
|
||||||
color: #75808d;
|
transition:
|
||||||
|
transform 0.18s ease,
|
||||||
|
box-shadow 0.18s ease,
|
||||||
|
border-color 0.18s ease,
|
||||||
|
background-color 0.18s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-danger {
|
.card-item--interactive {
|
||||||
color: var(--el-color-danger);
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-item--interactive:hover {
|
||||||
|
border-color: hsl(var(--primary) / 20%);
|
||||||
|
box-shadow: var(--shadow-subtle);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-item--interactive:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: hsl(var(--primary) / 32%);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 4px hsl(var(--primary) / 12%),
|
||||||
|
var(--shadow-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__footer) {
|
||||||
|
padding: 0 18px 18px;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
display: flex;
|
||||||
|
min-height: 116px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-avatar {
|
||||||
|
background: hsl(var(--surface-subtle));
|
||||||
|
border: 1px solid hsl(var(--line-subtle));
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-row {
|
.title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsl(var(--text-strong));
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-desc {
|
||||||
|
min-height: 44px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 22px;
|
||||||
|
color: hsl(var(--text-muted));
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid hsl(var(--divider-faint) / 85%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-primary-hint {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-label {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-icon {
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: hsl(var(--text-strong));
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-action-btn {
|
||||||
|
--el-button-font-weight: 500;
|
||||||
|
--el-button-text-color: hsl(var(--text-muted));
|
||||||
|
--el-button-hover-text-color: hsl(var(--text-strong));
|
||||||
|
--el-button-active-text-color: hsl(var(--text-strong));
|
||||||
|
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-action-btn--danger {
|
||||||
|
--el-button-text-color: hsl(var(--destructive));
|
||||||
|
--el-button-hover-text-color: hsl(var(--destructive));
|
||||||
|
--el-button-active-text-color: hsl(var(--destructive));
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-action-btn--menu {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-action-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.card-menu-item--danger) {
|
||||||
|
color: hsl(var(--destructive));
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import type { FormInstance } from 'element-plus';
|
|||||||
|
|
||||||
import type { BotInfo } from '@easyflow/types';
|
import type { BotInfo } from '@easyflow/types';
|
||||||
|
|
||||||
import type { ActionButton } from '#/components/page/CardList.vue';
|
import type {
|
||||||
|
ActionButton,
|
||||||
|
CardPrimaryAction,
|
||||||
|
} from '#/components/page/CardList.vue';
|
||||||
|
|
||||||
import { computed, markRaw, onMounted, ref } from 'vue';
|
import { computed, markRaw, onMounted, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@@ -70,36 +73,35 @@ const headerButtons = [
|
|||||||
function resolveNavTitle(row: BotInfo) {
|
function resolveNavTitle(row: BotInfo) {
|
||||||
return (row as Record<string, any>)?.title || row?.name || '';
|
return (row as Record<string, any>)?.title || row?.name || '';
|
||||||
}
|
}
|
||||||
const actions: ActionButton[] = [
|
const primaryAction: CardPrimaryAction = {
|
||||||
{
|
|
||||||
icon: Edit,
|
|
||||||
text: $t('button.edit'),
|
|
||||||
className: '',
|
|
||||||
permission: '',
|
|
||||||
onClick(row: BotInfo) {
|
|
||||||
modalRef.value?.open('edit', row);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Setting,
|
icon: Setting,
|
||||||
text: $t('button.setting'),
|
text: $t('button.setting'),
|
||||||
className: '',
|
|
||||||
permission: '',
|
|
||||||
onClick(row: BotInfo) {
|
onClick(row: BotInfo) {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/ai/bots/setting/' + row.id,
|
path: `/ai/bots/setting/${row.id}`,
|
||||||
query: {
|
query: {
|
||||||
pageKey: '/ai/bots',
|
pageKey: '/ai/bots',
|
||||||
navTitle: resolveNavTitle(row),
|
navTitle: resolveNavTitle(row),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions: ActionButton[] = [
|
||||||
|
{
|
||||||
|
icon: Edit,
|
||||||
|
text: $t('button.edit'),
|
||||||
|
placement: 'inline',
|
||||||
|
onClick(row: BotInfo) {
|
||||||
|
modalRef.value?.open('edit', row);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Delete,
|
icon: Delete,
|
||||||
text: $t('button.delete'),
|
text: $t('button.delete'),
|
||||||
className: 'item-danger',
|
tone: 'danger',
|
||||||
permission: '/api/v1/bot/remove',
|
permission: '/api/v1/bot/remove',
|
||||||
|
placement: 'inline',
|
||||||
onClick(row: BotInfo) {
|
onClick(row: BotInfo) {
|
||||||
removeBot(row);
|
removeBot(row);
|
||||||
},
|
},
|
||||||
@@ -298,6 +300,7 @@ const getSideList = async () => {
|
|||||||
<CardList
|
<CardList
|
||||||
:default-icon="defaultAvatar"
|
:default-icon="defaultAvatar"
|
||||||
:data="pageList"
|
:data="pageList"
|
||||||
|
:primary-action="primaryAction"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,29 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormInstance } from 'element-plus';
|
import type {FormInstance} from 'element-plus';
|
||||||
|
import {ElForm, ElFormItem, ElInput, ElInputNumber, ElMessage, ElMessageBox,} from 'element-plus';
|
||||||
|
|
||||||
import type { ActionButton } from '#/components/page/CardList.vue';
|
import type {ActionButton, CardPrimaryAction,} from '#/components/page/CardList.vue';
|
||||||
|
import CardPage from '#/components/page/CardList.vue';
|
||||||
|
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import {computed, onMounted, ref} from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import {useRouter} from 'vue-router';
|
||||||
|
|
||||||
import { EasyFlowFormModal } from '@easyflow/common-ui';
|
import {EasyFlowFormModal} from '@easyflow/common-ui';
|
||||||
import { $t } from '@easyflow/locales';
|
import {$t} from '@easyflow/locales';
|
||||||
|
|
||||||
import { Delete, Edit, Notebook, Plus, Search } from '@element-plus/icons-vue';
|
import {Delete, Edit, Notebook, Plus, Search} from '@element-plus/icons-vue';
|
||||||
import {
|
import {tryit} from 'radash';
|
||||||
ElForm,
|
|
||||||
ElFormItem,
|
|
||||||
ElInput,
|
|
||||||
ElInputNumber,
|
|
||||||
ElMessage,
|
|
||||||
ElMessageBox,
|
|
||||||
} from 'element-plus';
|
|
||||||
import { tryit } from 'radash';
|
|
||||||
|
|
||||||
import { api } from '#/api/request';
|
import {api} from '#/api/request';
|
||||||
import defaultIcon from '#/assets/ai/knowledge/book.svg';
|
import defaultIcon from '#/assets/ai/knowledge/book.svg';
|
||||||
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
||||||
import CardPage from '#/components/page/CardList.vue';
|
|
||||||
import PageData from '#/components/page/PageData.vue';
|
import PageData from '#/components/page/PageData.vue';
|
||||||
import PageSide from '#/components/page/PageSide.vue';
|
import PageSide from '#/components/page/PageSide.vue';
|
||||||
import DocumentCollectionModal from '#/views/ai/documentCollection/DocumentCollectionModal.vue';
|
import DocumentCollectionModal from '#/views/ai/documentCollection/DocumentCollectionModal.vue';
|
||||||
@@ -37,6 +30,17 @@ const collectionTypeLabelMap = {
|
|||||||
function resolveNavTitle(row: Record<string, any>) {
|
function resolveNavTitle(row: Record<string, any>) {
|
||||||
return row?.title || row?.name || '';
|
return row?.title || row?.name || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openKnowledgeDetail(row: { id: string; name?: string; title?: string }) {
|
||||||
|
router.push({
|
||||||
|
path: '/ai/documentCollection/document',
|
||||||
|
query: {
|
||||||
|
id: row.id,
|
||||||
|
pageKey: '/ai/documentCollection',
|
||||||
|
navTitle: resolveNavTitle(row),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
interface FieldDefinition {
|
interface FieldDefinition {
|
||||||
// 字段名称
|
// 字段名称
|
||||||
prop: string;
|
prop: string;
|
||||||
@@ -49,38 +53,29 @@ interface FieldDefinition {
|
|||||||
// 占位符
|
// 占位符
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
// 操作按钮配置
|
const primaryAction: CardPrimaryAction = {
|
||||||
|
icon: Notebook,
|
||||||
|
text: $t('documentCollection.actions.knowledge'),
|
||||||
|
permission: '/api/v1/documentCollection/save',
|
||||||
|
onClick(row) {
|
||||||
|
openKnowledgeDetail(row);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const actions: ActionButton[] = [
|
const actions: ActionButton[] = [
|
||||||
{
|
{
|
||||||
icon: Edit,
|
icon: Edit,
|
||||||
text: $t('button.edit'),
|
text: $t('button.edit'),
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/documentCollection/save',
|
permission: '/api/v1/documentCollection/save',
|
||||||
|
placement: 'inline',
|
||||||
onClick(row) {
|
onClick(row) {
|
||||||
aiKnowledgeModalRef.value.openDialog(row);
|
aiKnowledgeModalRef.value.openDialog(row);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: Notebook,
|
|
||||||
text: $t('documentCollection.actions.knowledge'),
|
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/documentCollection/save',
|
|
||||||
onClick(row) {
|
|
||||||
router.push({
|
|
||||||
path: '/ai/documentCollection/document',
|
|
||||||
query: {
|
|
||||||
id: row.id,
|
|
||||||
pageKey: '/ai/documentCollection',
|
|
||||||
navTitle: resolveNavTitle(row),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: Search,
|
icon: Search,
|
||||||
text: $t('documentCollection.actions.retrieve'),
|
text: $t('documentCollection.actions.retrieve'),
|
||||||
className: '',
|
placement: 'inline',
|
||||||
permission: '',
|
|
||||||
onClick(row) {
|
onClick(row) {
|
||||||
router.push({
|
router.push({
|
||||||
path: '/ai/documentCollection/document',
|
path: '/ai/documentCollection/document',
|
||||||
@@ -96,8 +91,9 @@ const actions: ActionButton[] = [
|
|||||||
{
|
{
|
||||||
text: $t('button.delete'),
|
text: $t('button.delete'),
|
||||||
icon: Delete,
|
icon: Delete,
|
||||||
className: 'item-danger',
|
tone: 'danger',
|
||||||
permission: '/api/v1/documentCollection/remove',
|
permission: '/api/v1/documentCollection/remove',
|
||||||
|
placement: 'inline',
|
||||||
onClick(row) {
|
onClick(row) {
|
||||||
handleDelete(row);
|
handleDelete(row);
|
||||||
},
|
},
|
||||||
@@ -314,10 +310,11 @@ function changeCategory(category: any) {
|
|||||||
<template #default="{ pageList }">
|
<template #default="{ pageList }">
|
||||||
<CardPage
|
<CardPage
|
||||||
:default-icon="defaultIcon"
|
:default-icon="defaultIcon"
|
||||||
title-key="title"
|
title-field="title"
|
||||||
avatar-key="icon"
|
icon-field="icon"
|
||||||
description-key="description"
|
desc-field="description"
|
||||||
:data="pageList"
|
:data="pageList"
|
||||||
|
:primary-action="primaryAction"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
tag-field="collectionType"
|
tag-field="collectionType"
|
||||||
:tag-map="collectionTypeLabelMap"
|
:tag-map="collectionTypeLabelMap"
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormInstance } from 'element-plus';
|
import type {FormInstance} from 'element-plus';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
import { EasyFlowFormModal } from '@easyflow/common-ui';
|
|
||||||
|
|
||||||
import { Plus, Remove } from '@element-plus/icons-vue';
|
|
||||||
import {
|
import {
|
||||||
ElForm,
|
ElForm,
|
||||||
ElFormItem,
|
ElFormItem,
|
||||||
@@ -18,13 +12,20 @@ import {
|
|||||||
ElSelect,
|
ElSelect,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
import { api } from '#/api/request';
|
import {onMounted, ref} from 'vue';
|
||||||
|
|
||||||
|
import {EasyFlowFormModal} from '@easyflow/common-ui';
|
||||||
|
|
||||||
|
import {Plus, Remove} from '@element-plus/icons-vue';
|
||||||
|
|
||||||
|
import {api} from '#/api/request';
|
||||||
import UploadAvatar from '#/components/upload/UploadAvatar.vue';
|
import UploadAvatar from '#/components/upload/UploadAvatar.vue';
|
||||||
import { $t } from '#/locales';
|
import {$t} from '#/locales';
|
||||||
|
|
||||||
const emit = defineEmits(['reload']);
|
const emit = defineEmits(['reload']);
|
||||||
const embeddingLlmList = ref<any>([]);
|
const embeddingLlmList = ref<any>([]);
|
||||||
const rerankerLlmList = ref<any>([]);
|
const rerankerLlmList = ref<any>([]);
|
||||||
|
const categoryList = ref<any[]>([]);
|
||||||
interface headersType {
|
interface headersType {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -46,6 +47,11 @@ onMounted(() => {
|
|||||||
api.get('/api/v1/model/list?supportRerankerLlmList=true').then((res) => {
|
api.get('/api/v1/model/list?supportRerankerLlmList=true').then((res) => {
|
||||||
rerankerLlmList.value = res.data;
|
rerankerLlmList.value = res.data;
|
||||||
});
|
});
|
||||||
|
api.get('/api/v1/pluginCategory/list').then((res) => {
|
||||||
|
if (res.errorCode === 0) {
|
||||||
|
categoryList.value = res.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openDialog,
|
openDialog,
|
||||||
@@ -57,6 +63,7 @@ const isAdd = ref(true);
|
|||||||
const tempAddHeaders = ref<headersType[]>([]);
|
const tempAddHeaders = ref<headersType[]>([]);
|
||||||
const entity = ref<any>({
|
const entity = ref<any>({
|
||||||
alias: '',
|
alias: '',
|
||||||
|
categoryIds: [],
|
||||||
deptId: '',
|
deptId: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
title: '',
|
title: '',
|
||||||
@@ -91,6 +98,7 @@ const rules = ref({
|
|||||||
|
|
||||||
// functions
|
// functions
|
||||||
function openDialog(row: any) {
|
function openDialog(row: any) {
|
||||||
|
tempAddHeaders.value = [];
|
||||||
if (row.id) {
|
if (row.id) {
|
||||||
isAdd.value = false;
|
isAdd.value = false;
|
||||||
if (row.headers) {
|
if (row.headers) {
|
||||||
@@ -100,26 +108,62 @@ function openDialog(row: any) {
|
|||||||
entity.value = {
|
entity.value = {
|
||||||
...row,
|
...row,
|
||||||
authType: row.authType || 'none',
|
authType: row.authType || 'none',
|
||||||
|
categoryIds: row.categoryIds?.map((item: any) => item.id) || [],
|
||||||
};
|
};
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function syncPluginCategories(pluginId: string, categoryIds: string[]) {
|
||||||
|
if (!pluginId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const relationRes = await api.post(
|
||||||
|
'/api/v1/pluginCategoryMapping/updateRelation',
|
||||||
|
{
|
||||||
|
pluginId,
|
||||||
|
categoryIds,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (relationRes.errorCode !== 0) {
|
||||||
|
throw new Error(relationRes.message || 'sync categories failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
saveForm.value?.validate((valid) => {
|
saveForm.value?.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
btnLoading.value = true;
|
||||||
const plainEntity = { ...entity.value };
|
const plainEntity = { ...entity.value };
|
||||||
const plainHeaders = [...tempAddHeaders.value];
|
const plainHeaders = [...tempAddHeaders.value];
|
||||||
|
const categoryIds = [...(plainEntity.categoryIds || [])];
|
||||||
|
delete plainEntity.categoryIds;
|
||||||
if (isAdd.value) {
|
if (isAdd.value) {
|
||||||
api
|
api
|
||||||
.post('/api/v1/plugin/plugin/save', {
|
.post('/api/v1/plugin/plugin/save', {
|
||||||
...plainEntity,
|
...plainEntity,
|
||||||
headers: plainHeaders,
|
headers: plainHeaders,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
if (res.errorCode === 0) {
|
if (res.errorCode === 0) {
|
||||||
|
const pluginId =
|
||||||
|
res.data?.id ||
|
||||||
|
res.data ||
|
||||||
|
plainEntity.id ||
|
||||||
|
entity.value.id ||
|
||||||
|
'';
|
||||||
|
await syncPluginCategories(pluginId, categoryIds);
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
ElMessage.success($t('message.saveOkMessage'));
|
ElMessage.success($t('message.saveOkMessage'));
|
||||||
emit('reload');
|
emit('reload');
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
ElMessage.error(error?.message || $t('message.saveFailMessage'));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
btnLoading.value = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
api
|
api
|
||||||
@@ -127,12 +171,21 @@ function save() {
|
|||||||
...plainEntity,
|
...plainEntity,
|
||||||
headers: plainHeaders,
|
headers: plainHeaders,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
if (res.errorCode === 0) {
|
if (res.errorCode === 0) {
|
||||||
|
await syncPluginCategories(entity.value.id, categoryIds);
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
ElMessage.success($t('message.updateOkMessage'));
|
ElMessage.success($t('message.updateOkMessage'));
|
||||||
emit('reload');
|
emit('reload');
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
ElMessage.error(error?.message || $t('message.saveFailMessage'));
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
btnLoading.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,6 +194,7 @@ function save() {
|
|||||||
function closeDialog() {
|
function closeDialog() {
|
||||||
saveForm.value?.resetFields();
|
saveForm.value?.resetFields();
|
||||||
isAdd.value = true;
|
isAdd.value = true;
|
||||||
|
tempAddHeaders.value = [];
|
||||||
entity.value = {};
|
entity.value = {};
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
}
|
}
|
||||||
@@ -199,6 +253,22 @@ function removeHeader(index: number) {
|
|||||||
:placeholder="$t('plugin.placeholder.description')"
|
:placeholder="$t('plugin.placeholder.description')"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
<ElFormItem prop="categoryIds" :label="$t('plugin.category')">
|
||||||
|
<ElSelect
|
||||||
|
v-model="entity.categoryIds"
|
||||||
|
multiple
|
||||||
|
collapse-tags
|
||||||
|
collapse-tags-tooltip
|
||||||
|
:max-collapse-tags="3"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="item in categoryList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
<ElFormItem prop="Headers" label="Headers">
|
<ElFormItem prop="Headers" label="Headers">
|
||||||
<div
|
<div
|
||||||
class="headers-container-reduce flex flex-row gap-4"
|
class="headers-container-reduce flex flex-row gap-4"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ActionButton } from '#/components/page/CardList.vue';
|
import type {
|
||||||
|
ActionButton,
|
||||||
|
CardPrimaryAction,
|
||||||
|
} from '#/components/page/CardList.vue';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
@@ -19,13 +22,11 @@ import {
|
|||||||
import { api } from '#/api/request';
|
import { api } from '#/api/request';
|
||||||
import defaultPluginIcon from '#/assets/ai/plugin/defaultPluginIcon.png';
|
import defaultPluginIcon from '#/assets/ai/plugin/defaultPluginIcon.png';
|
||||||
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
||||||
import CategorizeIcon from '#/components/icons/CategorizeIcon.vue';
|
|
||||||
import PluginToolIcon from '#/components/icons/PluginToolIcon.vue';
|
import PluginToolIcon from '#/components/icons/PluginToolIcon.vue';
|
||||||
import CardPage from '#/components/page/CardList.vue';
|
import CardPage from '#/components/page/CardList.vue';
|
||||||
import PageData from '#/components/page/PageData.vue';
|
import PageData from '#/components/page/PageData.vue';
|
||||||
import PageSide from '#/components/page/PageSide.vue';
|
import PageSide from '#/components/page/PageSide.vue';
|
||||||
import AddPluginModal from '#/views/ai/plugin/AddPluginModal.vue';
|
import AddPluginModal from '#/views/ai/plugin/AddPluginModal.vue';
|
||||||
import CategoryPluginModal from '#/views/ai/plugin/CategoryPluginModal.vue';
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -52,23 +53,7 @@ function resolveNavTitle(item: PluginRecord) {
|
|||||||
return (item.title as string) || (item.name as string) || '';
|
return (item.title as string) || (item.name as string) || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 操作按钮配置
|
function openPluginTools(item: PluginRecord) {
|
||||||
const actions: ActionButton[] = [
|
|
||||||
{
|
|
||||||
icon: Edit,
|
|
||||||
text: $t('button.edit'),
|
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/plugin/save',
|
|
||||||
onClick(item) {
|
|
||||||
aiPluginModalRef.value.openDialog(item);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: PluginToolIcon,
|
|
||||||
text: $t('plugin.button.tools'),
|
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/plugin/save',
|
|
||||||
onClick(item) {
|
|
||||||
router.push({
|
router.push({
|
||||||
path: '/ai/plugin/tools',
|
path: '/ai/plugin/tools',
|
||||||
query: {
|
query: {
|
||||||
@@ -77,22 +62,33 @@ const actions: ActionButton[] = [
|
|||||||
navTitle: resolveNavTitle(item),
|
navTitle: resolveNavTitle(item),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
|
||||||
{
|
const primaryAction: CardPrimaryAction = {
|
||||||
icon: CategorizeIcon,
|
icon: PluginToolIcon,
|
||||||
text: $t('plugin.button.categorize'),
|
text: $t('plugin.button.tools'),
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/plugin/save',
|
permission: '/api/v1/plugin/save',
|
||||||
onClick(item) {
|
onClick(item) {
|
||||||
categoryCategoryModal.value.openDialog(item);
|
openPluginTools(item);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions: ActionButton[] = [
|
||||||
|
{
|
||||||
|
icon: Edit,
|
||||||
|
text: $t('button.edit'),
|
||||||
|
permission: '/api/v1/plugin/save',
|
||||||
|
placement: 'inline',
|
||||||
|
onClick(item) {
|
||||||
|
aiPluginModalRef.value.openDialog(item);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Delete,
|
icon: Delete,
|
||||||
text: $t('button.delete'),
|
text: $t('button.delete'),
|
||||||
className: 'item-danger',
|
tone: 'danger',
|
||||||
permission: '/api/v1/plugin/remove',
|
permission: '/api/v1/plugin/remove',
|
||||||
|
placement: 'inline',
|
||||||
onClick(item) {
|
onClick(item) {
|
||||||
handleDelete(item);
|
handleDelete(item);
|
||||||
},
|
},
|
||||||
@@ -162,7 +158,6 @@ const handleDelete = (item: PluginRecord) => {
|
|||||||
|
|
||||||
const pageDataRef = ref();
|
const pageDataRef = ref();
|
||||||
const aiPluginModalRef = ref();
|
const aiPluginModalRef = ref();
|
||||||
const categoryCategoryModal = ref();
|
|
||||||
const headerButtons = [
|
const headerButtons = [
|
||||||
{
|
{
|
||||||
key: 'add',
|
key: 'add',
|
||||||
@@ -267,10 +262,11 @@ const handleClickCategory = (item: PluginCategory) => {
|
|||||||
>
|
>
|
||||||
<template #default="{ pageList }">
|
<template #default="{ pageList }">
|
||||||
<CardPage
|
<CardPage
|
||||||
title-key="title"
|
title-field="title"
|
||||||
avatar-key="icon"
|
icon-field="icon"
|
||||||
description-key="description"
|
desc-field="description"
|
||||||
:data="pageList"
|
:data="pageList"
|
||||||
|
:primary-action="primaryAction"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
:default-icon="defaultPluginIcon"
|
:default-icon="defaultPluginIcon"
|
||||||
/>
|
/>
|
||||||
@@ -279,7 +275,6 @@ const handleClickCategory = (item: PluginCategory) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AddPluginModal ref="aiPluginModalRef" @reload="handleSearch" />
|
<AddPluginModal ref="aiPluginModalRef" @reload="handleSearch" />
|
||||||
<CategoryPluginModal ref="categoryCategoryModal" @reload="handleSearch" />
|
|
||||||
<EasyFlowFormModal
|
<EasyFlowFormModal
|
||||||
:title="isEdit ? `${$t('button.edit')}` : `${$t('button.add')}`"
|
:title="isEdit ? `${$t('button.edit')}` : `${$t('button.add')}`"
|
||||||
v-model:open="dialogVisible"
|
v-model:open="dialogVisible"
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FormInstance } from 'element-plus';
|
import type {FormInstance} from 'element-plus';
|
||||||
|
import {ElForm, ElFormItem, ElInput, ElInputNumber, ElMessage, ElMessageBox,} from 'element-plus';
|
||||||
|
|
||||||
import type { ActionButton } from '#/components/page/CardList.vue';
|
import type {ActionButton, CardPrimaryAction,} from '#/components/page/CardList.vue';
|
||||||
|
import CardList from '#/components/page/CardList.vue';
|
||||||
|
|
||||||
import { computed, markRaw, onMounted, ref } from 'vue';
|
import {computed, markRaw, onMounted, ref} from 'vue';
|
||||||
|
|
||||||
import { EasyFlowFormModal } from '@easyflow/common-ui';
|
import {EasyFlowFormModal} from '@easyflow/common-ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CopyDocument,
|
CopyDocument,
|
||||||
@@ -17,27 +19,18 @@ import {
|
|||||||
Upload,
|
Upload,
|
||||||
VideoPlay,
|
VideoPlay,
|
||||||
} from '@element-plus/icons-vue';
|
} from '@element-plus/icons-vue';
|
||||||
import {
|
import {tryit} from 'radash';
|
||||||
ElForm,
|
|
||||||
ElFormItem,
|
|
||||||
ElInput,
|
|
||||||
ElInputNumber,
|
|
||||||
ElMessage,
|
|
||||||
ElMessageBox,
|
|
||||||
} from 'element-plus';
|
|
||||||
import { tryit } from 'radash';
|
|
||||||
|
|
||||||
import { api } from '#/api/request';
|
import {api} from '#/api/request';
|
||||||
import workflowIcon from '#/assets/ai/workflow/workflowIcon.png';
|
import workflowIcon from '#/assets/ai/workflow/workflowIcon.png';
|
||||||
// import workflowSvg from '#/assets/workflow.svg';
|
// import workflowSvg from '#/assets/workflow.svg';
|
||||||
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
import HeaderSearch from '#/components/headerSearch/HeaderSearch.vue';
|
||||||
import DesignIcon from '#/components/icons/DesignIcon.vue';
|
import DesignIcon from '#/components/icons/DesignIcon.vue';
|
||||||
import CardList from '#/components/page/CardList.vue';
|
|
||||||
import PageData from '#/components/page/PageData.vue';
|
import PageData from '#/components/page/PageData.vue';
|
||||||
import PageSide from '#/components/page/PageSide.vue';
|
import PageSide from '#/components/page/PageSide.vue';
|
||||||
import { $t } from '#/locales';
|
import {$t} from '#/locales';
|
||||||
import { router } from '#/router';
|
import {router} from '#/router';
|
||||||
import { useDictStore } from '#/store';
|
import {useDictStore} from '#/store';
|
||||||
|
|
||||||
import WorkflowModal from './WorkflowModal.vue';
|
import WorkflowModal from './WorkflowModal.vue';
|
||||||
|
|
||||||
@@ -54,30 +47,29 @@ interface FieldDefinition {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const primaryAction: CardPrimaryAction = {
|
||||||
|
icon: DesignIcon,
|
||||||
|
text: $t('button.design'),
|
||||||
|
permission: '/api/v1/workflow/save',
|
||||||
|
onClick: (row: any) => {
|
||||||
|
toDesignPage(row);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const actions: ActionButton[] = [
|
const actions: ActionButton[] = [
|
||||||
{
|
{
|
||||||
icon: Edit,
|
icon: Edit,
|
||||||
text: $t('button.edit'),
|
text: $t('button.edit'),
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/workflow/save',
|
permission: '/api/v1/workflow/save',
|
||||||
|
placement: 'inline',
|
||||||
onClick: (row: any) => {
|
onClick: (row: any) => {
|
||||||
showDialog(row);
|
showDialog(row);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: DesignIcon,
|
|
||||||
text: $t('button.design'),
|
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/workflow/save',
|
|
||||||
onClick: (row: any) => {
|
|
||||||
toDesignPage(row);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: VideoPlay,
|
icon: VideoPlay,
|
||||||
text: $t('button.run'),
|
text: $t('button.run'),
|
||||||
className: '',
|
placement: 'inline',
|
||||||
permission: '',
|
|
||||||
onClick: (row: any) => {
|
onClick: (row: any) => {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'RunPage',
|
name: 'RunPage',
|
||||||
@@ -90,8 +82,8 @@ const actions: ActionButton[] = [
|
|||||||
{
|
{
|
||||||
icon: Tickets,
|
icon: Tickets,
|
||||||
text: $t('aiWorkflowExecRecord.moduleName'),
|
text: $t('aiWorkflowExecRecord.moduleName'),
|
||||||
className: '',
|
|
||||||
permission: '/api/v1/workflow/save',
|
permission: '/api/v1/workflow/save',
|
||||||
|
placement: 'menu',
|
||||||
onClick: (row: any) => {
|
onClick: (row: any) => {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'ExecRecord',
|
name: 'ExecRecord',
|
||||||
@@ -104,8 +96,7 @@ const actions: ActionButton[] = [
|
|||||||
{
|
{
|
||||||
icon: Download,
|
icon: Download,
|
||||||
text: $t('button.export'),
|
text: $t('button.export'),
|
||||||
className: '',
|
placement: 'menu',
|
||||||
permission: '',
|
|
||||||
onClick: (row: any) => {
|
onClick: (row: any) => {
|
||||||
exportJson(row);
|
exportJson(row);
|
||||||
},
|
},
|
||||||
@@ -113,8 +104,7 @@ const actions: ActionButton[] = [
|
|||||||
{
|
{
|
||||||
icon: CopyDocument,
|
icon: CopyDocument,
|
||||||
text: $t('button.copy'),
|
text: $t('button.copy'),
|
||||||
className: '',
|
placement: 'menu',
|
||||||
permission: '',
|
|
||||||
onClick: (row: any) => {
|
onClick: (row: any) => {
|
||||||
showDialog({
|
showDialog({
|
||||||
title: `${row.title}Copy`,
|
title: `${row.title}Copy`,
|
||||||
@@ -125,8 +115,8 @@ const actions: ActionButton[] = [
|
|||||||
{
|
{
|
||||||
icon: Delete,
|
icon: Delete,
|
||||||
text: $t('button.delete'),
|
text: $t('button.delete'),
|
||||||
className: 'item-danger',
|
tone: 'danger',
|
||||||
permission: '',
|
placement: 'inline',
|
||||||
onClick: (row: any) => {
|
onClick: (row: any) => {
|
||||||
remove(row);
|
remove(row);
|
||||||
},
|
},
|
||||||
@@ -402,6 +392,7 @@ function handleHeaderButtonClick(data: any) {
|
|||||||
<CardList
|
<CardList
|
||||||
:default-icon="workflowIcon"
|
:default-icon="workflowIcon"
|
||||||
:data="pageList"
|
:data="pageList"
|
||||||
|
:primary-action="primaryAction"
|
||||||
:actions="actions"
|
:actions="actions"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user