Files
1818uniapp-admin/src/components/JsonPathPicker/JsonTreeNode.vue

226 lines
4.6 KiB
Vue
Raw Normal View History

<template>
<div class="tree-node">
<template v-if="isObject">
<div class="node-bracket">{</div>
<div class="node-children">
<div v-for="(value, key) in data" :key="key" class="node-item">
<span class="node-key">"{{ key }}"</span>
<span class="node-colon">:</span>
<JsonTreeNode
:data="value"
:path="buildPath(key)"
:fields="fields"
:selected-paths="selectedPaths"
@select="$emit('select', $event)"
/>
<span v-if="!isLastKey(key)" class="node-comma">,</span>
</div>
</div>
<div class="node-bracket">}</div>
</template>
<template v-else-if="isArray">
<div class="node-bracket">[</div>
<div class="node-children">
<div v-for="(item, index) in data" :key="index" class="node-item">
<span class="array-index">[{{ index }}]</span>
<JsonTreeNode
:data="item"
:path="buildArrayPath(index)"
:fields="fields"
:selected-paths="selectedPaths"
@select="$emit('select', $event)"
/>
<span v-if="index < data.length - 1" class="node-comma">,</span>
</div>
</div>
<div class="node-bracket">]</div>
</template>
<template v-else>
<span
class="node-value"
:class="[valueType, { selected: isSelected, clickable: true }]"
@click="handleClick"
:title="'点击选择: ' + path"
>
<template v-if="isString">"{{ data }}"</template>
<template v-else-if="isNull">null</template>
<template v-else>{{ data }}</template>
<span v-if="isSelected" class="selected-badge">{{ selectedFieldLabel }}</span>
</span>
</template>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
data: {
type: [Object, Array, String, Number, Boolean, null],
default: null
},
path: {
type: String,
default: ''
},
fields: {
type: Array,
default: () => []
},
selectedPaths: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['select'])
const isObject = computed(() => {
return props.data !== null && typeof props.data === 'object' && !Array.isArray(props.data)
})
const isArray = computed(() => {
return Array.isArray(props.data)
})
const isString = computed(() => {
return typeof props.data === 'string'
})
const isNull = computed(() => {
return props.data === null
})
const valueType = computed(() => {
if (props.data === null) return 'null'
if (typeof props.data === 'string') return 'string'
if (typeof props.data === 'number') return 'number'
if (typeof props.data === 'boolean') return 'boolean'
return 'unknown'
})
const isSelected = computed(() => {
return Object.values(props.selectedPaths).includes(props.path)
})
const selectedFieldLabel = computed(() => {
for (const [key, value] of Object.entries(props.selectedPaths)) {
if (value === props.path) {
const field = props.fields.find(f => f.key === key)
return field ? field.label.split(' ')[0] : key
}
}
return ''
})
const buildPath = (key) => {
return props.path ? `${props.path}.${key}` : key
}
const buildArrayPath = (index) => {
return `${props.path}[${index}]`
}
const isLastKey = (key) => {
const keys = Object.keys(props.data)
return keys[keys.length - 1] === key
}
const handleClick = () => {
if (props.path) {
emit('select', { path: props.path, value: props.data })
}
}
</script>
<style scoped>
.tree-node {
display: inline;
}
.node-bracket {
color: #8c8c8c;
display: inline;
}
.node-children {
padding-left: 20px;
display: block;
}
.node-item {
display: block;
}
.node-key {
color: #9254de;
font-weight: 500;
}
.node-colon {
color: #8c8c8c;
margin: 0 4px;
}
.node-comma {
color: #8c8c8c;
}
.array-index {
color: #8c8c8c;
font-size: 11px;
margin-right: 4px;
}
.node-value {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 1px 4px;
border-radius: 3px;
transition: all 0.2s;
}
.node-value.clickable {
cursor: pointer;
}
.node-value.clickable:hover {
background: #e6f7ff;
outline: 1px solid #1890ff;
}
.node-value.string {
color: #389e0d;
}
.node-value.number {
color: #1890ff;
}
.node-value.boolean {
color: #eb2f96;
}
.node-value.null {
color: #8c8c8c;
font-style: italic;
}
.node-value.selected {
background: #bae7ff;
outline: 2px solid #1890ff;
}
.selected-badge {
font-size: 10px;
background: #1890ff;
color: #fff;
padding: 1px 6px;
border-radius: 10px;
font-weight: 500;
}
</style>