You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
7.8 KiB
282 lines
7.8 KiB
<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>
|
|
|