mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2025-07-08 21:31:54 +00:00
init commit
This commit is contained in:
562
src/components/flow/nodes/yys/PropertySelect.vue
Normal file
562
src/components/flow/nodes/yys/PropertySelect.vue
Normal file
@ -0,0 +1,562 @@
|
||||
<template>
|
||||
<YuhunSelect
|
||||
:showSelectYuhun="showYuhunSelect"
|
||||
:currentYuhun="currentYuhun"
|
||||
@closeSelectYuhun="closeYuhunSelect"
|
||||
@updateYuhun="updateYuhunSelect"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
title="属性选择器"
|
||||
@close="cancel"
|
||||
:before-close="cancel"
|
||||
width="80%"
|
||||
>
|
||||
<el-form :model="property" label-width="120px">
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 基础属性选项卡 -->
|
||||
<el-tab-pane label="基础属性" name="basic">
|
||||
<el-form-item label="属性类型">
|
||||
<el-select v-model="property.type" @change="handleTypeChange">
|
||||
<el-option v-for="type in propertyTypes" :key="type.value" :label="type.label" :value="type.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 攻击属性 -->
|
||||
<div v-if="property.type === 'attack'">
|
||||
<el-form-item label="攻击值类型">
|
||||
<el-radio-group v-model="property.attackType">
|
||||
<el-radio label="fixed" size="large">固定值</el-radio>
|
||||
<el-radio label="percentage" size="large">百分比</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="攻击值">
|
||||
<el-input-number v-model="property.attackValue" :min="0" :precision="property.attackType === 'percentage' ? 1 : 0" />
|
||||
<span v-if="property.attackType === 'percentage'">%</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 生命属性 -->
|
||||
<div v-if="property.type === 'health'">
|
||||
<el-form-item label="生命值类型">
|
||||
<el-radio-group v-model="property.healthType">
|
||||
<el-radio label="fixed" size="large">固定值</el-radio>
|
||||
<el-radio label="percentage" size="large">百分比</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="生命值">
|
||||
<el-input-number v-model="property.healthValue" :min="0" :precision="property.healthType === 'percentage' ? 1 : 0" />
|
||||
<span v-if="property.healthType === 'percentage'">%</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 防御属性 -->
|
||||
<div v-if="property.type === 'defense'">
|
||||
<el-form-item label="防御值类型">
|
||||
<el-radio-group v-model="property.defenseType">
|
||||
<el-radio label="fixed" size="large">固定值</el-radio>
|
||||
<el-radio label="percentage" size="large">百分比</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="防御值">
|
||||
<el-input-number v-model="property.defenseValue" :min="0" :precision="property.defenseType === 'percentage' ? 1 : 0" />
|
||||
<span v-if="property.defenseType === 'percentage'">%</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 速度属性 -->
|
||||
<div v-if="property.type === 'speed'">
|
||||
<el-form-item label="速度值">
|
||||
<el-input-number v-model="property.speedValue" :min="0" :precision="0" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 暴击相关属性 -->
|
||||
<div v-if="property.type === 'crit'">
|
||||
<el-form-item label="暴击率">
|
||||
<el-input-number v-model="property.critRate" :min="0" :max="100" :precision="1" />
|
||||
<span>%</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div v-if="property.type === 'critDmg'">
|
||||
<el-form-item label="暴击伤害">
|
||||
<el-input-number v-model="property.critDmg" :min="0" :precision="1" />
|
||||
<span>%</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 效果命中与抵抗 -->
|
||||
<div v-if="property.type === 'effectHit'">
|
||||
<el-form-item label="效果命中">
|
||||
<el-input-number v-model="property.effectHitValue" :min="0" :precision="1" />
|
||||
<span>%</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div v-if="property.type === 'effectResist'">
|
||||
<el-form-item label="效果抵抗">
|
||||
<el-input-number v-model="property.effectResistValue" :min="0" :precision="1" />
|
||||
<span>%</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 所有属性都显示的字段 -->
|
||||
<el-form-item label="优先级">
|
||||
<el-select v-model="property.priority">
|
||||
<el-option label="必须" value="required"/>
|
||||
<el-option label="推荐" value="recommended"/>
|
||||
<el-option label="可选" value="optional"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 式神要求选项卡 -->
|
||||
<el-tab-pane label="式神要求" name="shikigami">
|
||||
<el-form-item label="等级要求">
|
||||
<el-radio-group v-model="property.levelRequired" class="ml-4">
|
||||
<el-radio value="40" size="large">40</el-radio>
|
||||
<el-radio value="0" size="large">献祭</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="技能要求">
|
||||
<el-radio-group v-model="property.skillRequiredMode" class="ml-4">
|
||||
<el-radio value="all" size="large">全满</el-radio>
|
||||
<el-radio value="111" size="large">111</el-radio>
|
||||
<el-radio value="custom" size="large">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="property.skillRequiredMode === 'custom'" style="display: flex; flex-direction: row; gap: 10px; width: 100%;">
|
||||
<el-select v-for="(value, index) in property.skillRequired" :key="index" :placeholder="value"
|
||||
style="margin-bottom: 10px;" @change="updateSkillRequired(index, $event)">
|
||||
<el-option label="*" value="X"/>
|
||||
<el-option label="1" value="1"/>
|
||||
<el-option label="2" value="2"/>
|
||||
<el-option label="3" value="3"/>
|
||||
<el-option label="4" value="4"/>
|
||||
<el-option label="5" value="5"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 御魂要求选项卡 -->
|
||||
<el-tab-pane label="御魂要求" name="yuhun">
|
||||
<div style="display: flex; flex-direction: row; width: 100%;">
|
||||
<div style="display: flex; flex-direction: column; width: 50%;">
|
||||
<el-form-item label="御魂套装">
|
||||
<div style="display: flex; flex-direction: row; flex-wrap: wrap; gap: 5px;">
|
||||
<img
|
||||
v-for="(effect, index) in property.yuhun.yuhunSetEffect"
|
||||
:key="index"
|
||||
style="width: 50px; height: 50px;"
|
||||
:src="effect.avatar"
|
||||
class="image"
|
||||
@click="openYuhunSelect(index)"
|
||||
/>
|
||||
<el-button type="primary" @click="openYuhunSelect(-1)">
|
||||
<el-icon :size="20">
|
||||
<CirclePlus/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="御魂效果目标">
|
||||
<el-select v-model="yuhunTarget" @change="handleYuhunTargetChange">
|
||||
<el-option v-for="option in yuhunTargetOptions" :key="option.value" :label="t(option.label)" :value="option.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; width: 50%;">
|
||||
<el-form-item label="2号位主属性">
|
||||
<el-select multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
v-model="property.yuhun.property2">
|
||||
<el-option label="攻击加成" value="Attack"/>
|
||||
<el-option label="防御加成" value="Defense"/>
|
||||
<el-option label="生命加成" value="Health"/>
|
||||
<el-option label="速度" value="Speed"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="4号位主属性">
|
||||
<el-select multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
v-model="property.yuhun.property4">
|
||||
<el-option label="攻击加成" value="Attack"/>
|
||||
<el-option label="防御加成" value="Defense"/>
|
||||
<el-option label="生命加成" value="Health"/>
|
||||
<el-option label="效果命中" value="ControlHit"/>
|
||||
<el-option label="效果抵抗" value="ControlMiss"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="6号位主属性">
|
||||
<el-select multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
v-model="property.yuhun.property6">
|
||||
<el-option label="攻击加成" value="Attack"/>
|
||||
<el-option label="防御加成" value="Defense"/>
|
||||
<el-option label="生命加成" value="Health"/>
|
||||
<el-option label="暴击" value="Crit"/>
|
||||
<el-option label="暴击伤害" value="CritDamage"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 效果指标选项卡 -->
|
||||
<el-tab-pane label="效果指标" name="effect">
|
||||
<el-form-item label="伤害期望">
|
||||
<el-input-number v-model="property.expectedDamage" :min="0" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="生存能力">
|
||||
<el-slider v-model="property.survivalRate" :step="10" :marks="{0: '弱', 50: '中', 100: '强'}" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="输出偏向">
|
||||
<el-select v-model="property.damageType">
|
||||
<el-option label="普攻" value="normal"/>
|
||||
<el-option label="技能" value="skill"/>
|
||||
<el-option label="均衡" value="balanced"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-form-item label="额外描述">
|
||||
<el-input v-model="property.description" type="textarea"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="confirm">确认</el-button>
|
||||
<el-button @click="cancel">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { CirclePlus } from '@element-plus/icons-vue';
|
||||
import YuhunSelect from "@/components/flow/nodes/yys/YuhunSelect.vue";
|
||||
|
||||
// 获取当前的 i18n 实例
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
currentProperty: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
showPropertySelect: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['closePropertySelect', 'updateProperty']);
|
||||
|
||||
// 属性类型选项
|
||||
const propertyTypes = [
|
||||
{ label: '攻击', value: 'attack' },
|
||||
{ label: '生命', value: 'health' },
|
||||
{ label: '防御', value: 'defense' },
|
||||
{ label: '速度', value: 'speed' },
|
||||
{ label: '暴击率', value: 'crit' },
|
||||
{ label: '暴击伤害', value: 'critDmg' },
|
||||
{ label: '效果命中', value: 'effectHit' },
|
||||
{ label: '效果抵抗', value: 'effectResist' },
|
||||
];
|
||||
|
||||
// 标签页控制
|
||||
const activeTab = ref('basic');
|
||||
|
||||
// 御魂选择相关
|
||||
const showYuhunSelect = ref(false);
|
||||
const currentYuhun = ref({ name: '未选择御魂', avatar: '', type: '' });
|
||||
const yuhunIndex = ref(-1);
|
||||
const yuhunTarget = ref('1');
|
||||
|
||||
// 御魂目标选项
|
||||
const yuhunTargetOptions = [
|
||||
{ label: 'yuhun_target.fullName.0', value: '0' },
|
||||
{ label: 'yuhun_target.fullName.1', value: '1' },
|
||||
{ label: 'yuhun_target.fullName.2', value: '2' },
|
||||
{ label: 'yuhun_target.fullName.3', value: '3' },
|
||||
{ label: 'yuhun_target.fullName.4', value: '4' },
|
||||
{ label: 'yuhun_target.fullName.5', value: '5' },
|
||||
{ label: 'yuhun_target.fullName.6', value: '6' },
|
||||
{ label: 'yuhun_target.fullName.7', value: '7' },
|
||||
{ label: 'yuhun_target.fullName.8', value: '8' },
|
||||
{ label: 'yuhun_target.fullName.9', value: '9' },
|
||||
{ label: 'yuhun_target.fullName.10', value: '10' },
|
||||
{ label: 'yuhun_target.fullName.11', value: '11' },
|
||||
{ label: 'yuhun_target.fullName.12', value: '12' },
|
||||
];
|
||||
|
||||
// 属性数据对象
|
||||
const property = ref({
|
||||
// 基础属性
|
||||
type: 'attack',
|
||||
attackType: 'percentage',
|
||||
attackValue: 0,
|
||||
healthType: 'percentage',
|
||||
healthValue: 0,
|
||||
defenseType: 'percentage',
|
||||
defenseValue: 0,
|
||||
speedValue: 0,
|
||||
critRate: 0,
|
||||
critDmg: 0,
|
||||
effectHitValue: 0,
|
||||
effectResistValue: 0,
|
||||
priority: 'optional',
|
||||
|
||||
// 式神要求
|
||||
levelRequired: "40",
|
||||
skillRequiredMode: "all",
|
||||
skillRequired: ["5", "5", "5"],
|
||||
|
||||
// 御魂要求
|
||||
yuhun: {
|
||||
yuhunSetEffect: [],
|
||||
target: "1",
|
||||
property2: ["Attack"],
|
||||
property4: ["Attack"],
|
||||
property6: ["Crit", "CritDamage"],
|
||||
},
|
||||
|
||||
// 效果指标
|
||||
expectedDamage: 0,
|
||||
survivalRate: 50,
|
||||
damageType: "balanced",
|
||||
|
||||
// 附加信息
|
||||
description: '',
|
||||
});
|
||||
|
||||
const show = ref(false);
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.showPropertySelect, (newVal) => {
|
||||
show.value = newVal;
|
||||
});
|
||||
|
||||
watch(() => props.currentProperty, (newVal) => {
|
||||
if (newVal && Object.keys(newVal).length > 0) {
|
||||
// 如果传入了属性数据,则使用传入的数据
|
||||
property.value = { ...newVal };
|
||||
// 设置御魂目标
|
||||
if (newVal.yuhun && newVal.yuhun.target) {
|
||||
yuhunTarget.value = newVal.yuhun.target.toString();
|
||||
}
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
// 处理属性类型变更
|
||||
const handleTypeChange = (type) => {
|
||||
console.log('属性类型变更为:', type);
|
||||
};
|
||||
|
||||
// 处理技能要求变更
|
||||
const updateSkillRequired = (index, value) => {
|
||||
property.value.skillRequired[index] = value;
|
||||
};
|
||||
|
||||
// 处理御魂目标变更
|
||||
const handleYuhunTargetChange = (value) => {
|
||||
switch (value) {
|
||||
case "0": {
|
||||
property.value.yuhun.target = 0;
|
||||
property.value.yuhun.property2 = ["Attack", "Defense", "Health", "Speed"];
|
||||
property.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
property.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "1": {
|
||||
property.value.yuhun.target = 1;
|
||||
property.value.yuhun.property2 = ["Attack"];
|
||||
property.value.yuhun.property4 = ["Attack"];
|
||||
property.value.yuhun.property6 = ["Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "2": {
|
||||
property.value.yuhun.target = 2;
|
||||
property.value.yuhun.property2 = ["Speed"];
|
||||
property.value.yuhun.property4 = ["ControlHit"];
|
||||
property.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "3": {
|
||||
property.value.yuhun.target = 3;
|
||||
property.value.yuhun.property2 = ["Speed"];
|
||||
property.value.yuhun.property4 = ["ControlMiss"];
|
||||
property.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "4": {
|
||||
property.value.yuhun.target = 4;
|
||||
property.value.yuhun.property2 = ["Health"];
|
||||
property.value.yuhun.property4 = ["Health"];
|
||||
property.value.yuhun.property6 = ["Health"];
|
||||
break;
|
||||
}
|
||||
case "5": {
|
||||
property.value.yuhun.target = 5;
|
||||
property.value.yuhun.property2 = ["Attack"];
|
||||
property.value.yuhun.property4 = ["Attack"];
|
||||
property.value.yuhun.property6 = ["Attack"];
|
||||
break;
|
||||
}
|
||||
case "6": {
|
||||
property.value.yuhun.target = 6;
|
||||
property.value.yuhun.property2 = ["Defense"];
|
||||
property.value.yuhun.property4 = ["Defense"];
|
||||
property.value.yuhun.property6 = ["Defense"];
|
||||
break;
|
||||
}
|
||||
case "7": {
|
||||
property.value.yuhun.target = 7;
|
||||
property.value.yuhun.property2 = ["Speed"];
|
||||
property.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
property.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "8": {
|
||||
property.value.yuhun.target = 8;
|
||||
property.value.yuhun.property2 = ["Attack", "Defense", "Health", "Speed"];
|
||||
property.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
property.value.yuhun.property6 = ["Crit"];
|
||||
break;
|
||||
}
|
||||
case "9": {
|
||||
property.value.yuhun.target = 9;
|
||||
property.value.yuhun.property2 = ["Attack", "Defense", "Health", "Speed"];
|
||||
property.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
property.value.yuhun.property6 = ["CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "10": {
|
||||
property.value.yuhun.target = 10;
|
||||
property.value.yuhun.property2 = ["Speed"];
|
||||
property.value.yuhun.property4 = ["Health"];
|
||||
property.value.yuhun.property6 = ["Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "11": {
|
||||
property.value.yuhun.target = 11;
|
||||
property.value.yuhun.property2 = ["Speed"];
|
||||
property.value.yuhun.property4 = ["ControlHit", "ControlMiss"];
|
||||
property.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
case "12": {
|
||||
property.value.yuhun.target = 12;
|
||||
property.value.yuhun.property2 = ["Defense"];
|
||||
property.value.yuhun.property4 = ["Defense"];
|
||||
property.value.yuhun.property6 = ["Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 打开御魂选择
|
||||
const openYuhunSelect = (index) => {
|
||||
yuhunIndex.value = index;
|
||||
showYuhunSelect.value = true;
|
||||
};
|
||||
|
||||
// 关闭御魂选择
|
||||
const closeYuhunSelect = () => {
|
||||
showYuhunSelect.value = false;
|
||||
};
|
||||
|
||||
// 更新御魂选择
|
||||
const updateYuhunSelect = (yuhun, operator) => {
|
||||
showYuhunSelect.value = false;
|
||||
|
||||
if (operator === "Update") {
|
||||
if (yuhunIndex.value >= 0) {
|
||||
property.value.yuhun.yuhunSetEffect[yuhunIndex.value] = JSON.parse(JSON.stringify(yuhun));
|
||||
} else {
|
||||
property.value.yuhun.yuhunSetEffect.push(JSON.parse(JSON.stringify(yuhun)));
|
||||
}
|
||||
} else if (operator === "Remove") {
|
||||
if (yuhunIndex.value >= 0) {
|
||||
property.value.yuhun.yuhunSetEffect.splice(yuhunIndex.value, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 取消选择
|
||||
const cancel = () => {
|
||||
emit('closePropertySelect');
|
||||
resetData();
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const confirm = () => {
|
||||
// 复制当前属性数据
|
||||
const result = JSON.parse(JSON.stringify(property.value));
|
||||
|
||||
emit('updateProperty', result);
|
||||
resetData();
|
||||
};
|
||||
|
||||
// 重置数据
|
||||
const resetData = () => {
|
||||
yuhunTarget.value = '1';
|
||||
property.value = {
|
||||
// 基础属性
|
||||
type: 'attack',
|
||||
attackType: 'percentage',
|
||||
attackValue: 0,
|
||||
healthType: 'percentage',
|
||||
healthValue: 0,
|
||||
defenseType: 'percentage',
|
||||
defenseValue: 0,
|
||||
speedValue: 0,
|
||||
critRate: 0,
|
||||
critDmg: 0,
|
||||
effectHitValue: 0,
|
||||
effectResistValue: 0,
|
||||
priority: 'optional',
|
||||
|
||||
// 式神要求
|
||||
levelRequired: "40",
|
||||
skillRequiredMode: "all",
|
||||
skillRequired: ["5", "5", "5"],
|
||||
|
||||
// 御魂要求
|
||||
yuhun: {
|
||||
yuhunSetEffect: [],
|
||||
target: "1",
|
||||
property2: ["Attack"],
|
||||
property4: ["Attack"],
|
||||
property6: ["Crit", "CritDamage"],
|
||||
},
|
||||
|
||||
// 效果指标
|
||||
expectedDamage: 0,
|
||||
survivalRate: 50,
|
||||
damageType: "balanced",
|
||||
|
||||
// 附加信息
|
||||
description: '',
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.image {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
</style>
|
402
src/components/flow/nodes/yys/PropertySelectNode.vue
Normal file
402
src/components/flow/nodes/yys/PropertySelectNode.vue
Normal file
@ -0,0 +1,402 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { Handle, Position, useVueFlow } from '@vue-flow/core';
|
||||
import { NodeResizer } from '@vue-flow/node-resizer'
|
||||
import '@vue-flow/node-resizer/dist/style.css';
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
id: String,
|
||||
selected: Boolean
|
||||
});
|
||||
|
||||
// 获取Vue Flow的实例和节点更新方法
|
||||
const { findNode, updateNode } = useVueFlow();
|
||||
|
||||
// 属性信息保存在节点数据中
|
||||
const currentProperty = ref({
|
||||
type: '未选择',
|
||||
value: 0,
|
||||
valueType: '',
|
||||
priority: '可选',
|
||||
levelRequired: "40",
|
||||
skillRequiredMode: "all",
|
||||
skillRequired: ["5", "5", "5"],
|
||||
yuhun: {
|
||||
yuhunSetEffect: [],
|
||||
target: "1",
|
||||
property2: ["Attack"],
|
||||
property4: ["Attack"],
|
||||
property6: ["Crit", "CritDamage"],
|
||||
},
|
||||
expectedDamage: 0,
|
||||
survivalRate: 50,
|
||||
damageType: "balanced",
|
||||
description: '',
|
||||
});
|
||||
|
||||
// 节点尺寸
|
||||
const nodeWidth = ref(180);
|
||||
const nodeHeight = ref(180);
|
||||
|
||||
// 监听props.data的变化
|
||||
watch(() => props.data, (newData) => {
|
||||
if (newData && newData.property) {
|
||||
currentProperty.value = newData.property;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 更新属性信息的方法(将由App.vue调用)
|
||||
const updateNodeProperty = (property) => {
|
||||
currentProperty.value = property;
|
||||
};
|
||||
|
||||
// 备用方案:通过全局事件总线监听更新
|
||||
const handlePropertyUpdate = (event) => {
|
||||
const { nodeId, property } = event.detail;
|
||||
if (nodeId === props.id) {
|
||||
updateNodeProperty(property);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('PropertySelectNode mounted:', props.id);
|
||||
// 添加全局事件监听
|
||||
window.addEventListener('update-property', handlePropertyUpdate);
|
||||
|
||||
// 初始化时检查是否有数据
|
||||
if (props.data && props.data.property) {
|
||||
currentProperty.value = props.data.property;
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除全局事件监听器
|
||||
window.removeEventListener('update-property', handlePropertyUpdate);
|
||||
});
|
||||
|
||||
// 获取属性类型显示名称
|
||||
const getPropertyTypeName = () => {
|
||||
const typeMap = {
|
||||
'attack': '攻击',
|
||||
'health': '生命',
|
||||
'defense': '防御',
|
||||
'speed': '速度',
|
||||
'crit': '暴击率',
|
||||
'critDmg': '暴击伤害',
|
||||
'effectHit': '效果命中',
|
||||
'effectResist': '效果抵抗',
|
||||
'未选择': '未选择'
|
||||
};
|
||||
|
||||
return typeMap[currentProperty.value.type] || currentProperty.value.type;
|
||||
};
|
||||
|
||||
// 获取优先级显示名称
|
||||
const getPriorityName = () => {
|
||||
const priorityMap = {
|
||||
'required': '必须',
|
||||
'recommended': '推荐',
|
||||
'optional': '可选'
|
||||
};
|
||||
|
||||
return priorityMap[currentProperty.value.priority] || currentProperty.value.priority;
|
||||
};
|
||||
|
||||
// 获取格式化的属性值显示
|
||||
const getFormattedValue = () => {
|
||||
const type = currentProperty.value.type;
|
||||
|
||||
if (type === '未选择') return '';
|
||||
|
||||
// 根据属性类型获取相应的值
|
||||
let value = 0;
|
||||
let isPercentage = false;
|
||||
|
||||
switch (type) {
|
||||
case 'attack':
|
||||
value = currentProperty.value.attackValue || 0;
|
||||
isPercentage = currentProperty.value.attackType === 'percentage';
|
||||
break;
|
||||
case 'health':
|
||||
value = currentProperty.value.healthValue || 0;
|
||||
isPercentage = currentProperty.value.healthType === 'percentage';
|
||||
break;
|
||||
case 'defense':
|
||||
value = currentProperty.value.defenseValue || 0;
|
||||
isPercentage = currentProperty.value.defenseType === 'percentage';
|
||||
break;
|
||||
case 'speed':
|
||||
value = currentProperty.value.speedValue || 0;
|
||||
break;
|
||||
case 'crit':
|
||||
value = currentProperty.value.critRate || 0;
|
||||
isPercentage = true;
|
||||
break;
|
||||
case 'critDmg':
|
||||
value = currentProperty.value.critDmg || 0;
|
||||
isPercentage = true;
|
||||
break;
|
||||
case 'effectHit':
|
||||
value = currentProperty.value.effectHitValue || 0;
|
||||
isPercentage = true;
|
||||
break;
|
||||
case 'effectResist':
|
||||
value = currentProperty.value.effectResistValue || 0;
|
||||
isPercentage = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return isPercentage ? `${value}%` : value.toString();
|
||||
};
|
||||
|
||||
// 获取式神技能要求显示
|
||||
const getSkillRequirementText = () => {
|
||||
const mode = currentProperty.value.skillRequiredMode;
|
||||
if (mode === 'all') {
|
||||
return '技能: 全满';
|
||||
} else if (mode === '111') {
|
||||
return '技能: 111';
|
||||
} else if (mode === 'custom') {
|
||||
return `技能: ${currentProperty.value.skillRequired.join('/')}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// 获取御魂套装信息
|
||||
const getYuhunSetInfo = () => {
|
||||
const sets = currentProperty.value.yuhun?.yuhunSetEffect;
|
||||
if (!sets || sets.length === 0) return '';
|
||||
|
||||
return `御魂: ${sets.length}套`;
|
||||
};
|
||||
|
||||
// 获取显示的等级信息
|
||||
const getLevelText = () => {
|
||||
const level = currentProperty.value.levelRequired;
|
||||
return level === '0' ? '等级: 献祭' : `等级: ${level}`;
|
||||
};
|
||||
|
||||
// 处理调整大小
|
||||
const handleResize = (event, { width, height }) => {
|
||||
// 更新本地状态
|
||||
nodeWidth.value = width;
|
||||
nodeHeight.value = height;
|
||||
|
||||
// 更新Vue Flow中的节点
|
||||
const node = findNode(props.id);
|
||||
if (node) {
|
||||
const updatedNode = {
|
||||
...node,
|
||||
style: {
|
||||
...node.style,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`
|
||||
}
|
||||
};
|
||||
updateNode(props.id, updatedNode);
|
||||
}
|
||||
};
|
||||
|
||||
// 导出方法,使父组件可以调用
|
||||
defineExpose({
|
||||
updateNodeProperty
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="property-node" :class="[currentProperty.priority ? `priority-${currentProperty.priority}` : '']" :style="{ width: `${nodeWidth}px`, height: `${nodeHeight}px` }">
|
||||
<NodeResizer
|
||||
v-if="selected"
|
||||
:min-width="150"
|
||||
:min-height="150"
|
||||
:max-width="300"
|
||||
:max-height="300"
|
||||
/>
|
||||
|
||||
<!-- 输入连接点 -->
|
||||
<Handle type="target" position="left" :id="`${id}-target`" />
|
||||
|
||||
<div class="node-content">
|
||||
<div class="node-header">
|
||||
<div class="node-title">属性要求</div>
|
||||
</div>
|
||||
|
||||
<div class="node-body">
|
||||
<div class="property-main">
|
||||
<div class="property-type">{{ getPropertyTypeName() }}</div>
|
||||
<div v-if="currentProperty.type !== '未选择'" class="property-value">{{ getFormattedValue() }}</div>
|
||||
<div v-else class="property-placeholder">点击设置属性</div>
|
||||
</div>
|
||||
|
||||
<div class="property-details" v-if="currentProperty.type !== '未选择'">
|
||||
<div class="property-priority">优先级: {{ getPriorityName() }}</div>
|
||||
|
||||
<!-- 额外信息展示 -->
|
||||
<div class="property-extra-info" v-if="currentProperty.levelRequired">
|
||||
<div>{{ getLevelText() }}</div>
|
||||
<div>{{ getSkillRequirementText() }}</div>
|
||||
<div>{{ getYuhunSetInfo() }}</div>
|
||||
</div>
|
||||
|
||||
<div class="property-description" v-if="currentProperty.description">
|
||||
{{ currentProperty.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输出连接点 -->
|
||||
<Handle type="source" position="right" :id="`${id}-source`" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.node-content {
|
||||
background-color: white;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer) {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle) {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: #409EFF;
|
||||
border: 1px solid white;
|
||||
border-radius: 50%;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.top-left) {
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.top-right) {
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.bottom-left) {
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.bottom-right) {
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.priority-required {
|
||||
border: 2px solid #f56c6c;
|
||||
}
|
||||
|
||||
.priority-recommended {
|
||||
border: 2px solid #67c23a;
|
||||
}
|
||||
|
||||
.priority-optional {
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
padding: 8px 10px;
|
||||
background-color: #f0f7ff;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-body {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.property-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.property-type {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.property-value {
|
||||
font-size: 16px;
|
||||
color: #409eff;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.property-placeholder {
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
border: 1px dashed #c0c4cc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
margin: 8px 0;
|
||||
transition: width 0.2s, height 0.2s;
|
||||
}
|
||||
|
||||
.property-details {
|
||||
width: 100%;
|
||||
border-top: 1px dashed #ebeef5;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.property-priority {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.property-extra-info {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.property-extra-info > div {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.property-description {
|
||||
font-size: 11px;
|
||||
color: #606266;
|
||||
margin-top: 5px;
|
||||
border-top: 1px dashed #ebeef5;
|
||||
padding-top: 5px;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
627
src/components/flow/nodes/yys/ShikigamiGroup.vue
Normal file
627
src/components/flow/nodes/yys/ShikigamiGroup.vue
Normal file
@ -0,0 +1,627 @@
|
||||
<template>
|
||||
<ShikigamiSelect
|
||||
:showSelectShikigami="state.showSelectShikigami"
|
||||
:currentShikigami="state.currentShikigami"
|
||||
@closeSelectShikigami="closeSelectShikigami"
|
||||
@updateShikigami="updateShikigami"
|
||||
/>
|
||||
|
||||
<ShikigamiProperty
|
||||
:showProperty="state.showProperty"
|
||||
:currentShikigami="state.currentShikigami"
|
||||
@closeProperty="closeProperty"
|
||||
@updateProperty="updateProperty"
|
||||
/>
|
||||
|
||||
<div class="group-item">
|
||||
<div class="group-header">
|
||||
<div class="group-opt" data-html2canvas-ignore="true">
|
||||
<div class="opt-left">
|
||||
<el-button type="primary" icon="CopyDocument" @click="copy(group.shortDescription)">{{ t('Copy') }}
|
||||
</el-button>
|
||||
<el-button type="primary" icon="Document" @click="paste(groupIndex,'shortDescription')">{{
|
||||
t('Paste')
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="opt-right">
|
||||
<el-button class="drag-handle" type="primary" icon="Rank" circle></el-button>
|
||||
<el-button type="primary" @click="addGroup">{{ t('AddGroup') }}</el-button>
|
||||
<el-button type="primary" @click="addGroupElement(groupIndex)">{{ t('AddShikigami') }}</el-button>
|
||||
<el-button type="danger" icon="Delete" circle @click="removeGroup(groupIndex)"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
<QuillEditor ref="shortDescriptionEditor" v-model:content="group.shortDescription" contentType="html" theme="snow"
|
||||
:toolbar="toolbarOptions"/>
|
||||
</div>
|
||||
<div class="group-body">
|
||||
<draggable :list="props.group.groupInfo" item-key="name" class="body-content">
|
||||
<template #item="{element : position, index:positionIndex}">
|
||||
<div>
|
||||
<el-col>
|
||||
<el-card class="group-card" shadow="never">
|
||||
<div class="opt-btn" data-html2canvas-ignore="true">
|
||||
<!-- Add delete button here -->
|
||||
<el-button type="danger" icon="Delete" circle @click="removeGroupElement(positionIndex)"/>
|
||||
<!-- <el-button type="primary" icon="Plus" circle @click="addGroupElement(groupIndex)"/> -->
|
||||
</div>
|
||||
<div class="avatar-container">
|
||||
<!-- 头像图片 -->
|
||||
<img :src="position.avatar || '/assets/Shikigami/default.png'"
|
||||
style="cursor: pointer; vertical-align: bottom;"
|
||||
class="avatar-image"
|
||||
@click="editShikigami(positionIndex)"/>
|
||||
|
||||
<!-- 文字图层 -->
|
||||
<span v-if="position.properties">{{
|
||||
position.properties.levelRequired
|
||||
}}级 {{ position.properties.skillRequired.join('') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="property-wrap">
|
||||
<div style="display: flex; justify-content: center;" data-html2canvas-ignore="true">
|
||||
<span>{{ position.name || "" }}</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: center;" class="bottom" data-html2canvas-ignore="true">
|
||||
<el-button @click="editProperty(groupIndex,positionIndex)">{{ t('editProperties') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="position.properties">
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<span
|
||||
style="width: 100px;height: 50px;background-color: #666;
|
||||
border-radius: 5px; margin-right: 5px; color: white;
|
||||
text-align: center; white-space: pre-wrap; display: flex; align-items: center; justify-content: center; flex-direction: column ">
|
||||
{{ getYuhunNames(position.properties.yuhun.yuhunSetEffect) }}<br/>{{
|
||||
t('yuhun_target.shortName.' + position.properties.yuhun.target)
|
||||
}}·{{ getYuhunPropertyNames(position.properties.yuhun) }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
style="display: inline-block; width: 100px; height: 30px; border-radius: 5px; margin-right: 5px; color: red; text-align: center; white-space: pre-wrap; display: flex; align-items: center; justify-content: center; flex-direction: column ">
|
||||
{{ position.properties.desc }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<div class="group-footer">
|
||||
<div class="group-opt" data-html2canvas-ignore="true">
|
||||
<div class="opt-left">
|
||||
<el-button type="primary" icon="CopyDocument" @click="copy(group.details)">{{ t('Copy') }}</el-button>
|
||||
<el-button type="primary" icon="Document" @click="paste(groupIndex,'details')">{{
|
||||
t('Paste')
|
||||
}}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<QuillEditor ref="detailsEditor" v-model:content="group.details" contentType="html" theme="snow"
|
||||
:toolbar="toolbarOptions"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider-horizontal"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive, toRefs, nextTick} from 'vue';
|
||||
import ShikigamiSelect from './ShikigamiSelect.vue';
|
||||
import ShikigamiProperty from './ShikigamiProperty.vue';
|
||||
import html2canvas from 'html2canvas';
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import { Quill, QuillEditor } from '@vueup/vue-quill'
|
||||
import '@vueup/vue-quill/dist/vue-quill.bubble.css'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css' // 引入样式文件
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import shikigamiData from '../../../../data/Shikigami.json';
|
||||
import _ from 'lodash';
|
||||
import {Action, ElMessage, ElMessageBox} from "element-plus";
|
||||
import { useGlobalMessage } from '../../../../ts/useGlobalMessage';
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
const props = defineProps<{
|
||||
groups:any[];
|
||||
group: any;
|
||||
groupIndex;
|
||||
}>();
|
||||
|
||||
const groupIndex = props.groupIndex
|
||||
|
||||
|
||||
// 定义响应式数据
|
||||
const state = reactive({
|
||||
showSelectShikigami: false,
|
||||
showProperty: false,
|
||||
groupIndex: 0,
|
||||
positionIndex: 0,
|
||||
currentShikigami: {}
|
||||
});
|
||||
|
||||
const clipboard = ref('');
|
||||
|
||||
const dialogTableVisible = ref(false)
|
||||
const {showMessage} = useGlobalMessage();
|
||||
|
||||
// 获取当前的 i18n 实例
|
||||
const {t} = useI18n()
|
||||
|
||||
// 定义 QuillEditor 的 ref
|
||||
const shortDescriptionEditor = ref<InstanceType<typeof QuillEditor>>()
|
||||
const detailsEditor = ref<InstanceType<typeof QuillEditor>>()
|
||||
|
||||
const removeGroupElement = async ( positionIndex: number) => {
|
||||
|
||||
|
||||
if (props.group.groupInfo.length === 1) {
|
||||
showMessage('warning', '无法删除');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除此元素吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
props.group.groupInfo.splice(positionIndex, 1);
|
||||
showMessage('success', '删除成功!');
|
||||
} catch (error) {
|
||||
showMessage('info', '已取消删除');
|
||||
}
|
||||
};
|
||||
|
||||
const removeGroup = async (groupIndex: number) => {
|
||||
if (props.groups.length === 1) {
|
||||
showMessage('warning', '无法删除最后一个队伍');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除此组吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
});
|
||||
props.groups.splice(groupIndex, 1);
|
||||
showMessage('success', '删除成功!');
|
||||
} catch (error) {
|
||||
showMessage('info', '已取消删除');
|
||||
}
|
||||
};
|
||||
const addGroup = () => {
|
||||
props.group.groupInfo.push({
|
||||
shortDescription: '',
|
||||
groupInfo: [{}, {}, {}, {}, {}],
|
||||
details: ''
|
||||
});
|
||||
|
||||
const container = document.getElementById('main-container');
|
||||
|
||||
|
||||
nextTick(() => {
|
||||
container.scrollTo({
|
||||
top: container.scrollHeight,
|
||||
behavior: 'smooth' // 可选平滑滚动
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
const addGroupElement = (groupIndex) => {
|
||||
props.group.groupInfo.push({});
|
||||
editShikigami(props.group.groupInfo.length - 1);
|
||||
};
|
||||
|
||||
const editShikigami = ( positionIndex) => {
|
||||
console.log("==== 选择式神 ===", groupIndex, positionIndex);
|
||||
state.showSelectShikigami = true;
|
||||
state.positionIndex = positionIndex;
|
||||
state.currentShikigami = props.group.groupInfo[positionIndex];
|
||||
};
|
||||
|
||||
const getYuhunNames = (yuhunSetEffect) => {
|
||||
const names = yuhunSetEffect.map(item => item.name).join('');
|
||||
if (names.length <= 6) {
|
||||
return names;
|
||||
} else {
|
||||
return yuhunSetEffect.map(item => item.shortName || item.name).join('');
|
||||
}
|
||||
}
|
||||
|
||||
const getYuhunPropertyNames = (yuhun) => {
|
||||
// 根据条件处理 yuhun.property2
|
||||
let property2Value, property4Value, property6Value;
|
||||
if (yuhun.property2.length >= 4) {
|
||||
property2Value = 'X';
|
||||
} else {
|
||||
property2Value = t('yuhun_property.shortName.' + yuhun.property2[0]);
|
||||
}
|
||||
|
||||
if (yuhun.property4.length >= 5) {
|
||||
property4Value = 'X';
|
||||
} else {
|
||||
property4Value = t('yuhun_property.shortName.' + yuhun.property4[0]);
|
||||
}
|
||||
|
||||
if (yuhun.property6.length >= 5) {
|
||||
property6Value = 'X';
|
||||
} else {
|
||||
property6Value = t('yuhun_property.shortName.' + yuhun.property6[0]);
|
||||
}
|
||||
// 构建 propertyNames 字符串
|
||||
const propertyNames =
|
||||
property2Value + property4Value + property6Value
|
||||
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
const copy = (str) => {
|
||||
clipboard.value = str
|
||||
}
|
||||
|
||||
const paste = (groupIndex, type) => {
|
||||
console.log("paste", groupIndex, type, clipboard.value)
|
||||
if ('shortDescription' == type)
|
||||
props.group.shortDescription = clipboard.value
|
||||
else if ('details' == type)
|
||||
props.group.details = clipboard.value
|
||||
}
|
||||
|
||||
const registerFonts = () => {
|
||||
const Font = Quill.import('attributors/style/font')
|
||||
Font.whitelist = ['SimSun', 'SimHei', 'KaiTi', 'FangSong', 'Microsoft YaHei', 'PingFang SC']
|
||||
Quill.register(Font, true)
|
||||
}
|
||||
|
||||
// 自定义字号注册
|
||||
const registerSizes = () => {
|
||||
const Size = Quill.import('attributors/style/size')
|
||||
Size.whitelist = ['12px', '14px', '16px', '18px', '21px', '29px', '32px', '34px']
|
||||
Quill.register(Size, true)
|
||||
}
|
||||
|
||||
// 执行注册
|
||||
registerFonts()
|
||||
registerSizes()
|
||||
|
||||
// 工具栏配置
|
||||
const toolbarOptions = ref([
|
||||
[{font: ['SimSun', 'SimHei', 'KaiTi', 'FangSong', 'Microsoft YaHei', 'PingFang SC']}],
|
||||
[{header: 1}, {header: 2}],
|
||||
[{size: ['12px', '14px', '16px', '18px', '21px', '29px', '32px', '34px']}],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{color: []}, {background: []}],
|
||||
// ['blockquote', 'code-block'],
|
||||
[{list: 'bullet'}, {list: 'ordered'}, {'list': 'check'}],
|
||||
|
||||
[{indent: '-1'}, {indent: '+1'}],
|
||||
[{align: []}],
|
||||
[{direction: 'rtl'}],
|
||||
// [{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
// ['link', 'image', 'video'],
|
||||
// ['clean']
|
||||
] as const)
|
||||
|
||||
const saveQuillDesc = async (): Promise<string> => {
|
||||
if (!shortDescriptionEditor.value) {
|
||||
throw new Error('Quill editor instance not found')
|
||||
}
|
||||
return shortDescriptionEditor.value.getHTML()
|
||||
}
|
||||
|
||||
// 保存方法
|
||||
const saveQuillDetail = async (): Promise<string> => {
|
||||
if (!detailsEditor.value) {
|
||||
throw new Error('Quill detailsEditor instance not found')
|
||||
}
|
||||
return detailsEditor.value.getHTML()
|
||||
}
|
||||
|
||||
const updateShikigami = (shikigami) => {
|
||||
|
||||
state.showSelectShikigami = false;
|
||||
const oldProperties = props.group.groupInfo[state.positionIndex].properties;
|
||||
props.group.groupInfo[state.positionIndex] = _.cloneDeep(shikigami);
|
||||
props.group.groupInfo[state.positionIndex].properties = oldProperties;
|
||||
};
|
||||
|
||||
const editProperty = (groupIndex, positionIndex) => {
|
||||
state.showProperty = true;
|
||||
state.groupIndex = groupIndex;
|
||||
state.positionIndex = positionIndex;
|
||||
state.currentShikigami = props.group.groupInfo[positionIndex];
|
||||
};
|
||||
|
||||
const closeProperty = () => {
|
||||
state.showProperty = false;
|
||||
state.currentShikigami = {};
|
||||
};
|
||||
|
||||
const updateProperty = (property) => {
|
||||
state.showProperty = false;
|
||||
state.currentShikigami = {};
|
||||
props.group.groupInfo[state.positionIndex].properties = _.cloneDeep(property);
|
||||
};
|
||||
|
||||
const closeSelectShikigami = () => {
|
||||
console.log("close select ====");
|
||||
state.showSelectShikigami = false;
|
||||
state.currentShikigami = {};
|
||||
};
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
saveQuillDesc,
|
||||
saveQuillDetail
|
||||
});
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.drag-handle {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.position-drag-handle {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 正方形容器 */
|
||||
.avatar-wrapper {
|
||||
width: 100px; /* 正方形边长 */
|
||||
height: 100px; /* 与宽度相同 */
|
||||
position: relative;
|
||||
overflow: hidden; /* 隐藏超出部分 */
|
||||
border-radius: 50%; /* 圆形裁剪 */
|
||||
//border: 2px solid #fff; /* 可选:添加边框 */ //box-shadow: 0 2px 8px rgba(0,0,0,0.1); /* 可选:添加阴影 */
|
||||
}
|
||||
|
||||
/* 图片样式 */
|
||||
.avatar-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* 关键属性:保持比例填充容器 */
|
||||
object-position: center; /* 居中显示 */
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.el-card {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.group-opt {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.group-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.group-body {
|
||||
padding: 20px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
|
||||
.body-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.group-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.avatar-container span {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(50%);
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
text-shadow: -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black, 1px 1px 0 black;
|
||||
white-space: nowrap;
|
||||
padding: 0 8px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.opt-btn {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.property-wrap {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* 当鼠标悬停在容器上时显示按钮 */
|
||||
.group-card:hover .opt-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.group-footer {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.ql-container {
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
|
||||
.ql-tooltip[data-mode="link"]::before {
|
||||
content: "链接地址:";
|
||||
}
|
||||
|
||||
.ql-tooltip[data-mode="video"]::before {
|
||||
content: "视频地址:";
|
||||
}
|
||||
|
||||
.ql-tooltip.ql-editing {
|
||||
a.ql-action::after {
|
||||
content: "保存";
|
||||
border-right: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker.ql-font {
|
||||
.ql-picker-label[data-value=SimSun]::before,
|
||||
.ql-picker-item[data-value=SimSun]::before {
|
||||
content: "宋体";
|
||||
font-family: SimSun;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value=SimHei]::before,
|
||||
.ql-picker-item[data-value=SimHei]::before {
|
||||
content: "黑体";
|
||||
font-family: SimHei;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value=KaiTi]::before,
|
||||
.ql-picker-item[data-value=KaiTi]::before {
|
||||
content: "楷体";
|
||||
font-family: KaiTi;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value=FangSong]::before,
|
||||
.ql-picker-item[data-value=FangSong]::before {
|
||||
content: "仿宋";
|
||||
font-family: FangSong;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="Microsoft YaHei"]::before,
|
||||
.ql-picker-item[data-value="Microsoft YaHei"]::before {
|
||||
content: "微软雅黑";
|
||||
font-family: 'Microsoft YaHei';
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="PingFang SC"]::before,
|
||||
.ql-picker-item[data-value="PingFang SC"]::before {
|
||||
content: "苹方";
|
||||
font-family: 'PingFang SC';
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker.ql-size {
|
||||
.ql-picker-label::before,
|
||||
.ql-picker-item::before {
|
||||
font-size: 14px !important;
|
||||
content: "五号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="12px"]::before {
|
||||
content: "小五" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="12px"]::before {
|
||||
font-size: 12px;
|
||||
content: "小五" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="16px"]::before {
|
||||
content: "小四" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="16px"]::before {
|
||||
font-size: 16px;
|
||||
content: "小四" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="18px"]::before {
|
||||
content: "四号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="18px"]::before {
|
||||
font-size: 18px;
|
||||
content: "四号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="21px"]::before {
|
||||
content: "三号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="21px"]::before {
|
||||
font-size: 21px;
|
||||
content: "三号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="24px"]::before {
|
||||
content: "小二" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="24px"]::before {
|
||||
font-size: 24px;
|
||||
content: "小二" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="29px"]::before {
|
||||
content: "二号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="29px"]::before {
|
||||
font-size: 29px;
|
||||
content: "二号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="32px"]::before {
|
||||
content: "小一" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="32px"]::before {
|
||||
font-size: 32px;
|
||||
content: "小一" !important;
|
||||
}
|
||||
|
||||
.ql-picker-label[data-value="34px"]::before {
|
||||
content: "一号" !important;
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value="34px"]::before {
|
||||
font-size: 34px;
|
||||
content: "一号" !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
376
src/components/flow/nodes/yys/ShikigamiProperty.vue
Normal file
376
src/components/flow/nodes/yys/ShikigamiProperty.vue
Normal file
@ -0,0 +1,376 @@
|
||||
<template>
|
||||
|
||||
<YuhunSelect
|
||||
:showYuhunSelect="showYuhunSelect"
|
||||
:currentShikigami="currentShikigami"
|
||||
@closeYuhunSelect="closeYuhunSelect"
|
||||
@updateYuhunSelect="updateYuhunSelect"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
:visable.sync="show"
|
||||
title="配置属性"
|
||||
@cancel="cancel"
|
||||
:before-close="cancel"
|
||||
>
|
||||
<span>当前选择式神:{{ current.name }}</span>
|
||||
<el-form :model="shikigami" label-width="120px">
|
||||
<el-form-item label="等级要求">
|
||||
<el-radio-group v-model="shikigami.levelRequired" class="ml-4">
|
||||
<el-radio value="40" size="large">40</el-radio>
|
||||
<el-radio value="0" size="large">献祭</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="技能要求">
|
||||
<el-radio-group v-model="shikigami.skillRequiredMode" class="ml-4">
|
||||
<el-radio value="all" size="large">全满</el-radio>
|
||||
<el-radio value="111" size="large">111</el-radio>
|
||||
<el-radio value="custom" size="large">自定义</el-radio>
|
||||
</el-radio-group>
|
||||
<div v-if="shikigami.skillRequiredMode === 'custom'" style="display: flex; flex-direction: row; width: 100%;">
|
||||
<el-select v-for="(value, key, index) in shikigami.skillRequired" :placeholder="value"
|
||||
style="margin-bottom: 10px;" @change="updateSkillRequired(key, $event)">
|
||||
<el-option label="*" value="X"/>
|
||||
<el-option label="1" value="1"/>
|
||||
<el-option label="2" value="2"/>
|
||||
<el-option label="3" value="3"/>
|
||||
<el-option label="4" value="4"/>
|
||||
<el-option label="5" value="5"/>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div style="display: flex; flex-direction: row; width: 100%;">
|
||||
<div style="display: flex; flex-direction: column; width: 50%;">
|
||||
<el-form-item label="御魂指定">
|
||||
<!-- <el-input v-model="shikigami.speed"/>-->
|
||||
</el-form-item>
|
||||
<el-form-item label="御魂套装">
|
||||
<img
|
||||
v-for="(effect, index) in shikigami.yuhun.yuhunSetEffect"
|
||||
:key="index"
|
||||
style="width: 50px; height: 50px;"
|
||||
:src="effect.avatar"
|
||||
class="image"
|
||||
@click="openYuhunSelect(index)"
|
||||
/>
|
||||
<!-- <img style="width: 50px;height: 50px" v-if="shikigami.yuhun.yuhunSetEffect.length>0"-->
|
||||
<!-- :src="shikigami.yuhun.yuhunSetEffect[0].avatar" class="image"/>-->
|
||||
<!-- <img style="width: 50px;height: 50px" v-if="shikigami.yuhun.yuhunSetEffect.length>1"-->
|
||||
<!-- :src="shikigami.yuhun.yuhunSetEffect[1].avatar" class="image"/>-->
|
||||
|
||||
<el-button type="primary" @click="openYuhunSelect(-1)">
|
||||
<el-icon :size="20">
|
||||
<CirclePlus/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('yuhunTarget')">
|
||||
<el-select v-model="yuhunTarget">
|
||||
<el-option v-for="option in yuhunTargetOptions" :key="option.value" :label="t(option.label)" :value="option.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="2号位主属性">
|
||||
<el-select multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
v-model="shikigami.yuhun.property2">
|
||||
<el-option label="攻击加成" value="Attack"/>
|
||||
<el-option label="防御加成" value="Defense"/>
|
||||
<el-option label="生命加成" value="Health"/>
|
||||
<el-option label="速度" value="Speed"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="4号位主属性">
|
||||
<el-select multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
v-model="shikigami.yuhun.property4">
|
||||
<el-option label="攻击加成" value="Attack"/>
|
||||
<el-option label="防御加成" value="Defense"/>
|
||||
<el-option label="生命加成" value="Health"/>
|
||||
<el-option label="效果命中" value="ControlHit"/>
|
||||
<el-option label="效果抵抗" value="ControlMiss"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="6号位主属性">
|
||||
<el-select multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2"
|
||||
v-model="shikigami.yuhun.property6">
|
||||
<el-option label="攻击加成" value="Attack"/>
|
||||
<el-option label="防御加成" value="Defense"/>
|
||||
<el-option label="生命加成" value="Health"/>
|
||||
<el-option label="暴击" value="Crit"/>
|
||||
<el-option label="暴击伤害" value="CritDamage"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; width: 50%;">
|
||||
<el-form-item label="高级定制">
|
||||
<!-- <el-input v-model="shikigami.speed"/>-->
|
||||
</el-form-item>
|
||||
<el-form-item label="属性限制">
|
||||
<!-- <el-input v-model="shikigami.speed"/>-->
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item label="额外描述">
|
||||
<el-input v-model="shikigami.desc" type="textarea"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="confirm">Confirm</el-button>
|
||||
<el-button @click="cancel">Cancel</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import propertyData from "../../../../data/property.json";
|
||||
import {ref, watch, computed} from 'vue'
|
||||
import ShikigamiSelect from "@/components/flow/nodes/yys/ShikigamiSelect.vue";
|
||||
import YuhunSelect from "@/components/flow/nodes/yys/YuhunSelect.vue";
|
||||
import {useI18n} from 'vue-i18n'
|
||||
// import YuhunSelect from "./YuhunSelect.vue";
|
||||
|
||||
|
||||
// 获取当前的 i18n 实例
|
||||
const {t} = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
currentShikigami: {
|
||||
type: Object,
|
||||
default: {},
|
||||
},
|
||||
showProperty: {
|
||||
type: Boolean,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['closeProperty', 'updateProperty'])
|
||||
|
||||
const showYuhunSelect = ref(false)
|
||||
const yuhunTarget = ref('1')
|
||||
const shikigami = ref({
|
||||
edit: false,
|
||||
yuhun: {
|
||||
yuhunSetEffect: [],
|
||||
target: "1",
|
||||
property2: ["Attack"],
|
||||
property4: ["Attack"],
|
||||
property6: ["Crit", "CritDamage"],
|
||||
},
|
||||
levelRequired: "40",
|
||||
speed: "",
|
||||
skillRequiredMode: "all",
|
||||
skillRequired: ["5", "5", "5"]
|
||||
})
|
||||
const yuhunIndex = ref(-1)
|
||||
const current = ref({})
|
||||
const show = ref(props.showProperty)
|
||||
const yuhunTargetOptions = [
|
||||
{ label: 'yuhun_target.fullName.0', value: '0' },
|
||||
{ label: 'yuhun_target.fullName.1', value: '1' },
|
||||
{ label: 'yuhun_target.fullName.2', value: '2' },
|
||||
{ label: 'yuhun_target.fullName.3', value: '3' },
|
||||
{ label: 'yuhun_target.fullName.4', value: '4' },
|
||||
{ label: 'yuhun_target.fullName.5', value: '5' },
|
||||
{ label: 'yuhun_target.fullName.6', value: '6' },
|
||||
{ label: 'yuhun_target.fullName.7', value: '7' },
|
||||
{ label: 'yuhun_target.fullName.8', value: '8' },
|
||||
{ label: 'yuhun_target.fullName.9', value: '9' },
|
||||
{ label: 'yuhun_target.fullName.10', value: '10' },
|
||||
{ label: 'yuhun_target.fullName.11', value: '11' },
|
||||
{ label: 'yuhun_target.fullName.12', value: '12' },
|
||||
]
|
||||
|
||||
|
||||
watch(() => props.currentShikigami, (newVal) => {
|
||||
if (newVal.properties != undefined && newVal.properties.edit == true) {
|
||||
shikigami.value = newVal.properties
|
||||
}
|
||||
console.log("ShikigamiProperty.vue" + current.value.name)
|
||||
current.value = newVal
|
||||
console.log("ShikigamiProperty.vue" + current.value.name)
|
||||
}, {deep: true})
|
||||
|
||||
watch(() => props.showProperty, (newVal) => {
|
||||
show.value = newVal;
|
||||
})
|
||||
|
||||
watch(() => shikigami.value.skillRequiredMode, (newVal) => {
|
||||
if(newVal == "all") {
|
||||
shikigami.value.skillRequired = ["5", "5", "5"]
|
||||
}
|
||||
else if (newVal == "111") {
|
||||
shikigami.value.skillRequired = ["1", "1", "1"]
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => yuhunTarget.value, (newVal) => {
|
||||
switch (newVal) {
|
||||
//<el-option label="伤害输出" value="1"/>
|
||||
case "0": {
|
||||
shikigami.value.yuhun.target = 0
|
||||
shikigami.value.yuhun.property2 = ["Attack", "Defense", "Health", "Speed"];;
|
||||
shikigami.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
shikigami.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="伤害输出" value="1"/>
|
||||
case "1": {
|
||||
shikigami.value.yuhun.target = 1
|
||||
shikigami.value.yuhun.property2 = ["Attack"];
|
||||
shikigami.value.yuhun.property4 = ["Attack"];
|
||||
shikigami.value.yuhun.property6 = ["Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="效果命中" value="2"/>
|
||||
case "2": {
|
||||
shikigami.value.yuhun.target = 2
|
||||
shikigami.value.yuhun.property2 = ["Speed"];
|
||||
shikigami.value.yuhun.property4 = ["ControlHit"];
|
||||
shikigami.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="效果抵抗" value="3"/>
|
||||
case "3": {
|
||||
shikigami.value.yuhun.target = 3
|
||||
shikigami.value.yuhun.property2 = ["Speed"];
|
||||
shikigami.value.yuhun.property4 = ["ControlMiss"];
|
||||
shikigami.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="生命" value="4"/>
|
||||
case "4": {
|
||||
shikigami.value.yuhun.target = 4
|
||||
shikigami.value.yuhun.property2 = ["Health"];
|
||||
shikigami.value.yuhun.property4 = ["Health"];
|
||||
shikigami.value.yuhun.property6 = ["Health"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="攻击" value="5"/>
|
||||
case "5": {
|
||||
shikigami.value.yuhun.target = 5
|
||||
shikigami.value.yuhun.property2 = ["Attack"];
|
||||
shikigami.value.yuhun.property4 = ["Attack"];
|
||||
shikigami.value.yuhun.property6 = ["Attack"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="防御" value="6"/>
|
||||
case "6": {
|
||||
shikigami.value.yuhun.target = 6
|
||||
shikigami.value.yuhun.property2 = ["Defense"];
|
||||
shikigami.value.yuhun.property4 = ["Defense"];
|
||||
shikigami.value.yuhun.property6 = ["Defense"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="速度" value="7"/>
|
||||
case "7": {
|
||||
shikigami.value.yuhun.target = 7
|
||||
shikigami.value.yuhun.property2 = ["Speed"];
|
||||
shikigami.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
shikigami.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="暴击" value="8"/>
|
||||
case "8": {
|
||||
shikigami.value.yuhun.target = 8
|
||||
shikigami.value.yuhun.property2 = ["Attack", "Defense", "Health", "Speed"];
|
||||
shikigami.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
shikigami.value.yuhun.property6 = ["Crit"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="暴击伤害" value="9"/>
|
||||
case "9": {
|
||||
shikigami.value.yuhun.target = 9
|
||||
shikigami.value.yuhun.property2 = ["Attack", "Defense", "Health", "Speed"];
|
||||
shikigami.value.yuhun.property4 = ["Attack", "Defense", "Health", "ControlHit", "ControlMiss"];
|
||||
shikigami.value.yuhun.property6 = ["CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="治疗量" value="10"/>
|
||||
case "10": {
|
||||
shikigami.value.yuhun.target = 10
|
||||
shikigami.value.yuhun.property2 = ["Speed"];
|
||||
shikigami.value.yuhun.property4 = ["Health"];
|
||||
shikigami.value.yuhun.property6 = ["Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="命抗双修" value="11"/>
|
||||
case "11": {
|
||||
shikigami.value.yuhun.target = 11
|
||||
shikigami.value.yuhun.property2 = ["Speed"];
|
||||
shikigami.value.yuhun.property4 = ["ControlHit", "ControlMiss"];
|
||||
shikigami.value.yuhun.property6 = ["Attack", "Defense", "Health", "Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
//<el-option label="防御输出" value="12"/>
|
||||
case "12": {
|
||||
shikigami.value.yuhun.target = 12
|
||||
shikigami.value.yuhun.property2 = ["Defense"];
|
||||
shikigami.value.yuhun.property4 = ["Defense"];
|
||||
shikigami.value.yuhun.property6 = ["Crit", "CritDamage"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, {deep: true}
|
||||
)
|
||||
|
||||
|
||||
const openYuhunSelect = (index) => {
|
||||
yuhunIndex.value = index
|
||||
showYuhunSelect.value = true
|
||||
}
|
||||
|
||||
const closeYuhunSelect = () => showYuhunSelect.value = false
|
||||
|
||||
|
||||
const updateYuhunSelect = (yuhun, operator) => {
|
||||
showYuhunSelect.value = false
|
||||
//Update
|
||||
if (operator == "Update") {
|
||||
if (yuhunIndex.value >= 0)
|
||||
shikigami.value.yuhun.yuhunSetEffect[yuhunIndex.value] = (JSON.parse(JSON.stringify(yuhun)))
|
||||
else
|
||||
shikigami.value.yuhun.yuhunSetEffect.push(JSON.parse(JSON.stringify(yuhun)))
|
||||
}
|
||||
//Delete
|
||||
else if (operator == "Remove") {
|
||||
if (yuhunIndex.value >= 0) {
|
||||
// 使用splice方法移除指定位置的御魂
|
||||
shikigami.value.yuhun.yuhunSetEffect.splice(yuhunIndex.value, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit('closeProperty')
|
||||
resetData()
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
shikigami.value.edit = true
|
||||
emit('updateProperty', shikigami.value);
|
||||
resetData()
|
||||
}
|
||||
|
||||
const resetData = () => {
|
||||
yuhunTarget.value = '1'
|
||||
shikigami.value = {
|
||||
edit: false,
|
||||
yuhun: {
|
||||
yuhunSetEffect: [],
|
||||
target: "1",
|
||||
property2: ["Attack"],
|
||||
property4: ["Attack"],
|
||||
property6: ["Crit", "CritDamage"],
|
||||
},
|
||||
levelRequired: "40",
|
||||
speed: "",
|
||||
skillRequiredMode: "all",
|
||||
skillRequired: ["5", "5", "5"]
|
||||
}
|
||||
}
|
||||
|
||||
const updateSkillRequired = (index, value) => {
|
||||
shikigami.value.skillRequired[index] = value;
|
||||
}
|
||||
</script>
|
130
src/components/flow/nodes/yys/ShikigamiSelect.vue
Normal file
130
src/components/flow/nodes/yys/ShikigamiSelect.vue
Normal file
@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
title="请选择式神"
|
||||
@close="cancel"
|
||||
:before-close="cancel"
|
||||
>
|
||||
<span>当前选择式神:{{ current.name }}</span>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<el-input
|
||||
placeholder="请输入内容"
|
||||
v-model="searchText"
|
||||
style="width: 200px; margin-right: 10px;"
|
||||
/>
|
||||
</div>
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
type="card"
|
||||
class="demo-tabs"
|
||||
@tab-click="handleClick"
|
||||
editable
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="(rarity, index) in rarityLevels"
|
||||
:key="index"
|
||||
:label="rarity.label"
|
||||
:name="rarity.name"
|
||||
>
|
||||
<div style="max-height: 600px; overflow-y: auto;">
|
||||
<el-space wrap size="large">
|
||||
<div style="display: flex;flex-direction: column;justify-content: center" v-for="i in filterShikigamiByRarityAndSearch(rarity.name,searchText)" :key="i.name">
|
||||
<el-button
|
||||
style="width: 100px; height: 100px;"
|
||||
@click.stop="confirm(i)"
|
||||
>
|
||||
<img :src="i.avatar" style="width: 99px; height: 99px;">
|
||||
</el-button>
|
||||
<span style="text-align: center; display: block;">{{i.name}}</span>
|
||||
</div>
|
||||
</el-space>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import shikigamiData from "../../../../data/Shikigami.json"
|
||||
|
||||
interface Shikigami {
|
||||
name: string
|
||||
avatar: string
|
||||
rarity: string
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
currentShikigami: {
|
||||
type: Object as () => Shikigami,
|
||||
default: () => ({ name: '' })
|
||||
},
|
||||
showSelectShikigami: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['closeSelectShikigami', 'updateShikigami'])
|
||||
|
||||
const searchText = ref('') // 新增搜索文本
|
||||
const activeName = ref('ALL')
|
||||
let current = ref({name:''})
|
||||
const show = ref(false)
|
||||
|
||||
const rarityLevels = [
|
||||
{ label: "全部", name: "ALL" },
|
||||
{ label: "SP", name: "SP" },
|
||||
{ label: "SSR", name: "SSR" },
|
||||
{ label: "SR", name: "SR" },
|
||||
{ label: "R", name: "R" },
|
||||
{ label: "N", name: "N" },
|
||||
{ label: "联动", name: "L" },
|
||||
{ label: "呱太", name: "G" },
|
||||
]
|
||||
|
||||
watch(() => props.showSelectShikigami, (newVal) => {
|
||||
show.value = newVal
|
||||
})
|
||||
|
||||
watch(() => props.currentShikigami, (newVal) => {
|
||||
console.log("ShikigamiSelect.vue" + current.value.name)
|
||||
current.value = newVal
|
||||
console.log("ShikigamiSelect.vue" + current.value.name)
|
||||
}, {deep: true})
|
||||
|
||||
|
||||
const handleClick = (tab: TabsPaneContext) => {
|
||||
console.log('Tab clicked:', tab)
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit('closeSelectShikigami')
|
||||
show.value = false
|
||||
}
|
||||
|
||||
const confirm = (shikigami: Shikigami) => {
|
||||
emit('updateShikigami', shikigami)
|
||||
searchText.value=''
|
||||
activeName.value='ALL'
|
||||
// cancel()
|
||||
}
|
||||
|
||||
|
||||
// 修改后的过滤函数
|
||||
const filterShikigamiByRarityAndSearch = (rarity: string, search: string) => {
|
||||
let filteredList = shikigamiData;
|
||||
if (rarity.toLowerCase() !== 'all') {
|
||||
filteredList = filteredList.filter(item =>
|
||||
item.rarity.toLowerCase() === rarity.toLowerCase()
|
||||
);
|
||||
}
|
||||
if (search.trim() !== '') {
|
||||
return filteredList.filter(item =>
|
||||
item.name.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}
|
||||
return filteredList;
|
||||
}
|
||||
</script>
|
202
src/components/flow/nodes/yys/ShikigamiSelectNode.vue
Normal file
202
src/components/flow/nodes/yys/ShikigamiSelectNode.vue
Normal file
@ -0,0 +1,202 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { Handle, Position, useVueFlow } from '@vue-flow/core';
|
||||
import { NodeResizer } from '@vue-flow/node-resizer';
|
||||
import '@vue-flow/node-resizer/dist/style.css';
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
id: String,
|
||||
selected: Boolean
|
||||
});
|
||||
|
||||
// 获取Vue Flow的实例和节点更新方法
|
||||
const { findNode, updateNode } = useVueFlow();
|
||||
|
||||
// 式神信息保存在节点数据中
|
||||
const currentShikigami = ref({ name: '未选择式神', avatar: '', rarity: '' });
|
||||
|
||||
// 节点尺寸
|
||||
const nodeWidth = ref(180);
|
||||
const nodeHeight = ref(180);
|
||||
|
||||
// 监听props.data的变化
|
||||
watch(() => props.data, (newData) => {
|
||||
if (newData && newData.shikigami) {
|
||||
currentShikigami.value = newData.shikigami;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 更新式神信息的方法(将由App.vue调用)
|
||||
const updateNodeShikigami = (shikigami) => {
|
||||
currentShikigami.value = shikigami;
|
||||
};
|
||||
|
||||
// 备用方案:通过全局事件总线监听更新
|
||||
const handleShikigamiUpdate = (event) => {
|
||||
const { nodeId, shikigami } = event.detail;
|
||||
if (nodeId === props.id) {
|
||||
updateNodeShikigami(shikigami);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('ShikigamiSelectNode mounted:', props.id);
|
||||
// 添加全局事件监听
|
||||
window.addEventListener('update-shikigami', handleShikigamiUpdate);
|
||||
|
||||
// 初始化时检查是否有数据
|
||||
if (props.data && props.data.shikigami) {
|
||||
currentShikigami.value = props.data.shikigami;
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除全局事件监听器
|
||||
window.removeEventListener('update-shikigami', handleShikigamiUpdate);
|
||||
});
|
||||
|
||||
// 导出方法,使父组件可以调用
|
||||
defineExpose({
|
||||
updateNodeShikigami
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="shikigami-node" :style="{ width: `${nodeWidth}px`, height: `${nodeHeight}px` }">
|
||||
<NodeResizer
|
||||
v-if="selected"
|
||||
:min-width="150"
|
||||
:min-height="150"
|
||||
:max-width="300"
|
||||
:max-height="300"
|
||||
/>
|
||||
|
||||
<!-- 输入连接点 -->
|
||||
<Handle type="target" position="left" :id="`${id}-target`" />
|
||||
|
||||
<div class="node-content">
|
||||
<div class="node-header">
|
||||
<div class="node-title">式神选择</div>
|
||||
</div>
|
||||
|
||||
<div class="node-body">
|
||||
<div v-if="currentShikigami.avatar" class="shikigami-avatar">
|
||||
<img :src="currentShikigami.avatar" alt="式神头像" />
|
||||
</div>
|
||||
<div v-else class="shikigami-placeholder">
|
||||
点击选择式神
|
||||
</div>
|
||||
<div class="shikigami-name">{{ currentShikigami.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输出连接点 -->
|
||||
<Handle type="source" position="right" :id="`${id}-source`" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.node-content {
|
||||
background-color: white;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer) {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle) {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: #409EFF;
|
||||
border: 1px solid white;
|
||||
border-radius: 50%;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.top-left) {
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.top-right) {
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.bottom-left) {
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.bottom-right) {
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
padding: 8px 10px;
|
||||
background-color: #ecf5ff;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-body {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.shikigami-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 8px;
|
||||
transition: width 0.2s, height 0.2s;
|
||||
}
|
||||
|
||||
.shikigami-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.shikigami-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 1px dashed #c0c4cc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
transition: width 0.2s, height 0.2s;
|
||||
}
|
||||
|
||||
.shikigami-name {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
129
src/components/flow/nodes/yys/YuhunSelect.vue
Normal file
129
src/components/flow/nodes/yys/YuhunSelect.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
title="请选择御魂"
|
||||
@close="cancel"
|
||||
:before-close="cancel"
|
||||
>
|
||||
<span>当前选择御魂:{{ current.name }}</span>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<el-input
|
||||
placeholder="请输入内容"
|
||||
v-model="searchText"
|
||||
style="width: 200px; margin-right: 10px;"
|
||||
/>
|
||||
</div>
|
||||
<el-tabs
|
||||
v-model="activeName"
|
||||
type="card"
|
||||
class="demo-tabs"
|
||||
@tab-click="handleClick"
|
||||
editable
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="(type, index) in yuhunTypes"
|
||||
:key="index"
|
||||
:label="type.label"
|
||||
:name="type.name"
|
||||
>
|
||||
<div style="max-height: 600px; overflow-y: auto;">
|
||||
<el-space wrap size="large">
|
||||
<div style="display: flex;flex-direction: column;justify-content: center" v-for="i in filterYuhunByTypeAndSearch(type.name, searchText)" :key="i.name">
|
||||
<el-button
|
||||
style="width: 100px; height: 100px;"
|
||||
@click.stop="confirm(i)"
|
||||
>
|
||||
<img :src="i.avatar" style="width: 99px; height: 99px;">
|
||||
</el-button>
|
||||
<span style="text-align: center; display: block;">{{i.name}}</span>
|
||||
</div>
|
||||
</el-space>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import type { TabsPaneContext } from 'element-plus'
|
||||
import yuhunData from "../../../../data/Yuhun.json"
|
||||
|
||||
interface Yuhun {
|
||||
name: string
|
||||
shortName?: string
|
||||
type: string
|
||||
avatar: string
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
currentYuhun: {
|
||||
type: Object as () => Yuhun,
|
||||
default: () => ({ name: '' })
|
||||
},
|
||||
showSelectYuhun: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['closeSelectYuhun', 'updateYuhun'])
|
||||
|
||||
const searchText = ref('') // 搜索文本
|
||||
const activeName = ref('ALL')
|
||||
let current = ref({name:''})
|
||||
const show = ref(false)
|
||||
|
||||
const yuhunTypes = [
|
||||
{ label: "全部", name: "ALL" },
|
||||
{ label: "攻击类", name: "attack" },
|
||||
{ label: "暴击类", name: "Crit" },
|
||||
{ label: "生命类", name: "Health" },
|
||||
{ label: "防御类", name: "Defense" },
|
||||
{ label: "效果命中", name: "Effect" },
|
||||
{ label: "效果抵抗", name: "EffectResist" },
|
||||
{ label: "特殊类", name: "Special" }
|
||||
]
|
||||
|
||||
watch(() => props.showSelectYuhun, (newVal) => {
|
||||
show.value = newVal
|
||||
})
|
||||
|
||||
watch(() => props.currentYuhun, (newVal) => {
|
||||
console.log("YuhunSelect.vue" + current.value.name)
|
||||
current.value = newVal
|
||||
console.log("YuhunSelect.vue" + current.value.name)
|
||||
}, {deep: true})
|
||||
|
||||
const handleClick = (tab: TabsPaneContext) => {
|
||||
console.log('Tab clicked:', tab)
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
emit('closeSelectYuhun')
|
||||
show.value = false
|
||||
}
|
||||
|
||||
const confirm = (yuhun: Yuhun) => {
|
||||
emit('updateYuhun', yuhun)
|
||||
searchText.value=''
|
||||
activeName.value='ALL'
|
||||
// cancel()
|
||||
}
|
||||
|
||||
// 过滤函数
|
||||
const filterYuhunByTypeAndSearch = (type: string, search: string) => {
|
||||
let filteredList = yuhunData;
|
||||
if (type.toLowerCase() !== 'all') {
|
||||
filteredList = filteredList.filter(item =>
|
||||
item.type.toLowerCase() === type.toLowerCase()
|
||||
);
|
||||
}
|
||||
if (search.trim() !== '') {
|
||||
return filteredList.filter(item =>
|
||||
item.name.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}
|
||||
return filteredList;
|
||||
}
|
||||
</script>
|
230
src/components/flow/nodes/yys/YuhunSelectNode.vue
Normal file
230
src/components/flow/nodes/yys/YuhunSelectNode.vue
Normal file
@ -0,0 +1,230 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { Handle, Position, useVueFlow } from '@vue-flow/core';
|
||||
import { NodeResizer } from '@vue-flow/node-resizer'
|
||||
import '@vue-flow/node-resizer/dist/style.css';
|
||||
|
||||
const props = defineProps({
|
||||
data: Object,
|
||||
id: String,
|
||||
selected: Boolean
|
||||
});
|
||||
|
||||
// 获取Vue Flow的实例和节点更新方法
|
||||
const { findNode, updateNode } = useVueFlow();
|
||||
|
||||
// 御魂信息保存在节点数据中
|
||||
const currentYuhun = ref({ name: '未选择御魂', avatar: '', type: '' });
|
||||
|
||||
// 节点尺寸
|
||||
const nodeWidth = ref(180);
|
||||
const nodeHeight = ref(180);
|
||||
|
||||
// 监听props.data的变化
|
||||
watch(() => props.data, (newData) => {
|
||||
if (newData && newData.yuhun) {
|
||||
currentYuhun.value = newData.yuhun;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 更新御魂信息的方法(将由App.vue调用)
|
||||
const updateNodeYuhun = (yuhun) => {
|
||||
currentYuhun.value = yuhun;
|
||||
};
|
||||
|
||||
// 备用方案:通过全局事件总线监听更新
|
||||
const handleYuhunUpdate = (event) => {
|
||||
const { nodeId, yuhun } = event.detail;
|
||||
if (nodeId === props.id) {
|
||||
updateNodeYuhun(yuhun);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理调整大小
|
||||
const handleResize = (event, { width, height }) => {
|
||||
// 更新本地状态
|
||||
nodeWidth.value = width;
|
||||
nodeHeight.value = height;
|
||||
|
||||
// 更新Vue Flow中的节点
|
||||
const node = findNode(props.id);
|
||||
if (node) {
|
||||
const updatedNode = {
|
||||
...node,
|
||||
style: {
|
||||
...node.style,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`
|
||||
}
|
||||
};
|
||||
updateNode(props.id, updatedNode);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('YuhunSelectNode mounted:', props.id);
|
||||
// 添加全局事件监听
|
||||
window.addEventListener('update-yuhun', handleYuhunUpdate);
|
||||
|
||||
// 初始化时检查是否有数据
|
||||
if (props.data && props.data.yuhun) {
|
||||
currentYuhun.value = props.data.yuhun;
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除全局事件监听器
|
||||
window.removeEventListener('update-yuhun', handleYuhunUpdate);
|
||||
});
|
||||
|
||||
// 导出方法,使父组件可以调用
|
||||
defineExpose({
|
||||
updateNodeYuhun
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="yuhun-node" :style="{ width: `${nodeWidth}px`, height: `${nodeHeight}px` }">
|
||||
<NodeResizer
|
||||
v-if="selected"
|
||||
:min-width="150"
|
||||
:min-height="150"
|
||||
:max-width="300"
|
||||
:max-height="300"
|
||||
/>
|
||||
|
||||
<!-- 输入连接点 -->
|
||||
<Handle type="target" position="left" :id="`${id}-target`" />
|
||||
|
||||
<div class="node-content">
|
||||
<div class="node-header">
|
||||
<div class="node-title">御魂选择</div>
|
||||
</div>
|
||||
|
||||
<div class="node-body">
|
||||
<div v-if="currentYuhun.avatar" class="yuhun-avatar">
|
||||
<img :src="currentYuhun.avatar" alt="御魂图片" />
|
||||
</div>
|
||||
<div v-else class="yuhun-placeholder">
|
||||
点击选择御魂
|
||||
</div>
|
||||
<div class="yuhun-name">{{ currentYuhun.name }}</div>
|
||||
<div v-if="currentYuhun.type" class="yuhun-type">{{ currentYuhun.type }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 输出连接点 -->
|
||||
<Handle type="source" position="right" :id="`${id}-source`" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.node-content {
|
||||
background-color: white;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer) {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle) {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: #409EFF;
|
||||
border: 1px solid white;
|
||||
border-radius: 50%;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.top-left) {
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.top-right) {
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.bottom-left) {
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
:deep(.vue-flow__node-resizer-handle.bottom-right) {
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
.node-header {
|
||||
padding: 8px 10px;
|
||||
background-color: #f0f7ff;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.node-body {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.yuhun-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 8px;
|
||||
transition: width 0.2s, height 0.2s;
|
||||
}
|
||||
|
||||
.yuhun-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.yuhun-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border: 1px dashed #c0c4cc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
transition: width 0.2s, height 0.2s;
|
||||
}
|
||||
|
||||
.yuhun-name {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.yuhun-type {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 3px;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user