10 changed files with 383 additions and 164 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> |
|||
@ -1,49 +0,0 @@ |
|||
import type { IMeta } from '@/core/base/IMeta.ts' |
|||
|
|||
export default [ |
|||
{ field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true, category: 'basic' }, |
|||
{ field: 'name', editor: 'TextInput', label: '名称', category: 'basic' }, |
|||
{ field: 'dt.label', editor: 'TextInput', label: '标签', category: 'basic' }, |
|||
{ editor: 'TransformEditor', category: 'basic' }, |
|||
{ field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' }, |
|||
{ editor: '-', category: 'basic' }, |
|||
|
|||
{ field: 'dt.chargerWidth', editor: 'NumberInput', label: '充电桩宽度', category: 'basic' }, |
|||
{ field: 'dt.chargerDepth', editor: 'NumberInput', label: '充电桩深度', category: 'basic' }, |
|||
/** |
|||
* dt.bays 5列3层货架示例 |
|||
* { |
|||
* dt: { |
|||
* rackDepth: 1.1, // 货架深度
|
|||
* levelCount: 3, // 总层数
|
|||
* bayCount: 5, // 总列数
|
|||
* hideFloor: false, // 隐藏底板
|
|||
* extendColumns: true, // 扩展挡板
|
|||
* columnSpacing: 1, // 支脚跨越
|
|||
* bays: [ // 每列的配置
|
|||
* { |
|||
* bayWidth: 1.6, // 列的宽度
|
|||
* levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度
|
|||
* }, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* ] |
|||
* } |
|||
* } |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
*/ |
|||
|
|||
|
|||
{ field: 'tf', editor: 'InOutCenterEditor', category: 'basic' }, |
|||
{ field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' }, |
|||
{ field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' }, |
|||
{ field: 'visible', editor: 'Switch', label: '可见', category: 'basic' } |
|||
] as IMeta |
|||
@ -0,0 +1,20 @@ |
|||
import type { PropertySetter } from "@/core/base/PropertyTypes.ts"; |
|||
import { basicFieldsSetter } from "@/editor/widgets/property/PropertyPanelConstant.ts"; |
|||
|
|||
const propertySetter: PropertySetter = { |
|||
flatten: { |
|||
fields: [ |
|||
...basicFieldsSetter, |
|||
{ |
|||
dataPath: 'dt.chargerWidth', label: '充电桩宽度', input: 'InputNumber', |
|||
inputProps: {}, |
|||
}, |
|||
{ |
|||
dataPath: 'dt.chargerDepth', label: '充电桩深度', input: 'InputNumber', |
|||
inputProps: {}, |
|||
}, |
|||
], |
|||
}, |
|||
}; |
|||
|
|||
export default propertySetter; |
|||
@ -1,15 +1,15 @@ |
|||
import { defineModule } from '@/core/manager/ModuleManager.ts' |
|||
import ChargerRenderer from './ChargerRenderer.ts' |
|||
import ChargerEntity from './ChargerEntity.ts' |
|||
import ChargerMeta from './ChargerMeta.ts' |
|||
import ChargerInteraction from './ChargerInteraction.ts' |
|||
import propertySetter from "@/modules/charger/ChargerPropertySetter.ts"; |
|||
|
|||
export const ITEM_TYPE_NAME = 'charger' |
|||
|
|||
export default defineModule({ |
|||
name: ITEM_TYPE_NAME, |
|||
renderer: new ChargerRenderer(ITEM_TYPE_NAME), |
|||
interaction: new ChargerInteraction(ITEM_TYPE_NAME), |
|||
meta: ChargerMeta, |
|||
entity: ChargerEntity |
|||
name: ITEM_TYPE_NAME, |
|||
renderer: new ChargerRenderer(ITEM_TYPE_NAME), |
|||
interaction: new ChargerInteraction(ITEM_TYPE_NAME), |
|||
setter: propertySetter, |
|||
entity: ChargerEntity, |
|||
}) |
|||
|
|||
@ -1,49 +0,0 @@ |
|||
import type { IMeta } from '@/core/base/IMeta.ts' |
|||
|
|||
export default [ |
|||
{ field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true, category: 'basic' }, |
|||
{ field: 'name', editor: 'TextInput', label: '名称', category: 'basic' }, |
|||
{ field: 'dt.label', editor: 'TextInput', label: '标签', category: 'basic' }, |
|||
{ editor: 'TransformEditor', category: 'basic' }, |
|||
{ field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' }, |
|||
{ editor: '-', category: 'basic' }, |
|||
|
|||
{ field: 'dt.clxWidth', editor: 'NumberInput', label: 'CLX宽度', category: 'basic' }, |
|||
{ field: 'dt.clxDepth', editor: 'NumberInput', label: 'CLX深度', category: 'basic' }, |
|||
/** |
|||
* dt.bays 5列3层货架示例 |
|||
* { |
|||
* dt: { |
|||
* rackDepth: 1.1, // 货架深度
|
|||
* levelCount: 3, // 总层数
|
|||
* bayCount: 5, // 总列数
|
|||
* hideFloor: false, // 隐藏底板
|
|||
* extendColumns: true, // 扩展挡板
|
|||
* columnSpacing: 1, // 支脚跨越
|
|||
* bays: [ // 每列的配置
|
|||
* { |
|||
* bayWidth: 1.6, // 列的宽度
|
|||
* levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度
|
|||
* }, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* ] |
|||
* } |
|||
* } |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
*/ |
|||
|
|||
|
|||
{ field: 'tf', editor: 'InOutCenterEditor', category: 'basic' }, |
|||
{ field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' }, |
|||
{ field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' }, |
|||
{ field: 'visible', editor: 'Switch', label: '可见', category: 'basic' } |
|||
] as IMeta |
|||
@ -0,0 +1,20 @@ |
|||
import type { PropertySetter } from "@/core/base/PropertyTypes.ts"; |
|||
import { basicFieldsSetter } from "@/editor/widgets/property/PropertyPanelConstant.ts"; |
|||
|
|||
const propertySetter: PropertySetter = { |
|||
flatten: { |
|||
fields: [ |
|||
...basicFieldsSetter, |
|||
{ |
|||
dataPath: 'dt.clxWidth', label: 'CLX宽度', input: 'InputNumber', |
|||
inputProps: {}, |
|||
}, |
|||
{ |
|||
dataPath: 'dt.clxDepth', label: 'CLX深度', input: 'InputNumber', |
|||
inputProps: {}, |
|||
}, |
|||
], |
|||
}, |
|||
}; |
|||
|
|||
export default propertySetter; |
|||
@ -1,15 +1,15 @@ |
|||
import { defineModule } from '@/core/manager/ModuleManager.ts' |
|||
import ClxRenderer from './ClxRenderer.ts' |
|||
import ClxEntity from './ClxEntity.ts' |
|||
import ClxMeta from './ClxMeta.ts' |
|||
import ClxInteraction from './ClxInteraction.ts' |
|||
import propertySetter from "@/modules/clx/ClxPropertySetter.ts"; |
|||
|
|||
export const ITEM_TYPE_NAME = 'clx' |
|||
|
|||
export default defineModule({ |
|||
name: ITEM_TYPE_NAME, |
|||
renderer: new ClxRenderer(ITEM_TYPE_NAME), |
|||
interaction: new ClxInteraction(ITEM_TYPE_NAME), |
|||
meta: ClxMeta, |
|||
entity: ClxEntity |
|||
name: ITEM_TYPE_NAME, |
|||
renderer: new ClxRenderer(ITEM_TYPE_NAME), |
|||
interaction: new ClxInteraction(ITEM_TYPE_NAME), |
|||
setter: propertySetter, |
|||
entity: ClxEntity |
|||
}) |
|||
|
|||
@ -1,49 +0,0 @@ |
|||
import type { IMeta } from '@/core/base/IMeta.ts' |
|||
|
|||
export default [ |
|||
{ field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true, category: 'basic' }, |
|||
{ field: 'name', editor: 'TextInput', label: '名称', category: 'basic' }, |
|||
{ field: 'dt.label', editor: 'TextInput', label: '标签', category: 'basic' }, |
|||
{ editor: 'TransformEditor', category: 'basic' }, |
|||
{ field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' }, |
|||
{ editor: '-', category: 'basic' }, |
|||
|
|||
{ field: 'dt.ptrWidth', editor: 'NumberInput', label: 'PTR宽度', category: 'basic' }, |
|||
{ field: 'dt.ptrDepth', editor: 'NumberInput', label: 'PTR深度', category: 'basic' }, |
|||
/** |
|||
* dt.bays 5列3层货架示例 |
|||
* { |
|||
* dt: { |
|||
* rackDepth: 1.1, // 货架深度
|
|||
* levelCount: 3, // 总层数
|
|||
* bayCount: 5, // 总列数
|
|||
* hideFloor: false, // 隐藏底板
|
|||
* extendColumns: true, // 扩展挡板
|
|||
* columnSpacing: 1, // 支脚跨越
|
|||
* bays: [ // 每列的配置
|
|||
* { |
|||
* bayWidth: 1.6, // 列的宽度
|
|||
* levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度
|
|||
* }, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, |
|||
* ] |
|||
* } |
|||
* } |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
* |
|||
*/ |
|||
|
|||
|
|||
{ field: 'tf', editor: 'InOutCenterEditor', category: 'basic' }, |
|||
{ field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' }, |
|||
{ field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' }, |
|||
{ field: 'visible', editor: 'Switch', label: '可见', category: 'basic' } |
|||
] as IMeta |
|||
Loading…
Reference in new issue