Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front

This commit is contained in:
X1627315083@163.com
2026-03-05 13:51:17 +08:00
11 changed files with 246 additions and 211 deletions

View File

@@ -0,0 +1,5 @@
<svg width="13" height="14" viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.77778 6.3C5.77778 5.9134 5.45443 5.6 5.05556 5.6C4.65668 5.6 4.33333 5.9134 4.33333 6.3V9.8C4.33333 10.1866 4.65668 10.5 5.05556 10.5C5.45443 10.5 5.77778 10.1866 5.77778 9.8V6.3Z" fill="#FFEAE2"/>
<path d="M8.66667 6.3C8.66667 5.9134 8.34332 5.6 7.94444 5.6C7.54557 5.6 7.22222 5.9134 7.22222 6.3V9.8C7.22222 10.1866 7.54557 10.5 7.94444 10.5C8.34332 10.5 8.66667 10.1866 8.66667 9.8V6.3Z" fill="#FFEAE2"/>
<path d="M6.97498 0C8.3006 0 9.45611 0.874433 9.77762 2.1209L9.95278 2.8H11.5453C11.5515 2.79992 11.5578 2.79992 11.564 2.8H12.2778C12.6767 2.8 13 3.1134 13 3.5C13 3.8866 12.6767 4.2 12.2778 4.2H12.2045L11.3977 11.4983C11.2405 12.9211 10.0017 14 8.5253 14H4.4747C2.99834 14 1.75954 12.9211 1.60225 11.4983L0.79547 4.2H0.722222C0.32335 4.2 0 3.8866 0 3.5C0 3.1134 0.32335 2.8 0.722222 2.8H1.43598C1.44223 2.79992 1.44846 2.79992 1.45467 2.8H3.04722L3.22238 2.1209C3.54389 0.874431 4.6994 0 6.02502 0H6.97498ZM9.40399 4.2C9.39353 4.20022 9.3831 4.20022 9.37269 4.2H3.6273C3.6169 4.20022 3.60647 4.20022 3.59601 4.2H2.24818L3.03848 11.3491C3.11712 12.0605 3.73652 12.6 4.4747 12.6H8.5253C9.26348 12.6 9.88288 12.0605 9.96152 11.3491L10.7518 4.2H9.40399ZM4.62369 2.46045L4.53611 2.8H8.46387L8.37629 2.46045C8.21554 1.83722 7.63778 1.4 6.97497 1.4H6.025C5.3622 1.4 4.78444 1.83722 4.62369 2.46045Z" fill="#FFEAE2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -30,7 +30,7 @@
<script setup lang="ts">
import flowCanvas from './FlowCanvas/flow-canvas.vue'
import card from './FlowCanvas/components/cards/index.vue'
import card from './FlowCanvas/components/nodes/cards/index.vue'
import { computed, ref, markRaw, onMounted, reactive, nextTick } from 'vue'
const data = reactive({
x: 100,

View File

@@ -8,7 +8,7 @@
:position="handle.position"
:connectable="false"
/>
<div class="item">
<div class="item" @mousedown="(e) => stateManager.setActiveNodeID(node.id)">
<slot></slot>
</div>
<div class="add" @mousedown.stop v-if="isAdd" @click="onAdd">

View File

@@ -156,7 +156,7 @@
<style lang="less" scoped>
.card {
width: 250px;
width: 244px;
height: auto;
--border-radius: 16px;
border-radius: var(--border-radius);
@@ -164,6 +164,9 @@
box-shadow: 0 15px 21px 0 rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
&.active {
box-shadow: 0 15px 21px 0 rgba(0, 0, 0, 0.05), 0 0 0 2px #fae5d8;
}
> .header {
border-radius: var(--border-radius) var(--border-radius) 0 0;
height: 50px;
@@ -173,7 +176,7 @@
padding: 0 16px;
position: relative;
user-select: none;
> .delete-icon{
> .delete-icon {
margin-left: auto;
cursor: pointer;
}

View File

@@ -1,17 +1,11 @@
<template>
<!-- 结果图片 -->
<div
class="text"
:class="{ edit }"
@click="onClick"
@mousedown="onMouseDown"
@dblclick="onDoubleClick"
>
<div class="text" @mousedown="onMouseDown">
<div
tabindex="0"
class="input"
ref="inputRef"
:contenteditable="edit"
:contenteditable="active"
@input="onInput"
@blur="onBlur"
@paste.prevent
@@ -20,8 +14,12 @@
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, nextTick } from 'vue'
import { reactive, ref, onMounted, nextTick, watch } from 'vue'
const props = defineProps({
active: {
type: Boolean,
default: false
},
data: {
type: Object,
default: () => ({})
@@ -31,7 +29,6 @@
const data = reactive({
text: props.data?.text || '点击编辑文本'
})
const edit = ref(false)
const time = ref(null)
const inputRef = ref<any>()
const onInput = () => {
@@ -39,27 +36,26 @@
data.text = text
emit('update-data', data)
}
const onBlur = () => {
edit.value = false
}
const onClick = () => {
if (edit.value) return
edit.value = true
nextTick(() => {
// 光标定位到文本末尾
const range = document.createRange()
const selection = window.getSelection()
range.selectNodeContents(inputRef.value)
range.collapse(false)
selection.removeAllRanges()
selection.addRange(range)
})
}
const onDoubleClick = () => {
clearTimeout(time.value)
}
// watch(
// () => props.active,
// (newVal) => {
// if (!newVal) return
// nextTick(() => {
// // 光标定位到文本末尾
// const range = document.createRange()
// const selection = window.getSelection()
// range.selectNodeContents(inputRef.value)
// range.collapse(false)
// selection.removeAllRanges()
// selection.addRange(range)
// })
// }
// )
const onMouseDown = (e: MouseEvent) => {
if (edit.value) e.stopPropagation()
if (props.active) e.stopPropagation()
}
const onBlur = () => {
// emit('update-data', data)
}
onMounted(() => {
inputRef.value.innerHTML = data.text
@@ -73,7 +69,7 @@
user-select: none;
border: 1px solid transparent;
padding: 2px;
&.edit {
&.active {
border-color: #000;
> .input {
cursor: text;

View File

@@ -27,6 +27,8 @@
>
<component
:is="components[node.data.component]"
:class="{ active: stateManager.activeNodeID.value === node.id }"
:active="stateManager.activeNodeID.value === node.id"
:node="node"
:config="node.data"
:data="node.data.data"
@@ -127,7 +129,11 @@
stateManager.nodes.value = layout(
stateManager.nodes.value,
stateManager.edges.value,
direction
direction,
{
nodesep: nodeManager.nodesep,
ranksep: nodeManager.ranksep
}
)
nextTick(() => {
fitView({ maxZoom: 1 })
@@ -187,6 +193,15 @@
}
}
})
nodeManager.createResultNode({
data: {
disableDelete: true,
isHeader: false,
data: {
url: props.config.url
}
}
})
})
</script>
<style lang="less">

View File

@@ -26,6 +26,7 @@ export class EventManager {
}
/** 处理点击 */
handleClick(event: any) {
this.stateManager.setActiveNodeID("")
const tool = this.stateManager.tool.value
if (tool === TOOLS.TEXT) {
const { x, y, zoom } = this.vueFlow.value.viewport

View File

@@ -12,6 +12,8 @@ interface NodeOptions {
export class NodeManager {
stateManager: any
vueFlow: any
nodesep = 100 // 节点间距
ranksep = 100 // 层级间距
constructor(options) {
this.stateManager = options.stateManager;
this.vueFlow = options.vueFlow
@@ -37,7 +39,7 @@ export class NodeManager {
(!snode ?
{ x: positionX, y: positionY } :
{
x: snode.position.x + snode.dimensions.width + 50 + positionX,
x: snode.position.x + snode.dimensions.width + this.nodesep + positionX,
y: snode.position.y + positionY
})
const data = options?.data || {}
@@ -100,18 +102,18 @@ export class NodeManager {
}
return this.createNode(options_)
}
copyNodeById(id: string) {
const node = this.stateManager.getNodeById(id)
const flowNode = this.stateManager.flowManager.getNodeById(id)
console.log(node,this.stateManager.flowManager.getNodeById(id))
console.log(node, this.stateManager.flowManager.getNodeById(id))
if (!node) return console.warn(`copyNodeById: ${id}找不到对应节点`)
const node_ = {
...JSON.parse(JSON.stringify(node)),
id: createId(),
position: {
x: node.position.x,
y: node.position.y + (flowNode?.dimensions?.height || 0) + 50,
y: node.position.y + (flowNode?.dimensions?.height || 0) + this.ranksep,
}
}
delete node_.data?.superiorID

View File

@@ -10,6 +10,7 @@ export interface NodesItem {
}
export class StateManager {
vueFlow: any
activeNodeID: any
nodes: any
nodes_: any
edges: any
@@ -49,6 +50,7 @@ export class StateManager {
this.historyList = ref([])
this.historyIndex = ref(0)
this.activeNodeID = ref("")
this.nodes = ref<NodesItem[]>([]);
this.nodes_ = computed(() => {
return this.nodes.value.map((node, index) => {
@@ -90,6 +92,8 @@ export class StateManager {
})
}
/** 设置激活节点 */
setActiveNodeID(id: string) { this.activeNodeID.value = id }
/** 添加节点 */
addNode(node: NodesItem) {
this.nodes.value.push(node);

View File

@@ -14,7 +14,9 @@ export function useLayout() {
const previousDirection = ref('LR')
function layout(nodes, edges, direction = 'LR') {
function layout(nodes, edges, direction = 'LR', options) {
const nodesep = options?.nodesep || 50 // 节点间距
const ranksep = options?.ranksep || 50 // 层级间距
// 验证和规范化方向参数
const validDirections = ['TB', 'BT', 'LR', 'RL']
const layoutDirection = validDirections.includes(direction) ? direction : 'LR'
@@ -28,7 +30,11 @@ export function useLayout() {
// 根据方向判断是否为水平布局
const isHorizontal = layoutDirection === 'LR' || layoutDirection === 'RL'
dagreGraph.setGraph({ rankdir: layoutDirection })
dagreGraph.setGraph({
rankdir: layoutDirection,
nodesep,
ranksep,
})
previousDirection.value = layoutDirection
@@ -47,7 +53,7 @@ export function useLayout() {
}
dagre.layout(dagreGraph)
// set nodes with updated positions
return nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id)
@@ -125,20 +131,20 @@ export function findAndAddChild(items, targetId, newChild) {
*/
export function findAndRemoveChild(items, targetId) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
// 如果找到目标节点,从当前数组中删除
if (item.id === targetId) {
items.splice(i, 1)
return true
}
// 递归搜索子节点
if (item.children && item.children.length > 0) {
const found = findAndRemoveChild(item.children, targetId)
if (found) return true
}
const item = items[i]
// 如果找到目标节点,从当前数组中删除
if (item.id === targetId) {
items.splice(i, 1)
return true
}
// 递归搜索子节点
if (item.children && item.children.length > 0) {
const found = findAndRemoveChild(item.children, targetId)
if (found) return true
}
}
return false
}
}

View File

@@ -1,174 +1,179 @@
<template>
<div class="assist-input-wrapper flex flex-col" :class="{ agent: isAgentMode }">
<div class="scroll-content flex-col">
<!-- 图片预览区域 -->
<div v-if="uploadedImages.length > 0" class="image-preview-list flex wrap">
<div
v-for="(image, index) in uploadedImages"
:key="index"
class="image-preview-item"
>
<img :src="image.url" :alt="image.name" class="preview-image" />
<div class="image-remove-btn" @click="removeImage(index)">
<SvgIcon name="delete" size="16" />
<div class="animate-container flex-1 flex flex-col">
<div class="scroll-content flex-col">
<!-- 图片预览区域 -->
<div v-if="uploadedImages.length > 0" class="image-preview-list flex wrap">
<div
v-for="(image, index) in uploadedImages"
:key="index"
class="image-preview-item"
>
<img :src="image.url" :alt="image.name" class="preview-image" />
<div class="image-remove-btn" @click="removeImage(index)">
<SvgIcon name="delete" size="16" />
</div>
</div>
</div>
<!-- 编辑区域 -->
<div
ref="editorRef"
class="editor"
contenteditable="true"
:placeholder="$t('Input.placeholder')"
@input="handleEditorInput"
@paste="handleEditorPaste"
@keydown="handleKeyDown"
></div>
</div>
<!-- 编辑区域 -->
<div
ref="editorRef"
class="editor"
contenteditable="true"
:placeholder="$t('Input.placeholder')"
@input="handleEditorInput"
@paste="handleEditorPaste"
@keydown="handleKeyDown"
></div>
</div>
<div class="operate flex align-center space-between">
<div class="left flex align-center">
<div class="attach flex flex-center" @click="triggerFileUpload">
<img src="@/assets/icons/attach.svg" alt="" />
</div>
<input
ref="fileInputRef"
type="file"
accept="image/*"
style="display: none"
@change="handleFileChange"
/>
<el-select
v-if="!isAgentMode"
v-model="typeValue"
:placeholder="$t('Input.typePlaceholder')"
>
<el-option
v-for="item in typeOptions"
class="input-option"
:key="item.value"
:label="$t(item.label)"
:value="item.value"
<div class="operate flex align-center space-between">
<div class="left flex align-center">
<div class="attach flex flex-center" @click="triggerFileUpload">
<img src="@/assets/icons/attach.svg" alt="" />
</div>
<input
ref="fileInputRef"
type="file"
accept="image/*"
style="display: none"
@change="handleFileChange"
/>
</el-select>
<el-select
v-if="!isAgentMode"
v-model="areaValue"
:placeholder="$t('Input.areaPlaceholder')"
>
<el-option
v-for="item in areaOptions"
class="input-option"
:key="item.value"
:label="$t(item.label)"
:value="item.value"
/>
</el-select>
<div v-if="!isAgentMode" class="fida-style-select-wrapper">
<el-select
v-model="styleValue"
:placeholder="$t('Input.stylePlaceholder')"
@focus="openStylePopup"
/>
v-if="!isAgentMode"
v-model="typeValue"
:placeholder="$t('Input.typePlaceholder')"
>
<el-option
v-for="item in typeOptions"
class="input-option"
:key="item.value"
:label="$t(item.label)"
:value="item.value"
/>
</el-select>
<el-select
v-if="!isAgentMode"
v-model="areaValue"
:placeholder="$t('Input.areaPlaceholder')"
>
<el-option
v-for="item in areaOptions"
class="input-option"
:key="item.value"
:label="$t(item.label)"
:value="item.value"
/>
</el-select>
<div v-if="!isAgentMode" class="fida-style-select-wrapper">
<el-select
v-model="styleValue"
:placeholder="$t('Input.stylePlaceholder')"
@focus="openStylePopup"
/>
<el-popover
v-model:visible="stylePopupVisible"
placement="top"
:width="342"
:show-arrow="false"
trigger="click"
popper-class="fida-style-select-popover"
>
<template #reference>
<div class="fida-style-select-trigger"></div>
</template>
<div class="fida-style-popover-content flex flex-col">
<div class="fida-style-popover-header">
{{ $t('Input.chooseStyle') }}
</div>
<div class="fida-style-popover-grid">
<div
v-for="item in styleOptions"
:key="item.value"
class="fida-style-popover-item flex flex-center"
:class="{ 'is-selected': tempSelectedValue === item.value }"
@click="selectStyle(item.value)"
>
<img
:src="getStyleImage(typeValue, item.value)"
class="style-bg"
/>
<span class="fida-option-label flex flex-center">{{
item.label
}}</span>
<img
v-show="tempSelectedValue === item.value"
src="@/assets/images/checked.png"
class="checked-item-icon"
/>
</div>
</div>
<div class="fida-style-popover-footer flex flex-center">
<button class="fida-confirm-btn" @click="confirmStyle">
{{ $t('Input.confirm') }}
</button>
</div>
</div>
</el-popover>
</div>
<el-popover
v-model:visible="stylePopupVisible"
v-model:visible="settingPopupVisible"
placement="top"
:width="342"
:show-arrow="false"
trigger="click"
popper-class="fida-style-select-popover"
popper-class="fida-setting-popover"
>
<template #reference>
<div class="fida-style-select-trigger"></div>
<img src="@/assets/images/setting.png" class="setting-icon" />
</template>
<div class="fida-style-popover-content flex flex-col">
<div class="fida-style-popover-header">
{{ $t('Input.chooseStyle') }}
<div class="fida-setting-popover-content flex flex-col">
<div class="fida-setting-popover-header">
{{ $t('Input.styleTitle') }}
</div>
<div class="fida-style-popover-grid">
<div class="fida-setting-slider-list">
<div
v-for="item in styleOptions"
:key="item.value"
class="fida-style-popover-item flex flex-center"
:class="{ 'is-selected': tempSelectedValue === item.value }"
@click="selectStyle(item.value)"
v-for="item in settingOptions"
:key="item.label"
class="fida-setting-slider-item"
>
<img
:src="getStyleImage(typeValue, item.value)"
class="style-bg"
/>
<span class="fida-option-label flex flex-center">{{
item.label
}}</span>
<img
v-show="tempSelectedValue === item.value"
src="@/assets/images/checked.png"
class="checked-item-icon"
/>
<div class="fida-slider-label">{{ $t(item.label) }}</div>
<div class="fida-slider-row flex align-center">
<el-slider
class="setting-popover-slider"
v-model="item.value"
:show-tooltip="false"
/>
<span class="fida-slider-value">{{ item.value }}%</span>
</div>
</div>
</div>
<div class="fida-style-popover-footer flex flex-center">
<button class="fida-confirm-btn" @click="confirmStyle">
{{ $t('Input.confirm') }}
</button>
</div>
</div>
</el-popover>
</div>
<el-popover
v-model:visible="settingPopupVisible"
placement="top"
:width="342"
:show-arrow="false"
trigger="click"
popper-class="fida-setting-popover"
>
<template #reference>
<img src="@/assets/images/setting.png" class="setting-icon" />
</template>
<div class="fida-setting-popover-content flex flex-col">
<div class="fida-setting-popover-header">{{ $t('Input.styleTitle') }}</div>
<div class="fida-setting-slider-list">
<div
v-for="item in settingOptions"
:key="item.label"
class="fida-setting-slider-item"
>
<div class="fida-slider-label">{{ $t(item.label) }}</div>
<div class="fida-slider-row flex align-center">
<el-slider
class="setting-popover-slider"
v-model="item.value"
:show-tooltip="false"
/>
<span class="fida-slider-value">{{ item.value }}%</span>
</div>
</div>
</div>
<div class="right">
<div
class="create-btn flex flex-center"
v-if="!isAgentMode"
@click="handleCreateProject"
>
<img src="@/assets/images/shining.png" class="shining-icon" alt="" />
<span class="create-btn-text">{{ $t('Input.createProject') }}</span>
</div>
</el-popover>
</div>
<div class="right">
<div
class="create-btn flex flex-center"
v-if="!isAgentMode"
@click="handleCreateProject"
>
<img src="@/assets/images/shining.png" class="shining-icon" alt="" />
<span class="create-btn-text">{{ $t('Input.createProject') }}</span>
</div>
<div v-else class="sender-btn flex flex-center" @click="handleSendAgent">
<img
v-show="!generating"
src="@/assets/images/sender.png"
alt=""
class="sender-icon"
/>
<div v-show="generating" class="sender-pause" />
<div v-else class="sender-btn flex flex-center" @click="handleSendAgent">
<img
v-show="!generating"
src="@/assets/images/sender.png"
alt=""
class="sender-icon"
/>
<div v-show="generating" class="sender-pause" />
</div>
</div>
</div>
</div>
<div v-if="!isAgentMode" class="report-btn flex flex-center" @click="toogltReportTag">
<SvgIcon class="light-icon" name="light" size="16" />
<span>{{ $t('Input.trendingReport') }}</span>
@@ -618,24 +623,22 @@
min-height: 23.5rem;
max-height: 43.5rem;
width: 106.3rem;
border-radius: 2.8rem;
background-color: #fff;
border: 0.1rem solid #00000005;
box-shadow: 0px 0.5rem 1.4rem 0px #0000001a;
margin: 0 auto;
padding: 0;
position: relative;
&:not(.agent):hover {
box-shadow: 0px 0.5rem 3.36rem 2.2rem #f1ede999;
&:not(.agent) .animate-container {
box-shadow: 0px 0.5rem 1.4rem 0px #0000001a;
transition: all 0.3s ease;
top: -1rem;
.report-btn {
bottom: -8.4rem;
border-radius: 2.8rem;
background-color: #fff;
border: 0.1rem solid #00000005;
&:not(.agent):hover {
box-shadow: 0px 0.5rem 3.36rem 2.2rem #f1ede999;
transform: translateY(-1rem);
}
}
.scroll-content {
display: flex;
flex: 1;