Browse Source

左侧树

master
修宁 6 months ago
parent
commit
7a06a506d2
  1. 4
      package.json
  2. 78
      pnpm-lock.yaml
  3. 174
      src/components/YvJsonEditor.vue
  4. 51
      src/components/yvTable/DeleteCellRenderer.vue
  5. 259
      src/components/yvTable/YvAggridCheckbox.vue
  6. 154
      src/components/yvTable/YvAggridCombo.vue
  7. 105
      src/components/yvTable/YvJsonCode.vue
  8. 808
      src/components/yvTable/YvTable.vue
  9. 69
      src/components/yvTable/yv-aggrid-cn.locale.js
  10. 69
      src/components/yvTable/yv-aggrid-en.locale.js
  11. 11
      src/core/controls/DragControl.ts
  12. 7
      src/core/controls/IControls.ts
  13. 3
      src/core/controls/MouseMoveInspect.ts
  14. 3
      src/core/controls/SelectInspect.ts
  15. 43
      src/core/engine/Viewport.ts
  16. 4
      src/core/manager/EntityManager.ts
  17. 171
      src/core/manager/ItemFindManager.ts
  18. 4
      src/editor/ModelMain.less
  19. 33
      src/editor/widgets/modeltree/ModeltreeView.vue
  20. 133
      src/editor/widgets/modeltree/ModeltreeViewJs.js
  21. 1
      src/modules/gstore/GstoreRenderer.ts
  22. 8
      src/types/Types.d.ts
  23. 67
      src/utils/webutils.ts

4
package.json

@ -25,6 +25,7 @@
"@types/lodash": "^4.17.7",
"@types/node": "^22.14.0",
"@types/three": "^0.176.0",
"@types/codemirror": "^5.60.16",
"@vicons/antd": "^0.13.0",
"@vicons/fa": "^0.12.0",
"@vitejs/plugin-vue": "^5.2.3",
@ -52,7 +53,7 @@
"rimraf": "^6.0.1",
"sortablejs": "1.15.6",
"split.js": "^1.6.4",
"three": "^0.176.0",
"three": "^0.177.0",
"troika-three-text": "^0.52.4",
"tslib": "2.8.1",
"typescript": "~5.8.0",
@ -63,6 +64,7 @@
"vue-router": "^4.5.0",
"vue-tsc": "^2.2.8",
"vue3-menus": "^1.1.2",
"three-mesh-bvh": "^0.9.0",
"three-dxf-viewer": "^1.0.36"
}
}

78
pnpm-lock.yaml

@ -27,6 +27,9 @@ importers:
'@tsconfig/node22':
specifier: ^22.0.1
version: 22.0.2
'@types/codemirror':
specifier: ^5.60.16
version: 5.60.16
'@types/jquery':
specifier: ^3.3.31
version: 3.5.32
@ -68,7 +71,7 @@ importers:
version: 1.9.0
camera-controls:
specifier: 2.10.1
version: 2.10.1(three@0.176.0)
version: 2.10.1(three@0.177.0)
codemirror:
specifier: ^5.65.19
version: 5.65.19
@ -121,14 +124,17 @@ importers:
specifier: ^1.6.4
version: 1.6.5
three:
specifier: ^0.176.0
version: 0.176.0
specifier: ^0.177.0
version: 0.177.0
three-dxf-viewer:
specifier: ^1.0.36
version: 1.0.36
three-mesh-bvh:
specifier: ^0.9.0
version: 0.9.0(three@0.177.0)
troika-three-text:
specifier: ^0.52.4
version: 0.52.4(three@0.176.0)
version: 0.52.4(three@0.177.0)
tslib:
specifier: 2.8.1
version: 2.8.1
@ -570,56 +576,67 @@ packages:
resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.41.0':
resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.41.0':
resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.41.0':
resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.41.0':
resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.41.0':
resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.41.0':
resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.41.0':
resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.41.0':
resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.41.0':
resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.41.0':
resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.41.0':
resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==}
@ -652,6 +669,9 @@ packages:
'@tweenjs/tween.js@23.1.3':
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
'@types/codemirror@5.60.16':
resolution: {integrity: sha512-V/yHdamffSS075jit+fDxaOAmdP2liok8NSNJnAZfDJErzOheuygHZEhAJrfmk5TEyM32MhkZjwo/idX791yxw==}
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
@ -673,6 +693,9 @@ packages:
'@types/stats.js@0.17.4':
resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
'@types/tern@0.23.9':
resolution: {integrity: sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==}
'@types/three@0.176.0':
resolution: {integrity: sha512-FwfPXxCqOtP7EdYMagCFePNKoG1AGBDUEVKtluv2BTVRpSt7b+X27xNsirPCTCqY1pGYsPUzaM3jgWP7dXSxlw==}
@ -941,7 +964,7 @@ packages:
engines: {node: '>= 0.8'}
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, tarball: https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz}
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@ -1002,7 +1025,7 @@ packages:
engines: {node: '>= 0.4'}
dxf@5.2.0:
resolution: {integrity: sha512-qk29/318lCOrDWKPEx8MztNjBSGoXoPlpyYkgRwyAKMb8UEZ5nQU5IU3ruAYOtSgCWjiVZng41ros4CNkLuMiQ==, tarball: https://registry.npmmirror.com/dxf/-/dxf-5.2.0.tgz}
resolution: {integrity: sha512-qk29/318lCOrDWKPEx8MztNjBSGoXoPlpyYkgRwyAKMb8UEZ5nQU5IU3ruAYOtSgCWjiVZng41ros4CNkLuMiQ==}
engines: {node: '>=8.9.0'}
hasBin: true
@ -1590,13 +1613,18 @@ packages:
engines: {node: '>=16'}
three-dxf-viewer@1.0.36:
resolution: {integrity: sha512-Tu7k+/yCyovpMJTeQ8fpGHdjtynq66vMMXYzDCwI9prJo2dRDxig4S+ixQVuMIy3I99VZa51z5KwlkeWyCnFzQ==, tarball: https://registry.npmmirror.com/three-dxf-viewer/-/three-dxf-viewer-1.0.36.tgz}
resolution: {integrity: sha512-Tu7k+/yCyovpMJTeQ8fpGHdjtynq66vMMXYzDCwI9prJo2dRDxig4S+ixQVuMIy3I99VZa51z5KwlkeWyCnFzQ==}
three-mesh-bvh@0.9.0:
resolution: {integrity: sha512-xAwZj0hZknpwVsdK5BBJTIAZDjDPZCRzURY1o+z/JHBON/jc2UetK1CzPeQZiiOVSfI4jV2z7sXnnGtgsgnjaA==}
peerDependencies:
three: '>= 0.159.0'
three@0.171.0:
resolution: {integrity: sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==, tarball: https://registry.npmmirror.com/three/-/three-0.171.0.tgz}
resolution: {integrity: sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==}
three@0.176.0:
resolution: {integrity: sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==}
three@0.177.0:
resolution: {integrity: sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==}
tinyglobby@0.2.13:
resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==}
@ -1645,7 +1673,7 @@ packages:
browserslist: '>= 4.21.0'
vecks@3.9.2:
resolution: {integrity: sha512-ubOo1KoOrPu3llYmlE4SH60JWcR+YDaObP+1Kzpbcl09dKIf0fbBweW8Xyg+6Nk2Rrnyit6Jxw9bx2LOYpMQMA==, tarball: https://registry.npmmirror.com/vecks/-/vecks-3.9.2.tgz}
resolution: {integrity: sha512-ubOo1KoOrPu3llYmlE4SH60JWcR+YDaObP+1Kzpbcl09dKIf0fbBweW8Xyg+6Nk2Rrnyit6Jxw9bx2LOYpMQMA==}
vite-hot-client@2.0.4:
resolution: {integrity: sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==}
@ -2239,6 +2267,10 @@ snapshots:
'@tweenjs/tween.js@23.1.3': {}
'@types/codemirror@5.60.16':
dependencies:
'@types/tern': 0.23.9
'@types/estree@1.0.7': {}
'@types/jquery@3.5.32':
@ -2259,6 +2291,10 @@ snapshots:
'@types/stats.js@0.17.4': {}
'@types/tern@0.23.9':
dependencies:
'@types/estree': 1.0.7
'@types/three@0.176.0':
dependencies:
'@dimforge/rapier3d-compat': 0.12.0
@ -2601,9 +2637,9 @@ snapshots:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0
camera-controls@2.10.1(three@0.176.0):
camera-controls@2.10.1(three@0.177.0):
dependencies:
three: 0.176.0
three: 0.177.0
caniuse-lite@1.0.30001718: {}
@ -3249,9 +3285,13 @@ snapshots:
dxf: 5.2.0
three: 0.171.0
three-mesh-bvh@0.9.0(three@0.177.0):
dependencies:
three: 0.177.0
three@0.171.0: {}
three@0.176.0: {}
three@0.177.0: {}
tinyglobby@0.2.13:
dependencies:
@ -3260,17 +3300,17 @@ snapshots:
totalist@3.0.1: {}
troika-three-text@0.52.4(three@0.176.0):
troika-three-text@0.52.4(three@0.177.0):
dependencies:
bidi-js: 1.0.3
three: 0.176.0
troika-three-utils: 0.52.4(three@0.176.0)
three: 0.177.0
troika-three-utils: 0.52.4(three@0.177.0)
troika-worker-utils: 0.52.0
webgl-sdf-generator: 1.1.1
troika-three-utils@0.52.4(three@0.176.0):
troika-three-utils@0.52.4(three@0.177.0):
dependencies:
three: 0.176.0
three: 0.177.0
troika-worker-utils@0.52.0: {}

174
src/components/YvJsonEditor.vue

@ -0,0 +1,174 @@
<template>
<div class="yv-json-editor">
<textarea ref="_textarea"></textarea>
</div>
</template>
<script>
import _ from 'lodash'
// codemirror
import CodeMirror from "codemirror";
import "codemirror/lib/codemirror.css";
import "codemirror/mode/javascript/javascript.js";
// :
import "codemirror/addon/fold/foldgutter.css";
import "codemirror/addon/fold/foldcode.js";
import "codemirror/addon/fold/brace-fold.js";
import "codemirror/addon/fold/comment-fold.js";
import "codemirror/addon/fold/indent-fold.js";
import "codemirror/addon/fold/foldgutter.js";
import "codemirror/addon/hint/show-hint.css";
// :
//
import "codemirror/addon/edit/matchbrackets.js";
// :
import "codemirror/addon/scroll/annotatescrollbar.js";
import "codemirror/addon/search/matchesonscrollbar.js";
import "codemirror/addon/search/match-highlighter.js";
import "codemirror/addon/search/jump-to-line.js";
import "codemirror/addon/dialog/dialog.js";
import "codemirror/addon/dialog/dialog.css";
import "codemirror/addon/search/searchcursor.js";
import "codemirror/addon/search/search.js";
// :
//styleActiveLinetrue
import "codemirror/addon/selection/active-line.js";
//
import "codemirror/addon/lint/lint.css";
import "codemirror/addon/lint/lint.js";
import "codemirror/addon/lint/json-lint.js";
export default {
props: {
modelValue: String,
defaultValue: {type: String, default: ''},
readonly: {type: Boolean, default: false},
},
emits: [
'update:modelValue',
],
data() {
return {
// editor: undefined,
// meHeight: 'auto',
}
},
mounted() {
this.$nextTick(() => {
this.init()
})
},
beforeUnmount() {
// debugger
if (this.editor != null) {
this.editor.toTextArea();
this.editor = null;
}
},
methods: {
appendText(text) {
this.editor.setValue(this.editor.getValue() + text)
},
scrollToButtom() {
const editor = this.editor
const info = editor.getScrollInfo()
const height = info.height
editor.scrollTo(0, height)
},
init() {
this.editor = CodeMirror.fromTextArea(this.$refs._textarea, {
value: this.modelValue,
viewportMargin: Infinity,
// JS
mode: "application/json",
indentUnit: 2, // 2
// n*tabntabfalse
indentWithTabs: false,
// true
smartIndent: true, //
//
lineNumbers: false,
//
matchBrackets: true,
//
lineWrapping: false,
foldGutter: true,
gutters: [
"CodeMirror-linenumbers",
"CodeMirror-foldgutter",
"CodeMirror-lint-markers",
],
readOnly: this.readonly,
// gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
//
styleActiveLine: true,
theme: 'default innerMirror',
});
window['$editor'] = this.editor
// change
this.editor.on("change", () => {
// v-model
this.$emit("update:modelValue", this.editor.getValue());
});
this.editor.on("cursorActivity", (cm) => {
// console.log(cm)
// this.editor.showHint();
});
// this.editor.on("inputRead", () => {
// this.editor.showHint();
// });
if (this.modelValue) {
this.editor.setValue(this.modelValue);
} else {
if (this.defaultValue) {
this.editor.setValue(this.defaultValue);
}
}
},
},
computed: {},
watch: {
modelValue: {
handler(newValue) {
if (this.editor && typeof newValue === 'string' && newValue !== this.editor.getValue()) {
// v-model
this.editor.setValue(newValue);
}
},
},
readonly: {
handler(val) {
if (this.editor) {
this.editor.setOption("readOnly", val);
}
},
}
}
}
</script>
<style lang="less">
.yv-json-editor {
height: 100%;
.CodeMirror {
width: 100%;
height: auto !important;
min-height: 100px;
overflow-y: auto;
overflow-x: auto;
font-size: 16px;
color: #333;
font-family: Helvetica, -apple-system, system-ui, Ubuntu, Microsoft YaHei, sans-serif;
background-color: #FFF;
border: 1px solid #ece2e2;
}
}
</style>

51
src/components/yvTable/DeleteCellRenderer.vue

@ -0,0 +1,51 @@
<template>
<div>
<el-button size="small" link type="danger" @click="onClick">{{ params.action ?? '删除' }}</el-button>
</div>
</template>
<script>
import _ from 'lodash'
import {defineComponent, toRaw, reactive} from "vue"
import {ElButton} from 'element-plus'
// const props = defineProps({
// params: Object,
// value: null,
// })
export default defineComponent({
components: {ElButton},
name: "DeleteCellRenderer",
props: ['value', 'params'],
setup(props) {
const data = reactive({})
function onClick() {
const yvAggrid = props.params.context.componentParent
if (yvAggrid.$props.data) {
props.params.api.applyTransaction({
remove: [props.params.data],
})
// this.params.rowIndex
yvAggrid.$props.data.splice(props.params.rowIndex, 1)
}
if (_.isArray(yvAggrid.$props.modelValue)) {
if (props.params.action) {
//
yvAggrid.onActionClick(props.params.action, props.params)
} else {
// this.params.rowIndex
_.remove(yvAggrid.$props.modelValue, (r) => r._rid === props.params.data._rid)
// yvAggrid.$props.modelValue.splice(props.params.rowIndex, 1)
}
}
}
return {
data, onClick,
}
}
})
</script>

259
src/components/yvTable/YvAggridCheckbox.vue

@ -0,0 +1,259 @@
<template>
<el-switch v-if="containsData" v-model="boolValue" label=""
ref="_inner"
:before-change="onBeforeChange"
@change="onChange"
size="small" />
</template>
<script>
import _ from 'lodash'
import { defineComponent, reactive, onMounted, nextTick, defineProps, getCurrentInstance } from 'vue'
import { ElSwitch } from 'element-plus'
import { runEvent } from '@/utils/webutils.js'
export default {
props: ['params'],
components: { ElSwitch },
data(props) {
return {
value: null,
containsData: typeof props.params.data === 'object',
isTreeNode: typeof props.params.eParentOfValue === 'object',
boolValue: null,
isActive: false,
allowEdit: props.params.allowEdit
}
},
mounted() {
this.value = this.params.value
this.$nextTick(() => {
this.isActive = true
})
let value = this.params.value
this.boolValue = false
if (this.params.editor.convType === 'bool') {
if (`${value}` === 'true') {
this.boolValue = true
}
} else {
if (this.params.editor.activeValue) {
if (`${value}` === this.params.editor.activeValue) {
this.boolValue = true
}
} else {
//
if (`${value}` === '1' || `${value}` === 'true' || `${value}` === 'Y') {
this.boolValue = true
}
}
}
},
methods: {
getValue() {
return this.value
},
onBeforeChange() {
if (this.isActive === false) {
return true
}
const allowEdit = this.allowEdit
if (allowEdit) {
const f = new Function('param', 'scope', allowEdit)
const r = f.call(this.params.scope, this.params, this.params.scope)
if (r === false) {
//
return false
}
}
//
return true
},
onChange(boolValue) {
const params = this.params
if (!this.isActive) {
return
}
let newValue = boolValue
this.boolValue = boolValue
const convType = this.params.editor?.convType ?? 'bool'
if (convType === 'bool') {
if (`${boolValue}` === 'true') {
newValue = true
} else {
newValue = false
}
} else if (convType === 'num') {
if (boolValue) {
newValue = parseInt(params.editor.activeValue ?? '1')
} else {
newValue = parseInt(params.editor.inactiveValue ?? '0')
}
} else if (convType === 'str') {
if (boolValue) {
newValue = params.editor.activeValue
} else {
newValue = params.editor.inactiveValue
}
}
const dataIndex = this.params.colDef?.field || ''
if (dataIndex.includes('.')) {
_.set(this.params.data, dataIndex, newValue)
} else {
params.node.data[params.colDef.field] = newValue
}
const yvAggrid = this.params.context.componentParent
yvAggrid.onCellValueChanged({
...params,
newValue,
value: newValue,
valueFormatted: `${newValue}`
})
}
}
}
// export default defineComponent({
// name: "YvAggridCheckbox",
// props: ['value', 'params'],
// components: {ElSwitch},
// setup(props) {
// const data = reactive({
// containsData: typeof props.params.data === 'object',
// isTreeNode: typeof props.params.eParentOfValue === 'object',
// boolValue: null,
// isActive: false,
// allowEdit: props.params.allowEdit
// })
//
// function getValue() {
// return props.value;
// }
//
// function onBeforeChange() {
// if (data.isActive === false) {
// return true
// }
//
// const allowEdit = data.allowEdit
// if (allowEdit) {
// const f = new Function('param', 'scope', allowEdit)
// const r = f.call(props.params.scope, props.params, props.params.scope)
// if (r === false) {
// //
// return false
// }
// }
//
// //
// return true
// }
//
// function onChange(boolValue) {
// const params = props.params
// if (!data.isActive) {
// return
// }
//
// let newValue = boolValue
// data.boolValue = boolValue
//
// const convType = props.params.editor?.convType ?? 'bool'
//
// if (convType === 'bool') {
// if (`${boolValue}` === 'true') {
// newValue = true
// } else {
// newValue = false
// }
//
// } else if (convType === 'num') {
// if (boolValue) {
// newValue = parseInt(params.editor.activeValue ?? '1')
// } else {
// newValue = parseInt(params.editor.inactiveValue ?? '0')
// }
//
// } else if (convType === 'str') {
// if (boolValue) {
// newValue = params.editor.activeValue
// } else {
// newValue = params.editor.inactiveValue
// }
// }
//
// const dataIndex = props.params.colDef?.field || ''
//
// if (dataIndex.includes('.')) {
// _.set(props.params.data, dataIndex, newValue)
// } else {
// params.node.data[params.colDef.field] = newValue
// }
//
// const yvAggrid = props.params.context.componentParent
// yvAggrid.onCellValueChanged({
// ...params,
// value: newValue,
// valueFormatted: `${newValue}`
// })
// }
//
// onMounted(() => {
// nextTick(() => {
// data.isActive = true
// })
// let value = props.params.value
// data.boolValue = false
//
// const convType = props.params.editor?.convType ?? 'bool'
//
// if (convType === 'bool') {
// if (`${value}` === 'true') {
// data.boolValue = true
// }
//
// } else {
// if (props.params.editor.activeValue) {
// if (`${value}` === props.params.editor.activeValue) {
// }
// data.boolValue = true
// } else {
// //
// if (`${value}` === '1' || `${value}` === 'true' || `${value}` === 'Y') {
// data.boolValue = true
// }
// }
// }
//
// // if (this.boolValue === false) {
// // console.log('boolValue=false and params.value=' + value)
// // }
// })
//
// return {
// data,
// getValue,
// onChange,
// onBeforeChange
// }
// }
// })
// const props = defineProps({
// params: Object,
// value: null,
// })
//
// debugger
</script>

154
src/components/yvTable/YvAggridCombo.vue

@ -0,0 +1,154 @@
<template>
<el-select ref="_inner" v-model="value" :multiple="multiple" :allow-create="allowCreate"
filterable clearable class="yv-inner-aggrid-combo"
placement="bottom-start"
:fallback-placements="['bottom-start', 'top-start']"
@keyup="onEscKeyDown"
@change="onChange">
<el-option v-for="option in options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
<script>
import _ from 'lodash'
import {defineComponent, nextTick, computed, reactive, defineProps, watch, onMounted, ref} from "vue"
import {ElSelect} from 'element-plus'
export default {
props: ['params'],
components: {ElSelect},
mounted() {
this.value = this.params.value;
nextTick(() => {
this.$refs._inner?.focus()
this.$refs._inner?.toggleMenu()
})
},
data() {
return {
value: null,
}
},
computed: {
allowCreate() {
return (this.params.editor.allowCreate === true)
},
multiple() {
return (this.params.editor.multiple === true)
},
options() {
if (typeof this.params.editor.options === 'function') {
return this.params.editor.options()
}
if (typeof this.params.editor.options === 'object' &&
typeof this.params.editor.options.scope === 'function' &&
typeof this.params.editor.options.method === 'string') {
const opt = this.params.editor.options
return opt.scope()[opt.method]
}
return this.params.editor.options
}
},
watch: {
value(newVal) {
const dataIndex = this.params.colDef?.field || ''
if (dataIndex.includes('.')) {
// const nodeData = this.params.node.data
// _.set(nodeData, dataIndex, newVal)
this.params.onCellValueChanged({
...this.params,
newValue: newVal,
})
}
}
},
methods: {
onEscKeyDown(evt) {
if (evt.code === 'Escape') {
//
this.params.stopEditing()
}
},
getValue() {
return this.value;
},
onChange(newValue) {
}
}
}
// export default defineComponent({
// name: "YvAggridCheckbox",
// components: {ElSelect},
// props: ['value', 'params'],
// setup(props) {
// const _inner = ref(null)
// const data = reactive({
// value: null,
// })
//
// function getValue() {
// return props.value;
// }
//
// function onChange(newValue) {
// }
//
// const allowCreate = computed(() => {
// return (props.params.editor.allowCreate === true)
// })
// const multiple = computed(() => {
// return (props.params.editor.multiple === true)
// })
// const options = computed(() => {
// if (typeof props.params.editor.options === 'function') {
// return props.params.editor.options()
// }
// return props.params.editor.options
// })
//
// watch(() => props.value, (newVal) => {
// const dataIndex = props.params.colDef?.field || ''
//
// if (dataIndex.includes('.')) {
// _.set(props.params.data, dataIndex, newVal)
// }
// })
//
// onMounted(() => {
// data.value = props.params.value;
// nextTick(() => {
// _inner.value?.focus()
// })
// })
//
// return {
// _inner,
// data, options,
// getValue,
// onChange,
// allowCreate,
// multiple,
// }
// }
// })
// const props = defineProps({
// params: Object,
// value: null,
// })
// debugger
</script>
<style>
.yv-inner-aggrid-combo {
width: 100%;
}
.yv-inner-aggrid-combo.el-select .el-input.is-focus .el-input__wrapper {
box-shadow: none !important;
}
</style>

105
src/components/yvTable/YvJsonCode.vue

@ -0,0 +1,105 @@
<template>
<slot />
<div v-element-dialog-resize="{ draggable: true, fullscreen: true }">
<el-dialog v-model="showCodeWindow" :title="title" width="80%"
class="resize-dialog"
append-to-body
:close-on-press-escape="true"
:close-on-click-modal="false"
style="padding:0;height: 400px;"
@opened="onWindowOpen">
<YvJsonEditor language="json" v-model="code" />
<template #footer>
<el-button type="danger" :icon="renderIcon('Save')" @click="importCommit">确定</el-button>
<el-button :icon="renderIcon('Times')" @click="()=>this.showCodeWindow=false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import _ from 'lodash'
import YvJsonEditor from '../YvJsonEditor.vue'
import { renderIcon } from '@/utils/webutils.js'
import ElementDialogResize from '@/components/element-dialog-resize'
export default {
directives: { ElementDialogResize },
props: {
modelValue: Object,
title: { type: String, default: '代码编辑', required: false },
mergeOnly: { type: Boolean, default: true, required: false },
mergeCode: { type: Function, required: false },
toJsonStr: { type: Function, required: false },
isFreeze: { type: Boolean, default: false, required: false }
},
components: { YvJsonEditor },
emits: ['codeCommit', 'update:modelValue'],
data() {
return {
showCodeWindow: false,
code: ''
}
},
watch: {
'modelValue': {
handler(newVal) {
if (this.showCodeWindow) {
this.code = JSON.stringify(newVal, null, 2)
}
}
}
},
methods: {
renderIcon,
show() {
this.showCodeWindow = true
},
onWindowOpen() {
if (typeof this.toJsonStr === 'function') {
const code = this.toJsonStr(this.modelValue)
if (typeof code === 'string') {
this.code = code
} else {
this.code = JSON.stringify(code, null, 2)
}
return
}
this.code = JSON.stringify(this.modelValue, null, 2)
},
importCommit() {
try {
let targetObject = JSON.parse(this.code)
this.showCodeWindow = false
if (typeof this.mergeCode === 'function') {
this.mergeCode(this.modelValue, targetObject)
this.$emit('codeCommit', this.modelValue)
} else {
if (this.mergeOnly) {
//
Object.keys(this.modelValue).forEach(key => {
if (!targetObject.hasOwnProperty(key)) {
delete this.modelValue[key]
}
})
_.assign(this.modelValue, targetObject)
this.$emit('codeCommit', this.modelValue)
} else {
if (this.isFreeze) {
targetObject = Object.freeze(targetObject)
}
this.$emit('update:modelValue', targetObject)
this.$emit('codeCommit', targetObject)
}
}
} catch (e) {
system.showErrorDialog('保存出错:', e.toString())
}
}
}
}
</script>

808
src/components/yvTable/YvTable.vue

@ -0,0 +1,808 @@
<template>
<div class="yv-table-wrap">
<el-row v-if="setting.showToolbar">
<slot name="toolbar" />
<el-dropdown v-if="!setting.disableAppendButton"
split-button type="primary" :icon="renderIcon('Plus')" @click="()=>appendRow({})"
class="is-link" trigger="click">
<component :is="renderIcon('Plus')" />
新增
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="_appendNewNumClick">
添加
<el-input-number v-model="appendNewNum" :min="1" :max="999" size="small" @click.stop="()=>{}"
@keydown.enter="_appendNewNumClick"
style="width:80px;margin:0 5px" />
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button type="warning" link :icon="renderIcon('Delete')" @click="removeSelectedRows"
v-if="!setting.disableDeleteButton"
>删除
</el-button>
<YvJsonCode :modelValue="modelValue" :to-json-str="removeRidHandle" :merge-code="createRidHandle"
ref="arrayCodeDesign">
<el-button link :icon="renderIcon('Code')" @click="()=>$refs.arrayCodeDesign.show()">编辑JSON</el-button>
</YvJsonCode>
<el-input placeholder="筛选" v-model="filterStr" style="margin-left: 5px;width:200px;" size="small" clearable />
</el-row>
<ag-grid-vue
v-if="!!gridOption"
:class="['ag-theme-alpine', 'yv-table', 'hi-light-selected-row','allow-vertical-line']"
ref="_inner"
v-bind="gridOption"
:getRowId="getRowId"
:columnDefs="columnDefs"
@gridReady="onReady"
@cell-clicked="onCellClicked"
@cellEditingStarted="onCellEditingStarted"
@cellEditingStopped="onCellEditingStopped"
@cellDoubleClicked="onCellDoubleClicked"
@rowClicked="onRowClick"
@selectionChanged="onSelectionChange"
@rowDoubleClicked="onRowDblClick"
@rowDataUpdated="onRowDataUpdated"
@rowDragEnd="onRowDragEnd"
@cellValueChanged="onCellValueChanged"
/>
<slot />
</div>
</template>
<script>
import _ from 'lodash'
import { markRaw, toRaw } from 'vue'
import { renderIcon } from '@/utils/webutils.js'
import { localeText as localeTextCn } from './yv-aggrid-cn.locale'
import { localeText as localeTextEn } from './yv-aggrid-en.locale'
import YvAggridCheckbox from './YvAggridCheckbox.vue'
import YvAggridCombo from './YvAggridCombo.vue'
import DeleteCellRenderer from './DeleteCellRenderer.vue'
import YvJsonCode from './YvJsonCode.vue'
import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-alpine.css'
const GridRowHeight = 32
export default {
components: {
YvJsonCode,
YvAggridCheckbox, YvAggridCombo, DeleteCellRenderer
},
props: {
setting: { type: Object, required: true },
modelValue: { type: Array, required: false },
selectedRow: { type: Object, required: false, default: undefined }
},
emits: [
'actionClick',
'selectionChange',
'onReady',
'isChanged',
'rowDblClick',
'rowClick',
'rowDataUpdated',
'rowDragEnd',
'cellClicked',
'cellEditingStarted',
'cellEditingStopped',
'cellDoubleClicked',
'update:modelValue',
'update:selectedRow',
'update:selectedRows'
],
data() {
const gridOption = this.buildGridOption(this.setting)
const columnDefs = gridOption.columnDefs
return {
api: null,
columnApi: null,
filterStr: '',
isChanged: false,
gridOption: Object.freeze(gridOption),
columnDefs: Object.freeze(columnDefs),
appendNewNum: 10,
insertBeforeNewNum: 1,
insertAfterNewNum: 1
}
},
mounted() {
window['pp'] = this
},
watch: {
filterStr: {
handler(newValue) {
this.api.setQuickFilter(newValue)
}
},
modelValue: {
deep: true,
handler(newValue) {
// console.log('watch modelValue changed')
this.syncData()
}
}
},
methods: {
renderIcon,
onActionClick(action, params) {
const modelRow = _.find(this.modelValue, (row) => row._rid === params.data._rid)
this.$emit('actionClick', action, params, modelRow)
},
removeRidHandle(newArray) {
if (!_.isArray(newArray)) {
return []
}
return newArray.map(item => {
const newItem = _.cloneDeep(item)
delete newItem._rid
return newItem
})
},
createRidHandle(currentArray, newArray) {
if (!_.isArray(newArray)) {
throw new Error('newArray is not array')
}
for (let i = 0; i < newArray.length; i++) {
newArray[i]._rid = _.uniqueId('_')
}
this.$emit('update:modelValue', newArray)
},
getRowId(params) {
return params.data._rid
},
filter(str) {
this.api.setQuickFilter(str)
},
/**
* 停止编辑
*/
stopEditing() {
this.api.stopEditing()
},
/**
* 获取全部数据行, 每个行都会附带 _rid 属性, 用于标识这一行的数据
* @returns {Array} 数据行的数组
*/
getDataRows() {
this.stopEditing()
const rows = []
this.api.forEachNode((node) => {
if (!node.data) {
return
}
rows.push(node.data)
})
return rows
},
printNode() {
this.stopEditing()
const rows = []
this.api.forEachNode((node) => {
rows.push(node)
})
return rows
},
getNodeByRowIndex(rowIndex) {
this.stopEditing()
let ret = undefined
this.api.forEachNode((node) => {
if (node.rowIndex === rowIndex) {
ret = node
return false
}
})
return ret
},
/**
* 获取所有选中行返回的数据中包含 _rid 字段.
* _rid 字段可以作为 setRowDataByRowid / removeRowByRowid 的参数
*/
getSelectedRows() {
if (!this.api) {
return []
}
this.stopEditing()
const sel = []
// selected
this.api.forEachNode((node) => {
if (node.selected && node.displayed) {
if (node.data?._rid) {
const modelRow = _.find(this.modelValue, (row) => row._rid === node.data._rid)
if (modelRow) {
sel.push(modelRow)
}
}
}
})
if (sel.length <= 0) {
// range
const ridList = []
_.forEach(this.api.getCellRanges(), range => {
const startRow = range.startRow.rowIndex > range.endRow.rowIndex ? range.endRow.rowIndex : range.startRow.rowIndex
const endRow = range.startRow.rowIndex > range.endRow.rowIndex ? range.startRow.rowIndex : range.endRow.rowIndex
for (let i = startRow; i <= endRow; i++) {
const row = this.api.getDisplayedRowAtIndex(i)
if (row.data?._rid && !ridList.includes(row.data._rid)) {
ridList.push(row.data._rid)
}
}
})
_.forEach(ridList, (rid) => {
const row = _.find(this.modelValue, (row) => row._rid === rid)
if (row) {
sel.push(row)
}
})
}
return sel
},
/**
* 通过 _rid 计算变化的数据并同步到表格
*/
syncData() {
if (!_.isArray(this.modelValue)) {
console.log('applyTransaction', [])
this.api?.setRowData([])
return
}
// modelValue aggrid ,
const sel = {
add: [],
update: [],
remove: []
}
const modelValue = this.modelValue
const insertRows = [...modelValue]
this.api?.forEachNode((node) => {
if (!node.data) {
return
}
const modelRow = _.find(modelValue, (row) => node.data._rid === row._rid)
_.remove(insertRows, r => r === modelRow)
if (!modelRow) {
//
sel.remove.push(node.data)
} else if (!_.isEqual(node.data, modelRow)) {
//
Object.assign(node.data, _.cloneDeep(modelRow))
sel.update.push(node.data)
}
})
_.forEach(insertRows, (row, index) => {
if (!row._rid) {
row._rid = _.uniqueId('_')
}
})
sel.add = _.cloneDeep(insertRows)
if (sel.add.length > 0 || sel.update.length > 0 || sel.remove.length > 0) {
console.log('applyTransaction', sel)
this.api?.applyTransaction(sel)
this.refreshRowCount()
}
},
/**
* 最强制的方法刷新数据
*/
refreshData() {
if (!this.modelValue) {
this.api?.setRowData([])
return
}
// modelValue _rid
_.forEach(this.modelValue, (row, index) => {
row._rid = _.uniqueId('_')
})
this.api?.setRowData(_.cloneDeep(this.modelValue))
this.refreshRowCount()
},
appendRow(row) {
if (!_.isArray(this.modelValue)) {
if (_.isArray(row)) {
this.$emit('update:modelValue', [...row])
} else {
this.$emit('update:modelValue', [row])
}
} else {
if (_.isArray(row)) {
this.modelValue.push(...row)
} else {
this.modelValue.push(row)
}
}
},
/**
* 删除单元格模式下选中的行
*/
removeSelectedRows() {
const rows = this.getSelectedRows()
if (rows.length <= 0) {
system.msg('请选择要删除的行')
return
}
system.confirm('确定要删除选中的' + rows.length + '行吗?').then(() => {
const deleted = _.remove(this.modelValue, (r) => _.includes(rows, r))
if (deleted.length > 0) {
system.msg('成功删除' + deleted.length + '行')
this.api.clearRangeSelection()
} else {
system.msg('没有删除任何行')
}
})
},
buildGridOption() {
let localeText
// if (this.locale?.name === 'zh-cn') {
localeText = localeTextCn
// } else {
// localeText = localeTextEn
// }
const setting = _.cloneDeep(this.setting)
const gridOption = {
localeText: localeText,
context: {
componentParent: this
},
columnDefs: this.processColumns(setting),
suppressPropertyNamesCheck: true,
suppressMaxRenderedRowRestriction: true,
animateRows: false,
suppressContextMenu: true,
suppressAggFuncInHeader: true,
enableCellTextSelection: true,
ensureDomOrder: true,
domLayout: setting.domLayout,
tooltipInteraction: true,
suppressScrollOnNewData: true,
popupParent: document.querySelector('body'),
rowHeight: GridRowHeight,
defaultColDef: {
editable: false,
singleClickEdit: false,
sortable: true,
serverSortable: true,
resizable: true,
enableRowGroup: true,
enablePivot: true,
enableValue: true,
filter: true
}
}
if (setting.showDeleteButton) {
gridOption.columnDefs.push({
headerName: '操作',
width: 60,
maxWidth: 60,
minWidth: 60,
suppressMenu: true,
suppressSizeToFit: true,
suppressAutoSize: true,
suppressMovable: true,
suppressColumnsToolPanel: true,
pinned: 'right',
cellRenderer: 'DeleteCellRenderer'
})
}
if (setting.mode === 'select') {
//
_.extend(gridOption, {
rowSelection: 'single',
checkboxSelection: false,
enableRangeSelection: false,
suppressCellFocus: true,
enableCellTextSelection: true,
ensureDomOrder: true
})
gridOption.defaultColDef.singleClickEdit = false
gridOption.defaultColDef.editable = false
} else if (setting.mode === 'edit1') {
//
_.extend(gridOption, {
// rowSelection: 'single',
enableCellTextSelection: false,
ensureDomOrder: false,
enableRangeSelection: true,
suppressContextMenu: false
})
gridOption.defaultColDef.singleClickEdit = true
} else if (setting.mode === 'edit2') {
//
_.extend(gridOption, {
// rowSelection: 'multiple',
enableCellTextSelection: false,
ensureDomOrder: false,
enableRangeSelection: true,
suppressContextMenu: false
})
gridOption.defaultColDef.singleClickEdit = false
} else if (setting.mode === 'checkbox') {
_.extend(gridOption, {
rowSelection: 'multiple',
checkboxSelection: true,
enableCellTextSelection: true,
ensureDomOrder: true
})
}
if (setting.rowDragable) {
gridOption.animateRows = true
gridOption.rowDragManaged = true
}
return gridOption
},
processColumns(setting) {
const me = this
const list = []
_.forEach(setting.columns, column => {
if (typeof column.action === 'string') {
const action = column.action
delete column.action
_.assign(column, {
headerName: '操作',
suppressMenu: true,
suppressSizeToFit: true,
suppressAutoSize: true,
suppressMovable: true,
suppressColumnsToolPanel: true,
cellRenderer: 'DeleteCellRenderer',
cellRendererParams: {
action: action,
columnVjson: column.vjsonRaw,
scope: () => this.vcxt.scope,
vcxt: () => this.vcxt,
instance: () => this
}
})
}
if (column.dataIndex) {
column.field = column.dataIndex
delete column.dataIndex
}
if (column.header) {
column.headerName = column.header
delete column.header
}
if (typeof column.resizable === 'undefined') {
column.resizable = true
}
if (typeof column.hidden !== 'undefined') {
column.hide = column.hidden
delete column.hidden
}
if (setting.disableColumnMenu === true && typeof column.suppressMenu === 'undefined') {
column.suppressMenu = true
}
if (setting.sortable === false && typeof column.sortable === 'undefined') {
column.sortable = false
}
if (column.editor) {
const editor = column.editor
delete column.editor
if (editor.xtype === 'checkbox') {
_.extend(column, {
editable: true,
// cellEditor: 'YvAggridCheckbox',
// editable: false,
// cellRendererFramework: 'YvAggridCheckbox',
cellRenderer: 'YvAggridCheckbox',
cellRendererParams: {
editor: editor,
setEditRow: this.setEditRow,
top: this
}
})
} else if (editor.xtype === 'combo') {
_.extend(column, {
cellEditor: 'YvAggridCombo',
cellEditorParams: {
onCellValueChanged: this.onCellValueChanged.bind(this),
editor: editor,
top: () => this
}
// valueFormatter(param) {
// if (typeof param.value === 'undefined') {
// return ''
// }
//
// let dictArray = editor.options
// if (typeof dictArray === 'function') {
// dictArray = dictArray()
// } else if (typeof dictArray === 'object' && typeof dictArray.scope === 'function' && typeof dictArray.method === 'string') {
// dictArray = editor.options.scope()[editor.options.method]
// me.$watch(() => editor.options.scope()[editor.options.method], () => {
// me.api.refreshCells()
// })
// }
// const dictItem = _.find(dictArray, (dictItem) => (('' + dictItem.value) === ('' + param.value)))
// if (!dictItem) {
// return param.value
// }
//
// return dictItem.label
// }
})
} else {
throw new Error('不支持的编辑器类型:' + editor.xtype)
}
}
list.push(column)
})
if (setting.mode === 'checkbox') {
list.splice(0, 0, {
width: 37,
resizable: false,
suppressMenu: true,
headerCheckboxSelection: true,
enableCellTextSelection: true,
checkboxSelection: true,
suppressMovable: true,
suppressSizeToFit: true,
suppressAutoSize: true,
sortable: false,
serverSortable: false,
pinned: 'left'
})
}
if (setting.rowDragable) {
list.splice(0, 0, {
width: 25,
resizable: false,
suppressMenu: true,
rowDrag: true
})
}
if (setting.rowNumber) {
const hashValueGetter = (params) => {
return params.node ? (params.node.rowIndex + 1) : null
}
list.splice(0, 0, {
headerName: '#',
width: 30,
maxWidth: 100,
filter: false,
editable: false,
pinned: 'left',
resizable: true,
sortable: false,
serverSortable: false,
enableRowGroup: false,
enablePivot: false,
suppressMenu: true,
suppressColumnsToolPanel: true,
valueGetter: hashValueGetter
})
}
return list
},
runEvent(name, ...params) {
this.$emit(name, ...params)
},
onReady(evt) {
this.api = markRaw(evt.api)
this.columnApi = markRaw(evt.columnApi)
this.refreshData()
this.isChanged = false
this.runEvent('onReady')
},
onCellClicked() {
this.$emit('cellClicked', ...arguments)
},
onCellEditingStarted(param) {
if (param.colDef.cellRenderer === 'YvAggridCheckbox') {
this.stopEditing()
}
this.$emit('cellEditingStarted', ...arguments)
},
onCellEditingStopped() {
this.$emit('cellEditingStopped', ...arguments)
},
onCellDoubleClicked() {
this.$emit('cellDoubleClicked', ...arguments)
},
onRowClick() {
this.$emit('rowClick', ...arguments)
},
onSelectionChange({ api }) {
if (!api) {
return
}
const rows = api.getSelectedNodes()
this.$nextTick(() => {
this.$emit('update:selectedRow', rows[0])
this.$emit('update:selectedRows', rows)
})
},
onRowDblClick() {
this.$emit('rowDblClick', ...arguments)
},
onRowDragEnd(param) {
this.$emit('update:modelValue', this.getDataRows())
this.$emit('rowDragEnd', param)
},
onCellValueChanged(param) {
let { data, colDef, newValue } = param
if (!colDef) {
return
}
// checkbox
if (colDef.cellRenderer === 'YvAggridCheckbox' &&
colDef.cellRendererParams.editor?.convType !== 'num' &&
colDef.cellRendererParams.editor?.convType !== 'str') {
if (newValue === 'true') {
newValue = true
} else if (newValue === 'false') {
newValue = false
}
}
if (!data._rid) {
system.showErrorDialog('数据行没有 _rid 属性')
}
const modelRow = _.find(this.modelValue, (row) => (row._rid === data._rid))
if (!modelRow) {
system.showErrorDialog('数据行没有找到')
}
const dataIndex = colDef?.field || ''
if (dataIndex.includes('.')) {
if (Object.hasOwn(modelRow, dataIndex)) {
// bug ,
delete modelRow[dataIndex]
}
_.set(modelRow, colDef.field, newValue)
} else {
modelRow[dataIndex] = newValue
}
},
onRowDataUpdated(param) {
// console.log('rowDataUpdated', param.type)
// this.$emit('rowDataUpdated', param)
},
refreshRowCount() {
this.onSelectionChange({ api: this.api })
},
setData() {
if (!this.api) {
return
}
this.api.setRowData(this.modelValue)
this.refreshRowCount()
},
setEditRow(rowData) {
//
const [cell] = this.api.getEditingCells()
if (!cell) {
return
}
// node
let node
this.api.forEachNode((item) => {
if (!item.data) {
return
}
if (item.rowIndex === cell.rowIndex) {
node = item
return false
}
})
// rowData _ nodeData
const nodeData = _.cloneDeep(node.data)
_.forOwn(rowData, (value, key) => {
if (key.startsWith('_')) {
return
}
_.set(nodeData, key, value)
})
nodeData['_rid'] = node.id
// console.log("setSelectionRow", nodeData)
node.setData(nodeData)
this.runEvent('isChanged', true)
// ,
const colId = cell.column.colId
if (colId && Object.hasOwn(rowData, colId)) {
const [editor] = this.api.getCellEditorInstances()
editor.value = rowData[colId]
}
},
/**
* 设置表格的 loading 读取状态
* @param isLoad 是否"载入中"
*/
setLoading(isLoad) {
if (this.api) {
if (isLoad === true) {
this.api.showLoadingOverlay()
} else {
this.api.hideOverlay()
}
}
},
_appendNewNumClick() {
const rowNum = parseInt(this.appendNewNum)
if (rowNum > 0) {
const list = []
for (let i = 0; i < rowNum; i++) {
list.push({})
}
this.appendRow(list)
}
}
}
}
</script>
<style lang="less">
.yv-table-wrap {
display: flex;
flex-direction: column;
& > .yv-table {
flex-grow: 1;
display: flex;
&.allow-vertical-line .ag-cell-value {
border-right: 1px solid #ebeef5;
// border-right: 1px solid rgba(0, 0, 0, 0.09);
}
& > .ag-root-wrapper {
flex-grow: 1;
}
&.hi-light-selected-row .ag-row-focus {
background: var(--el-color-primary-light-8);
}
}
}
</style>

69
src/components/yvTable/yv-aggrid-cn.locale.js

@ -0,0 +1,69 @@
export const localeText = {
page: "页",
more: "更多",
to: "到",
of: "of",
next: "下⼀页",
last: "上⼀页",
first: "⾸页",
previous: "上⼀页",
loadingOoo: "加载中...",
selectAll: "查询全部",
searchOoo: "查询...",
blanks: "空⽩",
filterOoo: "过滤...",
applyFilter: "保存过滤器...",
equals: "相等",
notEqual: "不相等",
lessThan: "⼩于",
greaterThan: "⼤于",
lessThanOrEqual: "⼩于等于",
greaterThanOrEqual: "⼤于等于",
inRange: "范围",
contains: "包含",
notContains: "不包含",
startsWith: "开始于",
endsWith: "结束于",
group: "组",
columns: "列",
filters: "筛选",
rowGroupColumns: "laPivot Cols",
rowGroupColumnsEmptyMessage: "la drag cols to group",
valueColumns: "laValue Cols",
pivotMode: "laPivot-Mode",
groups: "laGroups",
values: "值",
pivots: "laPivots",
valueColumnsEmptyMessage: "la drag cols to aggregate",
pivotColumnsEmptyMessage: "la drag here to pivot",
toolPanelButton: "la tool panel",
noRowsToShow: "数据为空",
pinColumn: "固定",
valueAggregation: "laValue Agg",
autosizeThiscolumn: "自动调整宽度",
autosizeAllColumns: "自动调整所有字段宽度",
groupBy: "分组",
ungroupBy: "不分组",
resetColumns: "重置列",
expandAll: "展开全部",
collapseAll: "关闭",
toolPanel: "⼯具⾯板",
export: "导出",
csvExport: "导出为CSV格式⽂件",
excelExport: "导出到Excel",
pinLeft: "左固定 &lt;&lt;",
pinRight: "右固定 &gt;&gt;",
noPin: "不要固定",
sum: "总数",
min: "最⼩值",
max: "最⼤值",
none: "⽆",
count: "总",
average: "平均值",
copy: "复制",
copyWithHeaders: "带表头复制",
copyWithGroupHeaders: '带分组表头复制',
ctrlC: "ctrl + C",
paste: "粘贴",
ctrlV: "ctrl + V"
}

69
src/components/yvTable/yv-aggrid-en.locale.js

@ -0,0 +1,69 @@
export const localeText = {
page: "页",
more: "更多",
to: "到",
of: "of",
next: "下⼀页",
last: "上⼀页",
first: "⾸页",
previous: "上⼀页",
loadingOoo: "加载中...",
selectAll: "查询全部",
searchOoo: "查询...",
blanks: "空⽩",
filterOoo: "过滤...",
applyFilter: "保存过滤器...",
equals: "相等",
notEqual: "不相等",
lessThan: "⼩于",
greaterThan: "⼤于",
lessThanOrEqual: "⼩于等于",
greaterThanOrEqual: "⼤于等于",
inRange: "范围",
contains: "包含",
notContains: "不包含",
startsWith: "开始于",
endsWith: "结束于",
group: "组",
columns: "列",
filters: "筛选",
rowGroupColumns: "laPivot Cols",
rowGroupColumnsEmptyMessage: "la drag cols to group",
valueColumns: "laValue Cols",
pivotMode: "laPivot-Mode",
groups: "laGroups",
values: "值",
pivots: "laPivots",
valueColumnsEmptyMessage: "la drag cols to aggregate",
pivotColumnsEmptyMessage: "la drag here to pivot",
toolPanelButton: "la tool panel",
noRowsToShow: "数据为空",
pinColumn: "固定",
valueAggregation: "laValue Agg",
autosizeThiscolumn: "自动调整宽度",
autosizeAllColumns: "自动调整所有字段宽度",
groupBy: "分组",
ungroupBy: "不分组",
resetColumns: "重置列",
expandAll: "展开全部",
collapseAll: "关闭",
toolPanel: "⼯具⾯板",
export: "导出",
csvExport: "导出为CSV格式⽂件",
excelExport: "导出到Excel",
pinLeft: "左固定 &lt;&lt;",
pinRight: "右固定 &gt;&gt;",
noPin: "不要固定",
sum: "总数",
min: "最⼩值",
max: "最⼤值",
none: "⽆",
count: "总",
average: "平均值",
copy: "复制",
copyWithHeaders: "带表头复制",
copyWithGroupHeaders: '带分组表头复制',
ctrlC: "ctrl + C",
paste: "粘贴",
ctrlV: "ctrl + V"
}

11
src/core/controls/DragControl.ts

@ -1,6 +1,5 @@
import * as THREE from 'three'
import type Viewport from '@/core/engine/Viewport.ts'
import type IControls from '@/core/controls/IControls.ts'
import { getClosestObject } from '@/core/ModelUtils.ts'
import EventBus from '@/runtime/EventBus.ts'
import type { Object3DLike } from '@/types/ModelTypes.ts'
@ -10,7 +9,7 @@ import { LineManageWrap } from '@/core/manager/LineSegmentManager.ts'
/**
* ThreeJS X/Z
*/
export default class DragControl implements IControls {
export default class DragControl {
private viewport: Viewport
private _is_enabled: boolean = true
private domElement: HTMLElement
@ -109,11 +108,11 @@ export default class DragControl implements IControls {
} else {
// 射线方法修改 ==========================
// const mouse = this.getMousePosition(event.clientX, event.clientY)
// const intersected = this.getIntersectedDraggableObject(mouse)
const mouse = this.getMousePosition(event.clientX, event.clientY)
const intersected = this.getIntersectedDraggableObject(mouse)
// =====================================
const ids = this.viewport.itemFindManager.getItemsByPosition(CurrentMouseInfo.x, CurrentMouseInfo.z)
this.domElement.style.cursor = ids.length > 0 ? 'grab' : 'auto'
// const ids = this.viewport.itemFindManager.getItemsByPosition(CurrentMouseInfo.x, CurrentMouseInfo.z)
this.domElement.style.cursor = intersected ? 'grab' : 'auto'
}
}

7
src/core/controls/IControls.ts

@ -1,7 +0,0 @@
export default interface IControls {
init(viewport: any): void
dispose(): void
animate?: () => void;
}

3
src/core/controls/MouseMoveInspect.ts

@ -1,5 +1,4 @@
import type Viewport from '@/core/engine/Viewport'
import type IControls from './IControls'
import * as THREE from 'three'
let pmFn, otFn, lvFn
@ -7,7 +6,7 @@ let pmFn, otFn, lvFn
/**
* designer.mousePos
*/
export default class MouseMoveInspect implements IControls {
export default class MouseMoveInspect {
viewport: Viewport
canvas: HTMLCanvasElement

3
src/core/controls/SelectInspect.ts

@ -1,5 +1,4 @@
import * as THREE from 'three'
import type IControls from './IControls'
import type Viewport from '@/core/engine/Viewport'
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
@ -15,7 +14,7 @@ import { getAABBox, getOBBox } from '@/core/ModelUtils.ts'
/**
*
*/
export default class SelectInspect implements IControls {
export default class SelectInspect {
viewport: Viewport
/**
* 线

43
src/core/engine/Viewport.ts

@ -5,7 +5,6 @@ import Stats from 'three/examples/jsm/libs/stats.module'
import type WorldModel from '../manager/WorldModel'
import $ from 'jquery'
import { markRaw, reactive, toRaw, watch } from 'vue'
import type IControls from '../controls/IControls'
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
@ -41,29 +40,28 @@ export default class Viewport {
// dragControl: any // EsDragControls
animationFrameId: any = null
scene: SceneHelp
selectInspect = new SelectInspect()
mouseMoveInspect = new MouseMoveInspect()
dragControl = new DragControl()
labelManager = new LabelManager()
entityManager = new EntityManager()
itemFindManager = new ItemFindManager()
interactionManager = new InteractionManager()
// 状态管理器
stateManager: StateManager
tools: IControls[] = [
markRaw(this.selectInspect),
markRaw(this.mouseMoveInspect),
markRaw(this.dragControl),
markRaw(this.labelManager)
markRaw(this.labelManager),
markRaw(this.entityManager),
markRaw(this.itemFindManager),
markRaw(this.interactionManager)
]
// 状态管理器
stateManager: StateManager
// 实体管理器
entityManager = new EntityManager()
itemFindManager = new ItemFindManager()
// 交互管理器
interactionManager = new InteractionManager()
// 点实例管理器 moduleName -> InstancePointManager
pointManagerMap: Map<string, InstancePointManager> = new Map()
@ -168,9 +166,7 @@ export default class Viewport {
console.log('viewport on catelogCode: ' + this.scene.catalogCode)
const viewerDom = this.viewerDom
// 初始化各种管理器
this.entityManager.init(this)
this.interactionManager.init(this)
// 管理器
this.stateManager = new StateManager(option.stateManagerId, this)
// 渲染器
@ -440,6 +436,21 @@ export default class Viewport {
// return viewHeight / (2 * Math.tan(THREE.MathUtils.degToRad(referenceFOV) / 2))
// }
cameraToEntity(id: string) {
const { tf } = this.entityManager.findItemById(id)
// 移动正交相机去往目标点
if (this.camera instanceof THREE.OrthographicCamera) {
this.camera.position.set(tf[0][0], 60, tf[0][2])
this.camera.lookAt(tf[0][0], 0, tf[0][2])
this.camera.zoom = 60
this.camera.updateProjectionMatrix()
} else if (this.camera instanceof THREE.PerspectiveCamera) {
this.camera.position.set(tf[0][0], tf[1][1] + 10, tf[2][2])
this.camera.lookAt(tf[0][0], tf[1][1], tf[2][2])
}
}
handleResize(entries: any) {
for (let entry of entries) {
// entry.contentRect包含了元素的尺寸信息

4
src/core/manager/EntityManager.ts

@ -47,6 +47,10 @@ export default class EntityManager {
private readonly writeBackEntities = new Set<string>()
isUpdating = false
getAllEntityForGrid() {
return Array.from(this.___entityMap.values())
}
dispose() {
// 清理所有差量渲染器
for (const renderer of this.diffRenderer.values()) {

171
src/core/manager/ItemFindManager.ts

@ -1,200 +1,45 @@
import * as THREE from 'three'
import rbush from 'rbush'
import { OBB } from 'three/examples/jsm/math/OBB'
import type Viewport from '@/core/engine/Viewport.ts'
// import { Octree } from 'three/examples/jsm/math/Octree.js'
// import { QuadTreeNode } from '@/core/QuadTree.ts'
// import { convexHull } from '@/core/ModelUtils.ts'
interface ItemEntry extends rbush.BBox {
id: string
obb: OBB
}
// 主管理器类
export default class ItemFindManager {
private spatialIndex = new rbush<ItemEntry>()
private items = new Map<string, ItemEntry>()
private items = new Map<string, any>()
dispose() {
this.spatialIndex.clear()
this.items.clear()
}
constructor() {
}
init(viewport: Viewport) {
}
// 添加或更新物品
addOrUpdate(...items: ItemMetrix[]): void {
for (const item of items) {
const aabb = itemToAABB(item)
const obb = itemToOBB(item)
if (this.items.has(item.id)) {
this.remove(item.id)
}
const entry: ItemEntry = {
id: item.id,
obb,
...aabb
}
this.items.set(item.id, entry)
this.spatialIndex.insert(entry)
}
}
// 移除物品
remove(...ids: string[]): void {
for (const id of ids) {
const entry = this.items.get(id)
if (entry) {
this.spatialIndex.remove(entry)
this.items.delete(id)
}
}
}
// 位置查询
getItemsByPosition(x: number, z: number): string[] {
const candidates = this.spatialIndex.search({
minX: x,
minY: z,
maxX: x,
maxY: z
})
const point = new THREE.Vector3(x, 0, z)
return candidates.filter((item) => pointIntersectsOBB(point, item.obb)).map((item) => item.id)
return []
}
// 距离查询
getItemsByDistance(x: number, z: number, distance: number): string[] {
const sphere = new THREE.Sphere(new THREE.Vector3(x, 0, z), distance)
const candidates = this.spatialIndex.search({
minX: x - distance,
minY: z - distance,
maxX: x + distance,
maxY: z + distance
})
return candidates.filter((item) => sphereIntersectsOBB(sphere, item.obb)).map((item) => item.id)
return []
}
// 矩形区域查询(有交集)
getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] {
const rect = {
minX: Math.min(x1, x2),
maxX: Math.max(x1, x2),
minY: Math.min(z1, z2),
maxY: Math.max(z1, z2)
}
const candidates = this.spatialIndex.search(rect)
return candidates.filter((item) => rectIntersectsOBB(rect, item.obb)).map((item) => item.id)
return []
}
}
function pointIntersectsOBB(point: THREE.Vector3, obb: OBB): boolean {
const box = new THREE.Box3()
setBox3FromOBB(box, obb)
return box.containsPoint(point)
}
function sphereIntersectsOBB(sphere: THREE.Sphere, obb: OBB): boolean {
const box = new THREE.Box3()
setBox3FromOBB(box, obb)
return box.intersectsSphere(sphere)
}
function rectIntersectsOBB(rect: { minX: number; minY: number; maxX: number; maxY: number }, obb: OBB): boolean {
// 简化判断:用 AABB 投影到 XZ 平面做矩形交叉检测
const aabb = new THREE.Box3()
setBox3FromOBB(aabb, obb)
const aabb2D = {
minX: aabb.min.x,
minY: aabb.min.z,
maxX: aabb.max.x,
maxY: aabb.max.z
}
return !(
rect.maxX < aabb2D.minX ||
rect.minX > aabb2D.maxX ||
rect.maxY < aabb2D.minY ||
rect.minY > aabb2D.maxY
)
}
export function itemToAABB(item: ItemMetrix): { minX: number; minY: number; maxX: number; maxY: number } {
// 假设所有物品都是立方体,尺寸为 scale.x × scale.z
const x = item.tf[0][0]
const z = item.tf[0][2]
const halfWidth = item.tf[2][0] / 2
const halfDepth = item.tf[2][2] / 2
return {
minX: x - halfWidth,
maxX: x + halfWidth,
minY: z - halfDepth,
maxY: z + halfDepth
}
}
export function itemToOBB(item: ItemMetrix): OBB {
const position = new THREE.Vector3(...item.tf[0])
const rotation = new THREE.Euler(
THREE.MathUtils.degToRad(item.tf[1][0]),
THREE.MathUtils.degToRad(item.tf[1][1]),
THREE.MathUtils.degToRad(item.tf[1][2]),
'XYZ'
)
const scale = new THREE.Vector3(...item.tf[2])
const matrix = new THREE.Matrix4()
.makeRotationFromEuler(rotation)
.premultiply(new THREE.Matrix4().makeTranslation(position.x, position.y, position.z))
.premultiply(new THREE.Matrix4().makeScale(scale.x, scale.y, scale.z))
const obb = new OBB(
new THREE.Vector3(),
new THREE.Vector3(0.5, 0.5, 0.5),
new THREE.Matrix3().setFromMatrix4(matrix)
)
return obb
}
function setBox3FromOBB(box: THREE.Box3, obb: OBB): THREE.Box3 {
const center = obb.center
const halfSize = new THREE.Vector3().copy(obb.halfSize)
const rotation = obb.rotation
// 8 个局部顶点
const vertices = [
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, 1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, 1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, -1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, -1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, 1, -1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, 1, -1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, -1, -1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, -1, -1))
]
// 应用旋转和平移到每个顶点
const worldVertices = vertices.map((v) => {
return v.applyMatrix3(rotation).add(center)
})
// 构造包围盒
box.min.set(Infinity, Infinity, Infinity)
box.max.set(-Infinity, -Infinity, -Infinity)
for (const v of worldVertices) {
box.expandByPoint(v)
}
return box
}

4
src/editor/ModelMain.less

@ -172,6 +172,7 @@
.calc-left-panel {
flex: 1;
overflow: auto;
display: flex;
}
.calc-right-panel {
@ -279,6 +280,7 @@
display: flex;
align-items: center;
flex-direction: row;
.el-button {
margin-left: 5px;
}
@ -289,9 +291,11 @@
background: #dcdcdc;
margin: 0 5px;
}
.el-button-group {
flex: 1;
}
&.section-bottom-toolbar {
justify-content: space-between;

33
src/editor/widgets/modeltree/ModeltreeView.vue

@ -1,26 +1,20 @@
<template>
<div class="title">
<el-input v-model="searchKeyword" size="small" style="flex-grow: 1; margin: 0 5px 0 0;" placeholder="Search">
<template #prefix>
<component :is="renderIcon('element Search')"></component>
</template>
</el-input>
<h3 style="margin-right: 40px;">模型元素</h3>
<el-cascader placeholder="选择楼层" size="small" v-model="currentLevel" :options="calcCatalog"
filterable :show-all-levels="false" clearable
:props="{emitPath:false}"
style="margin-right: 5px; width: 150px;" />
style="margin-right: 5px; flex-grow:1" />
</div>
<div class="calc-left-panel">
<el-tree draggable node-key="id" :highlight-current="true"
:data="treedata" :expand-on-click-node="false" :auto-expand-parent="true"
:allow-drop="allowDrop" :allow-drag="allowDrag"
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
/>
<YvTable ref="grid1" class="grid1" v-model="grid1Data"
:setting="columnSetting"
@onReady="onGrid1Ready"
@rowClick="grid1RowClick">
<template #toolbar>
<el-button type="primary" @click="refreshGrid1" link>刷新</el-button>
</template>
</YvTable>
</div>
</template>
<script>
@ -28,3 +22,10 @@ import ModeltreeViewJs from './ModeltreeViewJs.js'
export default ModeltreeViewJs
</script>
<style lang="less">
.calc-left-panel {
& > .grid1 {
flex-grow: 1;
}
}
</style>

133
src/editor/widgets/modeltree/ModeltreeViewJs.js

@ -1,35 +1,42 @@
import { defineComponent } from 'vue'
import { renderIcon } from '@/utils/webutils.js'
import { defineComponent, markRaw } from 'vue'
import YvTable from '@/components/yvTable/YvTable.vue'
import IWidgets from '../IWidgets.js'
export default defineComponent({
name: 'ModeltreeView',
components: { YvTable },
mixins: [IWidgets],
data() {
return {
searchKeyword: '',
treedata: data
columnSetting: Object.freeze(GRID1_SETTING),
grid1Data: [],
searchKeyword: ''
}
},
mounted() {
},
methods: {
allowDrop(event) {
return true
},
allowDrag(event) {
return true
},
handleDragStart() {
},
handleDragEnter() {
},
handleDragLeave() {
},
handleDragOver() {
refreshGrid1() {
if (this.viewport?.entityManager) {
this.grid1Data = Object.freeze(this.viewport.entityManager.getAllEntityForGrid())
} else {
this.grid1Data = []
}
},
handleDragEnd() {
onGrid1Ready() {
this.refreshGrid1()
},
handleDrop() {
grid1RowClick(evt) {
console.log(this.$refs.grid1, evt)
const { data } = evt
if (data?.id) {
this.viewport.selectInspect.selectById(data.id)
this.viewport.cameraToEntity(data.id)
} else {
system.msg('点位没找到:' + data?.id, 'error')
}
}
},
computed: {
@ -57,60 +64,36 @@ export default defineComponent({
}
})
const data = [
{
label: 'Level one 1',
children: [
{
label: 'Level two 1-1',
children: [
{
label: 'Level three 1-1-1'
}
]
const GRID1_SETTING = {
editable: true,
sortable: false,
serverSortable: false,
rowNumber: false,
disableColumnMenu: false,
mode: 'edit2',
rowHeight: 20,
headerHeight: 30,
showToolbar: true,
disableAppendButton: true,
disableDeleteButton: true,
// domLayout: 'autoHeight',
columns: [
{ dataIndex: 't', width: 50, header: 't', editable: false, flex: 1 },
{ dataIndex: 'id', width: 50, header: 'id', editable: false, flex: 1 },
{
dataIndex: 'pos', width: 50, header: '', editable: false, hidden: true,
valueGetter(evt) {
const item = evt.data
return _.get(item, 'tf[0][0]') + ',' + _.get(item, 'tf[0][1]') + ',' + _.get(item, 'tf[0][2]')
}
]
},
{
label: 'Level one 2',
children: [
{
label: 'Level two 2-1',
children: [
{
label: 'Level three 2-1-1'
}
]
},
{
label: 'Level two 2-2',
children: [
{
label: 'Level three 2-2-1'
}
]
}
]
},
{
label: 'Level one 3',
children: [
{
label: 'Level two 3-1',
children: [
{
label: 'Level three 3-1-1'
}
]
},
{
label: 'Level two 3-2',
children: [
{
label: 'Level three 3-2-1'
}
]
},
{
dataIndex: 'size', width: 50, header: '', editable: false, hidden: true,
valueGetter(evt) {
const item = evt.data
return _.get(item, 'tf[2][0]') + ',' + _.get(item, 'tf[2][1]') + ',' + _.get(item, 'tf[2][2]')
}
]
}
]
}
]
}

1
src/modules/gstore/GstoreRenderer.ts

@ -5,7 +5,6 @@ import { type Object3DLike } from '@/types/ModelTypes.ts'
import InstancePointManager from '@/core/manager/InstancePointManager.ts'
import LineSegmentManager from '@/core/manager/LineSegmentManager.ts'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import { itemToOBB } from '@/core/manager/ItemFindManager.ts'
import { getOBBox } from '@/core/ModelUtils.ts'
/**

8
src/types/Types.d.ts

@ -63,3 +63,11 @@ interface CatalogGroup {
*
*/
type Catalog = CatalogGroup[];
interface IControls {
init(viewport: any): void
dispose(): void
animate?: () => void;
}

67
src/utils/webutils.ts

@ -234,3 +234,70 @@ export function renderIcon(icon: string, props = {}): any {
}
return () => h(ElIcon, props, { default: () => h(component) })
}
/**
*
* @param scope vcxt.scope
* @param vjson VJSON, listeners
* @param eventName
* @param args
*/
export function runEvent(scope, vjson, eventName, ...args) {
if (!vjson.listeners) {
return
}
if (typeof vjson.listeners[eventName] === 'string') {
let funcBody = vjson.listeners[eventName]
const me = scope
const methodVarName = []
// // 吧 args 参数添加进去
_.forEach(args, (v, k) => {
methodVarName.push('arg' + k)
})
// 花括号定义模式
if (_.startsWith(funcBody, '{')) {
if (_.endsWith(funcBody, '}')) {
// 剔除花括号
const methodName = funcBody.substring(1, funcBody.length - 1)
// 检查有没有其他符号
if (methodName.includes('+') || methodName.includes('-') || methodName.includes('(') ||
methodName.includes(')') || methodName.includes('*') || methodName.includes('/') ||
methodName.includes('{') || methodName.includes('}') || methodName.includes(' ')) {
console.error('错误表达式:' + funcBody)
return
}
// funcBody = "return this." + funcBody + "(arguments);"
try {
return me[methodName].call(me, ...args)
} catch (e) {
console.error(`页面方法(methodName=${methodName})运行错误`, '页面对象', me, '页面方法对象', me[methodName], '错误', e)
system.showErrorDialog('方法运行错误' + e)
return
}
} else {
console.error('错误表达式:' + funcBody)
return
}
}
const func = Function(...methodVarName, funcBody)
try {
return func.bind(scope)(...args)
} catch (e) {
console.error('runEventError', e)
//@ts-ignore
system.showErrorDialog(e.toString())
return
}
}
if (typeof vjson.listeners[eventName] === 'function') {
//@ts-ignore
return vjson.listeners[eventName].call(scope, this, ...args)?.catch?.(() => {
})
}
}

Loading…
Cancel
Save