mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2026-01-23 22:43:28 +00:00
属性面板拆分
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, watch } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useDialogs } from '../../ts/useDialogs';
|
import ShikigamiPanel from './panels/ShikigamiPanel.vue';
|
||||||
import { getLogicFlowInstance } from '@/ts/useLogicFlow';
|
import YuhunPanel from './panels/YuhunPanel.vue';
|
||||||
|
import PropertyRulePanel from './panels/PropertyRulePanel.vue';
|
||||||
type FitMode = 'contain' | 'cover' | 'fill';
|
import ImagePanel from './panels/ImagePanel.vue';
|
||||||
|
import TextPanel from './panels/TextPanel.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
height: {
|
height: {
|
||||||
@@ -16,10 +17,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { openDialog } = useDialogs();
|
|
||||||
|
|
||||||
const selectedNode = computed(() => props.node);
|
const selectedNode = computed(() => props.node);
|
||||||
|
|
||||||
const hasNodeSelected = computed(() => !!selectedNode.value);
|
const hasNodeSelected = computed(() => !!selectedNode.value);
|
||||||
|
|
||||||
const nodeType = computed(() => {
|
const nodeType = computed(() => {
|
||||||
@@ -27,132 +25,15 @@ const nodeType = computed(() => {
|
|||||||
return selectedNode.value.type || 'default';
|
return selectedNode.value.type || 'default';
|
||||||
});
|
});
|
||||||
|
|
||||||
type ImageForm = {
|
const panelMap: Record<string, any> = {
|
||||||
url: string;
|
shikigamiSelect: ShikigamiPanel,
|
||||||
fit: FitMode;
|
yuhunSelect: YuhunPanel,
|
||||||
width: number;
|
propertySelect: PropertyRulePanel,
|
||||||
height: number;
|
imageNode: ImagePanel,
|
||||||
|
textNode: TextPanel
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageForm = reactive<ImageForm>({
|
const panelComponent = computed(() => panelMap[nodeType.value] || null);
|
||||||
url: '',
|
|
||||||
fit: 'contain',
|
|
||||||
width: 180,
|
|
||||||
height: 120
|
|
||||||
});
|
|
||||||
|
|
||||||
const parseNumber = (value: any, fallback: number) => {
|
|
||||||
const num = Number(value);
|
|
||||||
return Number.isFinite(num) ? num : fallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getImageProps = (node?: any): ImageForm => {
|
|
||||||
const props = node?.properties ?? {};
|
|
||||||
const style = props.style ?? {};
|
|
||||||
return {
|
|
||||||
url: props.image?.url ?? props.url ?? '',
|
|
||||||
fit: (props.image?.fit ?? props.fit ?? 'contain') as FitMode,
|
|
||||||
width: parseNumber(props.width ?? style.width ?? node?.width, 180),
|
|
||||||
height: parseNumber(props.height ?? style.height ?? node?.height, 120)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => selectedNode.value,
|
|
||||||
(node) => {
|
|
||||||
if (!node) {
|
|
||||||
imageForm.url = '';
|
|
||||||
imageForm.fit = 'contain';
|
|
||||||
imageForm.width = 180;
|
|
||||||
imageForm.height = 120;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const next = getImageProps(node);
|
|
||||||
imageForm.url = next.url || '';
|
|
||||||
imageForm.fit = next.fit;
|
|
||||||
imageForm.width = next.width;
|
|
||||||
imageForm.height = next.height;
|
|
||||||
},
|
|
||||||
{ immediate: true, deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// 通用的弹窗处理方法
|
|
||||||
const handleOpenDialog = (type: 'shikigami' | 'yuhun' | 'property') => {
|
|
||||||
const lf = getLogicFlowInstance();
|
|
||||||
if (selectedNode.value && lf) {
|
|
||||||
const node = selectedNode.value;
|
|
||||||
const currentData = node.properties && node.properties[type] ? node.properties[type] : undefined;
|
|
||||||
|
|
||||||
openDialog(
|
|
||||||
type,
|
|
||||||
currentData,
|
|
||||||
node,
|
|
||||||
(updatedData) => {
|
|
||||||
lf.setProperties(node.id, {
|
|
||||||
...node.properties,
|
|
||||||
[type]: updatedData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyImageChanges = (partial: Partial<ImageForm>) => {
|
|
||||||
const lf = getLogicFlowInstance();
|
|
||||||
const node = selectedNode.value;
|
|
||||||
if (!lf || !node) return;
|
|
||||||
|
|
||||||
const baseProps = node.properties || {};
|
|
||||||
const merged = { ...getImageProps(node), ...partial };
|
|
||||||
|
|
||||||
const nextProps = {
|
|
||||||
...baseProps,
|
|
||||||
...merged,
|
|
||||||
width: merged.width,
|
|
||||||
height: merged.height,
|
|
||||||
style: {
|
|
||||||
...(baseProps.style || {}),
|
|
||||||
width: merged.width,
|
|
||||||
height: merged.height
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
...(baseProps.image || {}),
|
|
||||||
url: merged.url,
|
|
||||||
fit: merged.fit
|
|
||||||
},
|
|
||||||
url: merged.url
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(imageForm, merged);
|
|
||||||
lf.setProperties(node.id, nextProps);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageUpload = (e: Event) => {
|
|
||||||
const input = e.target as HTMLInputElement;
|
|
||||||
const file = input?.files?.[0];
|
|
||||||
if (!file) return;
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (evt) => {
|
|
||||||
const result = evt.target?.result as string;
|
|
||||||
if (result) {
|
|
||||||
applyImageChanges({ url: result });
|
|
||||||
}
|
|
||||||
if (input) input.value = '';
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageUrlChange = () => {
|
|
||||||
applyImageChanges({ url: imageForm.url });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSizeChange = () => {
|
|
||||||
applyImageChanges({ width: imageForm.width, height: imageForm.height });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFitChange = (val: FitMode) => {
|
|
||||||
applyImageChanges({ fit: val });
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -178,133 +59,11 @@ const handleFitChange = (val: FitMode) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 式神选择节点的特定属性 -->
|
<component v-if="panelComponent" :is="panelComponent" :node="selectedNode" />
|
||||||
<div v-if="nodeType === 'shikigamiSelect'" class="property-section">
|
<div v-else class="property-section">
|
||||||
<div class="section-header">式神属性</div>
|
<div class="section-header">暂无特定属性</div>
|
||||||
<div class="property-item">
|
<div class="property-item">
|
||||||
<span>当前选择式神:{{ selectedNode.properties?.shikigami?.name || '未选择' }}</span>
|
<div class="property-value">当前节点类型无需额外配置。</div>
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleOpenDialog('shikigami')"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
选择式神
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 御魂选择节点的特定属性 -->
|
|
||||||
<div v-if="nodeType === 'yuhunSelect'" class="property-section">
|
|
||||||
<div class="section-header">御魂属性</div>
|
|
||||||
<div class="property-item">
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleOpenDialog('yuhun')"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
选择御魂
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 属性选择节点的特定属性 -->
|
|
||||||
<div v-if="nodeType === 'propertySelect'" class="property-section">
|
|
||||||
<div class="section-header">属性设置</div>
|
|
||||||
<div class="property-item">
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
@click="handleOpenDialog('property')"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
设置属性
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 图片节点属性 -->
|
|
||||||
<div v-if="nodeType === 'imageNode'" class="property-section">
|
|
||||||
<div class="section-header">图片设置</div>
|
|
||||||
|
|
||||||
<div class="property-item">
|
|
||||||
<div class="property-label">图片 URL</div>
|
|
||||||
<div class="property-value">
|
|
||||||
<el-input
|
|
||||||
v-model="imageForm.url"
|
|
||||||
size="small"
|
|
||||||
placeholder="输入图片链接或上传文件"
|
|
||||||
style="width: 100%;"
|
|
||||||
@change="handleImageUrlChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="property-item">
|
|
||||||
<div class="property-label">上传文件</div>
|
|
||||||
<div class="property-value upload-row">
|
|
||||||
<input class="upload-input" type="file" accept="image/*" @change="handleImageUpload" />
|
|
||||||
<span class="upload-hint">本地上传将以 base64 保存</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="property-item">
|
|
||||||
<div class="property-label">显示模式</div>
|
|
||||||
<div class="property-value">
|
|
||||||
<el-select
|
|
||||||
v-model="imageForm.fit"
|
|
||||||
size="small"
|
|
||||||
style="width: 100%;"
|
|
||||||
@change="handleFitChange"
|
|
||||||
>
|
|
||||||
<el-option label="自适应" value="contain" />
|
|
||||||
<el-option label="填充" value="cover" />
|
|
||||||
<el-option label="拉伸" value="fill" />
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="property-item size-item">
|
|
||||||
<div class="property-label">宽 / 高</div>
|
|
||||||
<div class="property-value size-inputs">
|
|
||||||
<el-input-number
|
|
||||||
v-model="imageForm.width"
|
|
||||||
:min="40"
|
|
||||||
:max="1000"
|
|
||||||
size="small"
|
|
||||||
style="width: 120px;"
|
|
||||||
@change="handleSizeChange"
|
|
||||||
/>
|
|
||||||
<span class="size-divider">×</span>
|
|
||||||
<el-input-number
|
|
||||||
v-model="imageForm.height"
|
|
||||||
:min="40"
|
|
||||||
:max="1000"
|
|
||||||
size="small"
|
|
||||||
style="width: 120px;"
|
|
||||||
@change="handleSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="imageForm.url" class="property-item">
|
|
||||||
<div class="property-label">预览</div>
|
|
||||||
<div class="property-value image-preview">
|
|
||||||
<img :src="imageForm.url" alt="预览" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 文本节点属性 -->
|
|
||||||
<div v-if="nodeType === 'textNode'" class="property-section">
|
|
||||||
<div class="section-header">文本编辑</div>
|
|
||||||
<div class="property-item">
|
|
||||||
<!-- <QuillEditor-->
|
|
||||||
<!-- v-model:content="selectedNode.value.properties.html"-->
|
|
||||||
<!-- contentType="html"-->
|
|
||||||
<!-- :toolbar="quillToolbar"-->
|
|
||||||
<!-- theme="snow"-->
|
|
||||||
<!-- style="height:120px;"-->
|
|
||||||
<!-- @update:content="val => updateNodeData('html', val)"-->
|
|
||||||
<!-- />-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -382,50 +141,4 @@ const handleFitChange = (val: FitMode) => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.property-value.upload-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-hint {
|
|
||||||
color: #909399;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-item .property-value {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-inputs {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-divider {
|
|
||||||
color: #909399;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview {
|
|
||||||
border: 1px solid #ebeef5;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 6px;
|
|
||||||
background: #fafafa;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 140px;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
237
src/components/flow/panels/ImagePanel.vue
Normal file
237
src/components/flow/panels/ImagePanel.vue
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, watch } from 'vue';
|
||||||
|
import { getLogicFlowInstance } from '@/ts/useLogicFlow';
|
||||||
|
|
||||||
|
type FitMode = 'contain' | 'cover' | 'fill';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
node: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
type ImageForm = {
|
||||||
|
url: string;
|
||||||
|
fit: FitMode;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageForm = reactive<ImageForm>({
|
||||||
|
url: '',
|
||||||
|
fit: 'contain',
|
||||||
|
width: 180,
|
||||||
|
height: 120
|
||||||
|
});
|
||||||
|
|
||||||
|
const parseNumber = (value: any, fallback: number) => {
|
||||||
|
const num = Number(value);
|
||||||
|
return Number.isFinite(num) ? num : fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImageProps = (node?: any): ImageForm => {
|
||||||
|
const props = node?.properties ?? {};
|
||||||
|
const style = props.style ?? {};
|
||||||
|
return {
|
||||||
|
url: props.image?.url ?? props.url ?? '',
|
||||||
|
fit: (props.image?.fit ?? props.fit ?? 'contain') as FitMode,
|
||||||
|
width: parseNumber(props.width ?? style.width ?? node?.width, 180),
|
||||||
|
height: parseNumber(props.height ?? style.height ?? node?.height, 120)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.node,
|
||||||
|
(node) => {
|
||||||
|
if (!node) {
|
||||||
|
imageForm.url = '';
|
||||||
|
imageForm.fit = 'contain';
|
||||||
|
imageForm.width = 180;
|
||||||
|
imageForm.height = 120;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const next = getImageProps(node);
|
||||||
|
imageForm.url = next.url || '';
|
||||||
|
imageForm.fit = next.fit;
|
||||||
|
imageForm.width = next.width;
|
||||||
|
imageForm.height = next.height;
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const applyImageChanges = (partial: Partial<ImageForm>) => {
|
||||||
|
const lf = getLogicFlowInstance();
|
||||||
|
const node = props.node;
|
||||||
|
if (!lf || !node) return;
|
||||||
|
|
||||||
|
const baseProps = node.properties || {};
|
||||||
|
const merged = { ...getImageProps(node), ...partial };
|
||||||
|
|
||||||
|
const nextProps = {
|
||||||
|
...baseProps,
|
||||||
|
...merged,
|
||||||
|
width: merged.width,
|
||||||
|
height: merged.height,
|
||||||
|
style: {
|
||||||
|
...(baseProps.style || {}),
|
||||||
|
width: merged.width,
|
||||||
|
height: merged.height
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
...(baseProps.image || {}),
|
||||||
|
url: merged.url,
|
||||||
|
fit: merged.fit
|
||||||
|
},
|
||||||
|
url: merged.url
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(imageForm, merged);
|
||||||
|
lf.setProperties(node.id, nextProps);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageUpload = (e: Event) => {
|
||||||
|
const input = e.target as HTMLInputElement;
|
||||||
|
const file = input?.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (evt) => {
|
||||||
|
const result = evt.target?.result as string;
|
||||||
|
if (result) {
|
||||||
|
applyImageChanges({ url: result });
|
||||||
|
}
|
||||||
|
if (input) input.value = '';
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImageUrlChange = () => {
|
||||||
|
applyImageChanges({ url: imageForm.url });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSizeChange = () => {
|
||||||
|
applyImageChanges({ width: imageForm.width, height: imageForm.height });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFitChange = (val: FitMode) => {
|
||||||
|
applyImageChanges({ fit: val });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="property-section">
|
||||||
|
<div class="section-header">图片设置</div>
|
||||||
|
|
||||||
|
<div class="property-item">
|
||||||
|
<div class="property-label">图片 URL</div>
|
||||||
|
<div class="property-value">
|
||||||
|
<el-input
|
||||||
|
v-model="imageForm.url"
|
||||||
|
size="small"
|
||||||
|
placeholder="输入图片链接或上传文件"
|
||||||
|
style="width: 100%;"
|
||||||
|
@change="handleImageUrlChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property-item">
|
||||||
|
<div class="property-label">上传文件</div>
|
||||||
|
<div class="property-value upload-row">
|
||||||
|
<input class="upload-input" type="file" accept="image/*" @change="handleImageUpload" />
|
||||||
|
<span class="upload-hint">本地上传将以 base64 保存</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property-item">
|
||||||
|
<div class="property-label">显示模式</div>
|
||||||
|
<div class="property-value">
|
||||||
|
<el-select
|
||||||
|
v-model="imageForm.fit"
|
||||||
|
size="small"
|
||||||
|
style="width: 100%;"
|
||||||
|
@change="handleFitChange"
|
||||||
|
>
|
||||||
|
<el-option label="自适应" value="contain" />
|
||||||
|
<el-option label="填充" value="cover" />
|
||||||
|
<el-option label="拉伸" value="fill" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property-item size-item">
|
||||||
|
<div class="property-label">宽 / 高</div>
|
||||||
|
<div class="property-value size-inputs">
|
||||||
|
<el-input-number
|
||||||
|
v-model="imageForm.width"
|
||||||
|
:min="40"
|
||||||
|
:max="1000"
|
||||||
|
size="small"
|
||||||
|
style="width: 120px;"
|
||||||
|
@change="handleSizeChange"
|
||||||
|
/>
|
||||||
|
<span class="size-divider">×</span>
|
||||||
|
<el-input-number
|
||||||
|
v-model="imageForm.height"
|
||||||
|
:min="40"
|
||||||
|
:max="1000"
|
||||||
|
size="small"
|
||||||
|
style="width: 120px;"
|
||||||
|
@change="handleSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="imageForm.url" class="property-item">
|
||||||
|
<div class="property-label">预览</div>
|
||||||
|
<div class="property-value image-preview">
|
||||||
|
<img :src="imageForm.url" alt="预览" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.property-value.upload-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-hint {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-item .property-value {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-divider {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview {
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px;
|
||||||
|
background: #fafafa;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 140px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
33
src/components/flow/panels/PropertyRulePanel.vue
Normal file
33
src/components/flow/panels/PropertyRulePanel.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useDialogs } from '@/ts/useDialogs';
|
||||||
|
import { getLogicFlowInstance } from '@/ts/useLogicFlow';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
node: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { openDialog } = useDialogs();
|
||||||
|
|
||||||
|
const handleOpenDialog = () => {
|
||||||
|
const lf = getLogicFlowInstance();
|
||||||
|
const node = props.node;
|
||||||
|
if (!lf || !node) return;
|
||||||
|
|
||||||
|
const currentData = node.properties?.property;
|
||||||
|
openDialog('property', currentData, node, (updatedData) => {
|
||||||
|
lf.setProperties(node.id, {
|
||||||
|
...node.properties,
|
||||||
|
property: updatedData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="property-section">
|
||||||
|
<div class="section-header">属性设置</div>
|
||||||
|
<div class="property-item">
|
||||||
|
<el-button type="primary" @click="handleOpenDialog" style="width: 100%">设置属性</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
34
src/components/flow/panels/ShikigamiPanel.vue
Normal file
34
src/components/flow/panels/ShikigamiPanel.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useDialogs } from '@/ts/useDialogs';
|
||||||
|
import { getLogicFlowInstance } from '@/ts/useLogicFlow';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
node: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { openDialog } = useDialogs();
|
||||||
|
|
||||||
|
const handleOpenDialog = () => {
|
||||||
|
const lf = getLogicFlowInstance();
|
||||||
|
const node = props.node;
|
||||||
|
if (!lf || !node) return;
|
||||||
|
|
||||||
|
const currentData = node.properties?.shikigami;
|
||||||
|
openDialog('shikigami', currentData, node, (updatedData) => {
|
||||||
|
lf.setProperties(node.id, {
|
||||||
|
...node.properties,
|
||||||
|
shikigami: updatedData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="property-section">
|
||||||
|
<div class="section-header">式神属性</div>
|
||||||
|
<div class="property-item">
|
||||||
|
<span>当前选择式神:{{ node.properties?.shikigami?.name || '未选择' }}</span>
|
||||||
|
<el-button type="primary" @click="handleOpenDialog" style="width: 100%">选择式神</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
14
src/components/flow/panels/TextPanel.vue
Normal file
14
src/components/flow/panels/TextPanel.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
node: any;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="property-section">
|
||||||
|
<div class="section-header">文本节点</div>
|
||||||
|
<div class="property-item">
|
||||||
|
<div class="property-value">文本编辑器待实现,当前节点内容:{{ props.node?.properties?.text?.content || '未设置' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
33
src/components/flow/panels/YuhunPanel.vue
Normal file
33
src/components/flow/panels/YuhunPanel.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useDialogs } from '@/ts/useDialogs';
|
||||||
|
import { getLogicFlowInstance } from '@/ts/useLogicFlow';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
node: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { openDialog } = useDialogs();
|
||||||
|
|
||||||
|
const handleOpenDialog = () => {
|
||||||
|
const lf = getLogicFlowInstance();
|
||||||
|
const node = props.node;
|
||||||
|
if (!lf || !node) return;
|
||||||
|
|
||||||
|
const currentData = node.properties?.yuhun;
|
||||||
|
openDialog('yuhun', currentData, node, (updatedData) => {
|
||||||
|
lf.setProperties(node.id, {
|
||||||
|
...node.properties,
|
||||||
|
yuhun: updatedData
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="property-section">
|
||||||
|
<div class="section-header">御魂属性</div>
|
||||||
|
<div class="property-item">
|
||||||
|
<el-button type="primary" @click="handleOpenDialog" style="width: 100%">选择御魂</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user