23 changed files with 1952 additions and 309 deletions
@ -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"; |
||||
|
// 搜索资源引入:结束 |
||||
|
|
||||
|
//光标行背景高亮,配置里面也需要styleActiveLine设置为true |
||||
|
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*tab宽度个空格替换成n个tab字符,默认为false |
||||
|
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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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> |
||||
@ -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: "左固定 <<", |
||||
|
pinRight: "右固定 >>", |
||||
|
noPin: "不要固定", |
||||
|
sum: "总数", |
||||
|
min: "最⼩值", |
||||
|
max: "最⼤值", |
||||
|
none: "⽆", |
||||
|
count: "总", |
||||
|
average: "平均值", |
||||
|
copy: "复制", |
||||
|
copyWithHeaders: "带表头复制", |
||||
|
copyWithGroupHeaders: '带分组表头复制', |
||||
|
ctrlC: "ctrl + C", |
||||
|
paste: "粘贴", |
||||
|
ctrlV: "ctrl + V" |
||||
|
} |
||||
@ -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: "左固定 <<", |
||||
|
pinRight: "右固定 >>", |
||||
|
noPin: "不要固定", |
||||
|
sum: "总数", |
||||
|
min: "最⼩值", |
||||
|
max: "最⼤值", |
||||
|
none: "⽆", |
||||
|
count: "总", |
||||
|
average: "平均值", |
||||
|
copy: "复制", |
||||
|
copyWithHeaders: "带表头复制", |
||||
|
copyWithGroupHeaders: '带分组表头复制', |
||||
|
ctrlC: "ctrl + C", |
||||
|
paste: "粘贴", |
||||
|
ctrlV: "ctrl + V" |
||||
|
} |
||||
@ -1,7 +0,0 @@ |
|||||
export default interface IControls { |
|
||||
init(viewport: any): void |
|
||||
|
|
||||
dispose(): void |
|
||||
|
|
||||
animate?: () => void; |
|
||||
} |
|
||||
@ -1,200 +1,45 @@ |
|||||
import * as THREE from 'three' |
import * as THREE from 'three' |
||||
import rbush from 'rbush' |
|
||||
import { OBB } from 'three/examples/jsm/math/OBB' |
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 { Octree } from 'three/examples/jsm/math/Octree.js'
|
||||
// import { QuadTreeNode } from '@/core/QuadTree.ts'
|
// import { QuadTreeNode } from '@/core/QuadTree.ts'
|
||||
// import { convexHull } from '@/core/ModelUtils.ts'
|
// import { convexHull } from '@/core/ModelUtils.ts'
|
||||
|
|
||||
interface ItemEntry extends rbush.BBox { |
|
||||
id: string |
|
||||
obb: OBB |
|
||||
} |
|
||||
|
|
||||
// 主管理器类
|
// 主管理器类
|
||||
export default class ItemFindManager { |
export default class ItemFindManager { |
||||
private spatialIndex = new rbush<ItemEntry>() |
private items = new Map<string, any>() |
||||
private items = new Map<string, ItemEntry>() |
|
||||
|
|
||||
dispose() { |
dispose() { |
||||
this.spatialIndex.clear() |
|
||||
this.items.clear() |
this.items.clear() |
||||
} |
} |
||||
|
|
||||
constructor() { |
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) |
addOrUpdate(...items: ItemMetrix[]): void { |
||||
} |
|
||||
} |
} |
||||
|
|
||||
// 移除物品
|
// 移除物品
|
||||
remove(...ids: string[]): void { |
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[] { |
getItemsByPosition(x: number, z: number): string[] { |
||||
const candidates = this.spatialIndex.search({ |
return [] |
||||
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) |
|
||||
} |
} |
||||
|
|
||||
// 距离查询
|
// 距离查询
|
||||
getItemsByDistance(x: number, z: number, distance: number): string[] { |
getItemsByDistance(x: number, z: number, distance: number): string[] { |
||||
const sphere = new THREE.Sphere(new THREE.Vector3(x, 0, z), distance) |
return [] |
||||
|
|
||||
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) |
|
||||
} |
} |
||||
|
|
||||
// 矩形区域查询(有交集)
|
// 矩形区域查询(有交集)
|
||||
getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] { |
getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] { |
||||
const rect = { |
return [] |
||||
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) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
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 |
|
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue