终于把属性对接完了

This commit is contained in:
xu_yanfeng
2024-12-11 14:56:50 +08:00
parent 004b8f8241
commit b7dbec512a
7 changed files with 390 additions and 319 deletions

View File

@@ -0,0 +1,111 @@
<template>
<div class="property-engine">
<i class="icon iconfont" :class="getEngineTypeIcon()"></i>
<div class="type">{{ data.engineType }}</div>
<div class="name">{{ data.engineName }}</div>
<div class="place" @click="onPlaceInTree">
<i class="iconfont icon_place"></i>
</div>
</div>
</template>
<script lang="ts">
import ccui from "@xuyanfeng/cc-ui";
import { defineComponent, PropType, ref } from "vue";
import Bus, { BusMsg } from "../bus";
import { EngineData, NumberData, Property } from "../data";
export default defineComponent({
name: "property-engine",
components: {},
props: {
data: {
type: Object as PropType<EngineData>,
default: () => new EngineData(),
},
},
setup(props, context) {
return {
onPlaceInTree() {
Bus.emit(BusMsg.ShowPlace, props.data);
},
getEngineTypeIcon() {
console.log(props.data.engineType);
switch (props.data.engineType) {
case "cc_Sprite": {
return "icon_picture";
}
case "cc_Label": {
return "icon_text";
}
case "cc_Node": {
return "icon_node";
}
}
return "icon_unknown";
},
};
},
});
</script>
<style scoped lang="less">
@my-height: 20px;
.property-engine {
height: @my-height;
box-sizing: border-box;
flex: 1;
display: flex;
flex-direction: row;
border: solid #409eff 1px;
border-radius: 3px;
align-items: center;
align-content: center;
background-color: cornflowerblue;
height: @my-height;
align-items: center;
align-content: center;
display: flex;
flex-direction: row;
.icon {
font-size: 14px;
width: @my-height;
margin: 0 5px;
}
.type {
text-align: left;
flex: 1;
color: white;
font-size: 12px;
display: flex;
align-content: center;
height: @my-height;
padding: 0 5px;
background-color: rgb(48, 158, 0);
align-items: center;
}
.name {
font-size: 12px;
color: white;
flex: 1;
height: @my-height;
background-color: rgb(184, 157, 5);
display: flex;
padding: 0 5px;
align-items: center;
align-content: center;
}
.place {
padding: 0 5px;
cursor: pointer;
&:hover {
color: #6d6d6d;
}
&:active {
color: #000000;
}
}
}
</style>

View File

@@ -1,48 +1,19 @@
<template>
<div id="ui-prop">
<div class="normal-data" style="display: flex; flex-direction: row; align-items: center; min-height: 30px; margin: 0">
<div @mousedown="onPropNameMouseDown" class="key" @click="onClickFold" :style="{ cursor: isArrayOrObject() ? 'pointer' : '' }">
<i
class="data-arrow"
v-if="arrow"
:class="fold ? 'iconfont icon_arrow_right' : 'iconfont icon_arrow_down'"
:style="{
visibility: isArrayOrObject() ? 'visible' : 'hidden',
'margin-left': indent * 10 + 'px',
}"
>
</i>
<div class="text" ref="propText">
<!-- 使用CCProp -->
<!-- <el-popover
placement="top"
trigger="hover"
:disabled="!isShowTooltip()"
>
<div>{{ name }}</div>
<span>{{ name }}</span>
</el-popover> -->
</div>
</div>
<div class="value">
<div class="ui-prop">
<CCProp :name="name" :icon="icon" :head-width="headWidth" v-model:expand="expand" :arrow="value.isArrayOrObject()" :slide="value.isNumber()" :indent="indent * 10" @change-expand="onClickFold">
<div class="prop-value">
<div v-if="value.isInvalid()" class="invalid">
{{ value.data }}
</div>
<CCInput v-if="value.isString()" v-model="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCInput>
<CCInput v-if="value.isText()" type="textarea" :autosize="{ minRows: 3, maxRows: 5 }" placeholder="请输入内容" :disabled="value.readonly" @change="onChangeValue" v-model="value.data"> </CCInput>
<CCInputNumber v-if="value.isNumber()" style="width: 100%; text-align: left" v-model="value.data" :step="step" :disabled="value.readonly" @change="onChangeValue" controls-position="right"></CCInputNumber>
<div v-if="value.isVec2() || value.isVec3()" class="vec">
<ui-prop v-for="(vec, index) in value.data" :key="index" :arrow="false" :value="vec.value" :name="vec.name"> </ui-prop>
</div>
<CCSelect v-model="value.data" :disabled="value.readonly" :data="getEnumValues(value)" v-if="value.isEnum()" style="width: 100%" @change="onChangeValue"> </CCSelect>
<CCCheckBox v-model="value.data" v-if="value.isBool()" :disabled="value.readonly" @change="onChangeValue"> </CCCheckBox>
<div class="color" v-if="value.isColor()">
<CCColor style="position: absolute" :disabled="value.readonly" v-model="value.data" @change="onChangeValue"> </CCColor>
<div class="hex" :style="{ color: colorReverse(value.data) }">
{{ value.data }}
</div>
<CCInput v-if="value.isString()" v-model:value="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCInput>
<CCTextarea v-if="value.isText()" v-model:value="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCTextarea>
<CCInputNumber v-if="value.isNumber()" v-model:value="value.data" :step="step" :disabled="value.readonly" @change="onChangeValue"></CCInputNumber>
<div v-if="value.isVec2() || value.isVec3() || value.isVec4()" class="vec">
<UiProp v-for="(vec, index) in value.data" :icon="!!index" head-width="auto" :key="index" :arrow="false" :value="vec.value" :name="vec.name"> </UiProp>
</div>
<CCSelect v-if="value.isEnum()" v-model:value="value.data" :disabled="value.readonly" :data="getEnumValues(value)" @change="onChangeValue"> </CCSelect>
<CCCheckBox v-if="value.isBool()" v-model:value="value.data" :disabled="value.readonly" @change="onChangeValue"> </CCCheckBox>
<CCColor v-if="value.isColor()" :show-color-text="true" :disabled="value.readonly" v-model:color="value.data" @change="onChangeValue"> </CCColor>
<div v-if="value.isImage()" class="image-property">
<!-- TODO: 适配 -->
<div v-if="value.isImage() || true" placement="top" trigger="hover">
@@ -55,28 +26,14 @@
<CCButton @click="onShowValueInConsole">log</CCButton>
</div>
</div>
<div v-if="value.isEngine()" class="engine">
<div class="head">
<i class="icon" :class="getEngineTypeIcon()"></i>
<div class="type">{{ value["engineType"] }}</div>
</div>
<div class="name">{{ value["engineName"] }}</div>
<CCButton @click="onPlaceInTree" type="primary">
<i class="iconfont icon_place"></i>
</CCButton>
</div>
<div v-if="value.isObject() && fold" class="objectDesc">
{{ value.data }}
</div>
<div v-if="value.isArray()" class="array">Array({{ value.data.length }})</div>
<div class="slot" v-if="false">
<slot></slot>
</div>
<Engine v-if="value.isEngine()" v-model:data="(value as EngineData)"> </Engine>
<div v-if="value.isObject() && !expand" class="objectDesc">{{ value.data }}</div>
<div v-if="value.isArray()" class="array">Array[{{ value.data.length }}]</div>
</div>
</div>
<div v-if="isArrayOrObject()">
<div v-show="!fold && subData" style="display: flex; flex-direction: column">
<ui-prop v-for="(arr, index) in subData" :key="index" :indent="indent + 1" :value="arr.value" :name="getName(value.isArray(), arr)"> </ui-prop>
</CCProp>
<div v-if="value.isArrayOrObject()">
<div v-show="expand && subData">
<UiProp v-for="(arr, index) in subData" :key="index" :indent="indent + 1" :value="arr.value" :name="getName(value.isArray(), arr)"> </UiProp>
</div>
</div>
</div>
@@ -89,38 +46,33 @@ import { defineComponent, onMounted, onUnmounted, PropType, ref, toRaw, watch }
import { Msg } from "../../../core/types";
import Bus, { BusMsg } from "../bus";
import { connectBackground } from "../connectBackground";
import { DataType, EngineData, EnumData, Info, Property } from "../data";
const { CCInput, CCButton, CCInputNumber, CCSelect, CCCheckBox, CCColor } = ccui.components;
import { DataType, EngineData, EnumData, Info, NumberData, Property, StringData, TextData, Vec2Data, Vec3Data } from "../data";
import Engine from "./property-engine.vue";
const { CCInput, CCTextarea, CCProp, CCButton, CCInputNumber, CCSelect, CCCheckBox, CCColor } = ccui.components;
export default defineComponent({
name: "UiProp",
components: {
CCInput,
CCButton,
CCInputNumber,
CCSelect,
CCCheckBox,
CCColor,
},
name: "ui-prop",
components: { CCProp, Engine, CCTextarea, CCInput, CCButton, CCInputNumber, CCSelect, CCCheckBox, CCColor },
props: {
name: { type: String, default: "" },
indent: { type: Number, default: 0 },
arrow: { type: Boolean, default: true },
icon: { type: Boolean, default: true },
headWidth: { type: String, default: "120px" },
arrow: { type: Boolean, default: false },
step: { type: Number, default: 1 },
value: {
type: Object as PropType<Info | EngineData | EnumData>,
type: Object as PropType<Info | EngineData | EnumData | NumberData | StringData | TextData | Vec2Data | Vec3Data>,
default: () => {},
},
},
setup(props, { emit }) {
const { value, step } = props;
const fold = ref(true);
let clientX: number = 0;
const expand = ref(false);
onMounted(() => {
watchValue();
});
function watchValue() {
fold.value = true;
// fold.value = true;
if (value.isArray()) {
subData.value = value.data;
} else {
@@ -130,32 +82,10 @@ export default defineComponent({
watch(props.value, () => {
watchValue();
});
const subData = ref<Property[]>([]);
const propText = ref<HTMLDivElement>();
const subData = ref<Array<Property> | null>(null);
function _onMouseMove(event: MouseEvent) {
let x = event.clientX;
let calcStep = step || 0;
if (x > clientX) {
calcStep = Math.abs(calcStep);
} else {
calcStep = -Math.abs(calcStep);
}
emit("movestep", calcStep);
clientX = x;
}
function _onMouseUp(event: MouseEvent) {
document.removeEventListener("mousemove", _onMouseMove);
document.removeEventListener("mouseup", _onMouseUp);
document.removeEventListener("onselectstart", _onSelect);
}
function _onSelect() {
return false;
}
return {
fold,
propText,
expand,
subData,
getEnumValues(data: any): Option[] {
const value: EnumData = data;
@@ -168,56 +98,30 @@ export default defineComponent({
});
return ret;
},
isArrayOrObject() {
return value && (value.isArray() || value.isObject());
},
isImageValid() {
return !!value.data;
},
onPlaceInTree() {
Bus.emit(BusMsg.ShowPlace, value);
},
isShowTooltip() {
const el: HTMLDivElement = propText.value as HTMLDivElement;
if (el) {
if (el.scrollWidth > el.offsetWidth) {
// 出现了省略号
return true;
}
}
return false;
},
getEngineTypeIcon() {
switch ((value as EngineData).engineType) {
case "cc_Sprite": {
return "icon_picture";
}
case "cc_Label": {
return "icon_text";
}
case "cc_Node": {
return "icon_node";
}
}
return "icon_unknown";
},
getName(isArray: boolean, arr: Property) {
const type = arr.value.type;
if (isArray) {
return `[${arr.name}]`;
return arr.name;
} else {
return arr.name;
}
},
onClickFold() {
if (value.isObject() && fold && !subData) {
onClickFold(v: boolean) {
if (value.isArray()) {
return;
}
const s = toRaw(subData.value);
const e = toRaw(expand.value);
if (value.isObject() && s === null && e === true) {
// 请求object的item数据
Bus.emit(BusMsg.RequestObjectData, value, (info: Property[]) => {
fold.value = false;
Bus.emit(BusMsg.RequestObjectData, toRaw(value), (info: Property[]) => {
subData.value = info;
});
} else {
fold.value = !fold.value;
}
},
onShowValueInConsole() {
@@ -231,35 +135,93 @@ export default defineComponent({
},
onChangeValue() {
if (!value.readonly) {
connectBackground.postMessageToBackground(Msg.SetProperty, value);
connectBackground.postMessageToBackground(Msg.SetProperty, toRaw(value));
}
},
onPropNameMouseDown(event: MouseEvent) {
document.addEventListener("mousemove", _onMouseMove);
document.addEventListener("mouseup", _onMouseUp);
document.addEventListener("onselectstart", _onSelect);
},
colorReverse(OldColorValue: string) {
OldColorValue = "0x" + OldColorValue.replace(/#/g, "");
var str = "000000" + (0xffffff - parseInt(OldColorValue)).toString(16);
return "#" + str.substring(str.length - 6, str.length);
},
_onMouseMove,
_onMouseUp,
_onSelect,
};
},
});
</script>
<style scoped lang="less">
#ui-prop {
.ui-prop {
min-height: 30px;
margin: 1px 0;
display: flex;
flex-direction: column;
justify-content: center;
.prop-value {
flex: 3;
text-align: left;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: flex-start;
.invalid {
color: grey;
}
.objectDesc {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
font-size: 12px;
color: #d2d2d2;
}
.array {
display: flex;
flex-direction: column;
color: #d2d2d2;
font-size: 12px;
}
.vec {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
.ui-prop {
flex: 1;
.cc-prop {
overflow: hidden;
}
}
// #ui-prop:first-child {
// margin-left: 0;
// }
// #ui-prop:last-child {
// margin-right: 0;
// }
}
.array-object {
flex: 1;
max-width: 100%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
}
.image-property {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
height: 36px;
}
.slot {
display: flex;
width: 100%;
}
}
.normal-data {
margin: 0;
min-height: 30px;
@@ -278,13 +240,6 @@ export default defineComponent({
align-items: center;
min-width: 90px;
.data-arrow {
width: 20px;
height: 16px;
font-size: 16px;
cursor: pointer;
}
.text {
flex: 1;
white-space: nowrap;
@@ -301,147 +256,6 @@ export default defineComponent({
}
}
}
.value {
flex: 3;
text-align: left;
height: 100%;
overflow: hidden;
min-width: 400px;
.color {
position: relative;
height: 30px;
.hex {
line-height: 30px;
position: relative;
text-align: center;
user-select: none;
pointer-events: none;
}
}
.invalid {
color: grey;
}
.objectDesc {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
}
.array {
display: flex;
flex-direction: column;
color: red;
}
.engine {
display: flex;
flex-direction: row;
border: solid #409eff 1px;
border-radius: 5px;
align-items: center;
align-content: center;
.head {
background-color: cornflowerblue;
height: 28px;
align-items: center;
align-content: center;
display: flex;
flex-direction: row;
.icon {
font-size: 20px;
width: 20px;
margin-left: 5px;
}
.type {
display: flex;
align-content: center;
align-items: center;
margin: 0 5px;
}
}
.name {
flex: 1;
height: 28px;
padding-left: 5px;
background-color: gold;
display: flex;
align-items: center;
align-content: center;
}
}
.vec {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
#ui-prop {
margin: 0 10px;
flex: 1;
.normal-data {
.value {
min-width: 50px;
}
.key {
min-width: unset;
display: block;
margin-right: 5px;
flex: unset;
}
}
}
#ui-prop:first-child {
margin-left: 0;
}
#ui-prop:last-child {
margin-right: 0;
}
}
.array-object {
flex: 1;
max-width: 100%;
overflow: hidden;
display: flex;
flex-direction: row;
align-items: center;
.text {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.image-property {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
height: 36px;
}
.slot {
display: flex;
width: 100%;
}
}
}
}
</style>