Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 63 additions & 53 deletions dashboard/src/components/shared/ObjectEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<v-card-text class="pa-4" style="max-height: 400px; overflow-y: auto;">
<!-- Regular key-value pairs (non-template) -->
<div v-if="nonTemplatePairs.length > 0">
<div v-for="(pair, index) in nonTemplatePairs" :key="index" class="key-value-pair">
<div v-for="pair in nonTemplatePairs" :key="pair._id" class="key-value-pair">
<v-row no-gutters align="center" class="mb-2">
<v-col cols="4">
<v-text-field
Expand All @@ -37,7 +37,8 @@
variant="outlined"
hide-details
:placeholder="t('core.common.objectEditor.placeholders.keyName')"
@blur="updateKey(index, pair.key)"
@focus="pair._originalKey = pair.key"
@blur="onKeyBlur(pair)"
></v-text-field>
</v-col>
<v-col cols="7" class="pl-2 d-flex align-center justify-end">
Expand Down Expand Up @@ -86,7 +87,7 @@
variant="outlined"
hide-details="auto"
:placeholder="t('core.common.objectEditor.placeholders.jsonValue')"
@blur="updateJSON(index, pair.value)"
@blur="validateJSON(pair)"
:error-messages="pair.jsonError"
></v-text-field>
</v-col>
Expand Down Expand Up @@ -221,9 +222,11 @@
<script setup>
import { ref, computed, watch } from 'vue'
import { useI18n, useModuleI18n } from '@/i18n/composables'
import { useToast } from '@/utils/toast'

const { t } = useI18n()
const { tm, getRaw } = useModuleI18n('features/config-metadata')
const { warning: toastWarning } = useToast()

const props = defineProps({
modelValue: {
Expand Down Expand Up @@ -258,6 +261,7 @@ const localKeyValuePairs = ref([])
const originalKeyValuePairs = ref([])
const newKey = ref('')
const newValueType = ref('string')
const nextPairId = ref(0)

// Template schema support
const templateSchema = computed(() => {
Expand All @@ -284,12 +288,26 @@ watch(() => props.modelValue, (newValue) => {
// The dialog-based editing handles internal updates
}, { immediate: true })

function createPair({ key, value, type, slider, template, jsonError = '', _originalKey }) {
return {
_id: nextPairId.value++,
key,
value,
type,
slider,
template,
jsonError,
_originalKey
}
}

function initializeLocalKeyValuePairs() {
localKeyValuePairs.value = []
nextPairId.value = 0
for (const [key, value] of Object.entries(props.modelValue)) {
let _type = (typeof value) === 'object' ? 'json':(typeof value)
let _value = _type === 'json'?JSON.stringify(value):value
let _value = _type === 'json' ? JSON.stringify(value) : value

// Check if this key has a template schema
const template = templateSchema.value[key]
if (template) {
Expand All @@ -300,20 +318,20 @@ function initializeLocalKeyValuePairs() {
_value = template.default !== undefined ? template.default : _value
}
}
localKeyValuePairs.value.push({
key: key,

localKeyValuePairs.value.push(createPair({
key,
value: _value,
type: _type,
slider: template?.slider,
template: template
})
template
}))
}
}

function openDialog() {
initializeLocalKeyValuePairs()
originalKeyValuePairs.value = JSON.parse(JSON.stringify(localKeyValuePairs.value)) // Deep copy
originalKeyValuePairs.value = localKeyValuePairs.value.map(pair => ({ ...pair }))
newKey.value = ''
newValueType.value = 'string'
dialog.value = true
Expand All @@ -324,7 +342,7 @@ function addKeyValuePair() {
if (key !== '') {
const isKeyExists = localKeyValuePairs.value.some(pair => pair.key === key)
if (isKeyExists) {
alert(t('core.common.objectEditor.keyExists'))
toastWarning(t('core.common.objectEditor.keyExists'))
return
}

Expand All @@ -337,28 +355,28 @@ function addKeyValuePair() {
defaultValue = false
break
case 'json':
defaultValue = "{}"
defaultValue = '{}'
break
default: // string
defaultValue = ""
defaultValue = ''
break
}

localKeyValuePairs.value.push({
key: key,
localKeyValuePairs.value.push(createPair({
key,
value: defaultValue,
type: newValueType.value
})
}))
newKey.value = ''
}
}

function updateJSON(index, newValue) {
function validateJSON(pair) {
try {
JSON.parse(newValue)
localKeyValuePairs.value[index].jsonError = ''
JSON.parse(pair.value)
pair.jsonError = ''
} catch (e) {
localKeyValuePairs.value[index].jsonError = t('core.common.objectEditor.invalidJson')
pair.jsonError = t('core.common.objectEditor.invalidJson')
}
}

Expand All @@ -369,39 +387,30 @@ function removeKeyValuePairByKey(key) {
}
}

function updateKey(index, newKey) {
const originalKey = localKeyValuePairs.value[index].key
// 如果键名没有改变,则不执行任何操作
if (originalKey === newKey) return
function onKeyBlur(pair) {
const originalKey = pair._originalKey
const newKey = pair.key
if (originalKey === undefined || originalKey === newKey) return

// 检查新键名是否已存在
const isKeyExists = localKeyValuePairs.value.some((pair, i) => i !== index && pair.key === newKey)
const isKeyExists = localKeyValuePairs.value.some(p => p !== pair && p.key === newKey)
if (isKeyExists) {
// 如果键名已存在,提示用户并恢复原值
alert(t('core.common.objectEditor.keyExists'))
// 将键名恢复为修改前的原始值
localKeyValuePairs.value[index].key = originalKey
toastWarning(t('core.common.objectEditor.keyExists'))
pair.key = originalKey
return
}

// 检查新键名是否有模板
const template = templateSchema.value[newKey]
if (template) {
// 更新类型和默认值
localKeyValuePairs.value[index].type = template.type || localKeyValuePairs.value[index].type
if (localKeyValuePairs.value[index].value === undefined || localKeyValuePairs.value[index].value === null || localKeyValuePairs.value[index].value === '') {
localKeyValuePairs.value[index].value = template.default !== undefined ? template.default : localKeyValuePairs.value[index].value
pair.type = template.type || pair.type
if (pair.value === undefined || pair.value === null || pair.value === '') {
pair.value = template.default !== undefined ? template.default : pair.value
}
localKeyValuePairs.value[index].slider = template.slider
localKeyValuePairs.value[index].template = template
pair.slider = template.slider
pair.template = template
} else {
// 清除模板信息
localKeyValuePairs.value[index].slider = undefined
localKeyValuePairs.value[index].template = undefined
pair.slider = undefined
pair.template = undefined
}

// 更新本地副本
localKeyValuePairs.value[index].key = newKey
}

function isTemplateKeyAdded(templateKey) {
Expand All @@ -420,20 +429,20 @@ function getTemplateValue(templateKey) {
function updateTemplateValue(templateKey, newValue) {
const existingIndex = localKeyValuePairs.value.findIndex(pair => pair.key === templateKey)
const template = templateSchema.value[templateKey]

if (existingIndex >= 0) {
// 更新现有值
localKeyValuePairs.value[existingIndex].value = newValue
} else {
// 添加新字段
let valueType = template?.type || 'string'
localKeyValuePairs.value.push({
const valueType = template?.type || 'string'
localKeyValuePairs.value.push(createPair({
key: templateKey,
value: newValue,
type: valueType,
slider: template?.slider,
template: template
})
template
}))
}
}

Expand All @@ -454,10 +463,10 @@ function getDefaultValueForType(type) {
case 'boolean':
return false
case 'json':
return "{}"
return '{}'
case 'string':
default:
return ""
return ''
}
}

Expand All @@ -481,7 +490,7 @@ function confirmDialog() {
case 'bool':
case 'boolean':
// 布尔值通常由 v-switch 正确处理,但为保险起见可以显式转换
// 注意:在 JavaScript 中,只有严格的 false, 0, "", null, undefined, NaN 会被转换为 false
// 注意:在 JavaScript 中,只有严格的 false, 0, '', null, undefined, NaN 会被转换为 false
// 这里直接赋值 pair.value 应该是安全的,因为 v-model 绑定的就是布尔值
// convertedValue = Boolean(pair.value)
break
Expand All @@ -502,7 +511,7 @@ function confirmDialog() {

function cancelDialog() {
// Reset to original state
localKeyValuePairs.value = JSON.parse(JSON.stringify(originalKeyValuePairs.value))
localKeyValuePairs.value = originalKeyValuePairs.value.map(pair => ({ ...pair }))
dialog.value = false
}

Expand All @@ -529,3 +538,4 @@ function getTemplateTitle(template, templateKey) {
opacity: 0.8;
}
</style>

Loading