Browse Source
- 新增 InOutCenterEditor 组件,用于编辑输入、输出和中心端口 - 在 PropertyPanelConstant 中注册 InOutCenterEditor 组件 - 更新 TransformEditor 组件,注释掉调试日志 - 新增 flex 布局相关 CSS 类master
3 changed files with 331 additions and 5 deletions
@ -0,0 +1,282 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { computed, reactive } from "vue"; |
||||
|
import { ElIcon, ElRadioButton, ElRadioGroup, useFormItem } from "element-plus"; |
||||
|
import { ArrowDown, ArrowUp, Delete, EditPen } from "@element-plus/icons-vue"; |
||||
|
import { Typeof } from "@ease-forge/shared"; |
||||
|
|
||||
|
defineOptions({ |
||||
|
name: 'InOutCenterEditor', |
||||
|
}); |
||||
|
|
||||
|
// 组件事件定义 |
||||
|
const emit = defineEmits<{ |
||||
|
/** 更新内联表格数据 */ |
||||
|
"update:modelValue": [value: ItemJson["dt"]]; |
||||
|
}>(); |
||||
|
|
||||
|
// 定义 Props 类型 |
||||
|
interface InOutCenterEditorProps { |
||||
|
modelValue: ItemJson["dt"]; |
||||
|
} |
||||
|
|
||||
|
// 读取组件 props 属性 |
||||
|
const props = withDefaults(defineProps<InOutCenterEditorProps>(), {}); |
||||
|
|
||||
|
// 定义 State 类型 |
||||
|
interface InOutCenterEditorState { |
||||
|
portsType: "center" | "in" | "out"; |
||||
|
selectCenterIdx?: number; |
||||
|
selectInIdx?: number; |
||||
|
selectOutIdx?: number; |
||||
|
} |
||||
|
|
||||
|
// state 属性 |
||||
|
const state = reactive<InOutCenterEditorState>({ |
||||
|
portsType: "in", |
||||
|
}); |
||||
|
|
||||
|
// 定义 Data 类型 |
||||
|
interface InOutCenterEditorData { |
||||
|
} |
||||
|
|
||||
|
// 内部数据 |
||||
|
const data: InOutCenterEditorData = {}; |
||||
|
const { formItem } = useFormItem(); |
||||
|
const list = computed<Array<string>>(() => { |
||||
|
const portsType = state.portsType; |
||||
|
if (portsType === "center") return props.modelValue?.center ?? []; |
||||
|
if (portsType === "in") return props.modelValue?.in ?? []; |
||||
|
if (portsType === "out") return props.modelValue?.out ?? []; |
||||
|
return []; |
||||
|
}); |
||||
|
const selectIdx = computed<number | undefined>({ |
||||
|
set: newValue => { |
||||
|
const portsType = state.portsType; |
||||
|
if (portsType === "center") state.selectCenterIdx = newValue; |
||||
|
if (portsType === "in") state.selectInIdx = newValue; |
||||
|
if (portsType === "out") state.selectOutIdx = newValue; |
||||
|
}, |
||||
|
get: oldValue => { |
||||
|
const portsType = state.portsType; |
||||
|
if (portsType === "center") return state.selectCenterIdx; |
||||
|
if (portsType === "in") return state.selectInIdx; |
||||
|
if (portsType === "out") return state.selectOutIdx; |
||||
|
}, |
||||
|
}); |
||||
|
const canUpItem = computed(() => selectIdx.value > 0); |
||||
|
const canDownItem = computed(() => selectIdx.value < (list.value.length - 1)); |
||||
|
const canDeleteItem = computed(() => selectIdx.value < list.value.length); |
||||
|
|
||||
|
function setSelectIdx(idx: number) { |
||||
|
selectIdx.value = idx; |
||||
|
} |
||||
|
|
||||
|
function addItem() { |
||||
|
const [list, modelValue] = getListAndModelValue(); |
||||
|
list.push(`D_${Date.now()}`); |
||||
|
emit("update:modelValue", modelValue); |
||||
|
} |
||||
|
|
||||
|
function upItem() { |
||||
|
if (!canUpItem.value) return; |
||||
|
const idx = selectIdx.value; |
||||
|
if (Typeof.noValue(idx)) return; |
||||
|
if (idx <= 0) return; |
||||
|
const [list, modelValue] = getListAndModelValue(); |
||||
|
const idxUp = idx - 1; |
||||
|
const tmp = list[idxUp]; |
||||
|
list[idxUp] = list[idx]; |
||||
|
list[idx] = tmp; |
||||
|
emit("update:modelValue", modelValue); |
||||
|
selectIdx.value--; |
||||
|
} |
||||
|
|
||||
|
function downItem() { |
||||
|
if (!canDownItem.value) return; |
||||
|
const idx = selectIdx.value; |
||||
|
if (Typeof.noValue(idx)) return; |
||||
|
const [list, modelValue] = getListAndModelValue(); |
||||
|
if (idx >= (list.length - 1)) return; |
||||
|
const idxDown = idx + 1; |
||||
|
const tmp = list[idxDown]; |
||||
|
list[idxDown] = list[idx]; |
||||
|
list[idx] = tmp; |
||||
|
emit("update:modelValue", modelValue); |
||||
|
selectIdx.value++; |
||||
|
} |
||||
|
|
||||
|
function deleteItem() { |
||||
|
if (!canDeleteItem.value) return; |
||||
|
if (Typeof.noValue(selectIdx.value)) return; |
||||
|
const [list, modelValue] = getListAndModelValue(); |
||||
|
list.splice(selectIdx.value, 1); |
||||
|
emit("update:modelValue", modelValue); |
||||
|
if (selectIdx.value >= list.length) { |
||||
|
setSelectIdx(Math.max(0, selectIdx.value - 1)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getListAndModelValue() { |
||||
|
const modelValue = props.modelValue ?? {}; |
||||
|
const portsType = state.portsType; |
||||
|
let list: Array<string> = []; |
||||
|
if (portsType === "center") { |
||||
|
if (!modelValue.center) modelValue.center = []; |
||||
|
list = modelValue.center; |
||||
|
} else if (portsType === "in") { |
||||
|
if (!modelValue.in) modelValue.in = []; |
||||
|
list = modelValue.in; |
||||
|
} else if (portsType === "out") { |
||||
|
if (!modelValue.out) modelValue.out = []; |
||||
|
list = modelValue.out; |
||||
|
} |
||||
|
return [list, modelValue]; |
||||
|
} |
||||
|
|
||||
|
interface InOutCenterEditorExpose { |
||||
|
state: InOutCenterEditorState; |
||||
|
data: InOutCenterEditorData; |
||||
|
} |
||||
|
|
||||
|
const expose: InOutCenterEditorExpose = { |
||||
|
state, |
||||
|
data, |
||||
|
}; |
||||
|
// 定义组件公开内容 |
||||
|
defineExpose(expose); |
||||
|
|
||||
|
export type { |
||||
|
InOutCenterEditorProps, |
||||
|
InOutCenterEditorState, |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="flex-column-container in-out-center-editor"> |
||||
|
<ElRadioGroup class="flex-item-fixed flex-justify-content-center" v-model="state.portsType" size="small"> |
||||
|
<ElRadioButton label="Input Ports" value="in"/> |
||||
|
<ElRadioButton label="Central Ports" value="center"/> |
||||
|
<ElRadioButton label="Output Ports" value="out"/> |
||||
|
</ElRadioGroup> |
||||
|
<div class="flex-item-fill flex-row-container in-out-center-editor-data"> |
||||
|
<div class="flex-item-fixed in-out-center-editor-tools"> |
||||
|
<div class="tools-button-container"> |
||||
|
<ElIcon class="tools-button-icon" @click="addItem"> |
||||
|
<EditPen/> |
||||
|
</ElIcon> |
||||
|
<ElIcon :class="['tools-button-icon', { 'tools-button-disabled': !canUpItem }]" @click="upItem"> |
||||
|
<ArrowUp/> |
||||
|
</ElIcon> |
||||
|
<ElIcon :class="['tools-button-icon', { 'tools-button-disabled': !canDownItem }]" @click="downItem"> |
||||
|
<ArrowDown/> |
||||
|
</ElIcon> |
||||
|
<ElIcon :class="['tools-button-icon', { 'tools-button-disabled': !canDeleteItem }]" @click="deleteItem"> |
||||
|
<Delete/> |
||||
|
</ElIcon> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="flex-item-fill in-out-center-editor-data-list"> |
||||
|
<div |
||||
|
v-for="(item, idx) in list" |
||||
|
:class="[ |
||||
|
'list-item', |
||||
|
{ |
||||
|
'list-item-select': selectIdx === idx, |
||||
|
}, |
||||
|
]" |
||||
|
@click="setSelectIdx(idx)" |
||||
|
> |
||||
|
{{ item }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.in-out-center-editor { |
||||
|
width: 100%; |
||||
|
height: 160px; |
||||
|
} |
||||
|
|
||||
|
.in-out-center-editor-data { |
||||
|
margin-top: 6px; |
||||
|
border: 1px solid #dddddd; |
||||
|
} |
||||
|
|
||||
|
.in-out-center-editor-tools { |
||||
|
background-color: #f5f5f5; |
||||
|
border-right: 1px solid #dddddd; |
||||
|
width: 28px; |
||||
|
padding: 4px 0; |
||||
|
} |
||||
|
|
||||
|
.tools-button-container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex-wrap: nowrap; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
gap: 4px; |
||||
|
cursor: pointer; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.tools-button-container > .tools-button-icon { |
||||
|
color: #8c8c8c; |
||||
|
padding: 4px; |
||||
|
font-size: 22px; |
||||
|
border-radius: 2px; |
||||
|
} |
||||
|
|
||||
|
.tools-button-container > .tools-button-icon:hover { |
||||
|
color: #595959; |
||||
|
background-color: #d9d9d9; |
||||
|
} |
||||
|
|
||||
|
.tools-button-container > .tools-button-icon:active { |
||||
|
color: #434343; |
||||
|
background-color: #bfbfbf; |
||||
|
} |
||||
|
|
||||
|
.tools-button-container > .tools-button-disabled { |
||||
|
cursor: not-allowed; |
||||
|
} |
||||
|
|
||||
|
.tools-button-container > .tools-button-disabled.tools-button-icon, |
||||
|
.tools-button-container > .tools-button-disabled.tools-button-icon:hover, |
||||
|
.tools-button-container > .tools-button-disabled.tools-button-icon:active { |
||||
|
color: #d9d9d9; |
||||
|
background-color: unset; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.in-out-center-editor-data-list { |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.list-item { |
||||
|
padding: 4px 0 4px 2px; |
||||
|
cursor: pointer; |
||||
|
border-bottom: 1px solid #f0f0f0; |
||||
|
user-select: none; |
||||
|
color: #262626; |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
.list-item:hover { |
||||
|
background-color: #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.list-item:active { |
||||
|
background-color: #dddddd; |
||||
|
} |
||||
|
|
||||
|
.list-item.list-item-select, |
||||
|
.list-item.list-item-select:hover, |
||||
|
.list-item.list-item-select:active { |
||||
|
background-color: #d9d9d9; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue