feat(系统变量): 支持 key-value #1310

This commit is contained in:
dataeaseShu
2026-08-07 14:58:33 +08:00
committed by dataeaseShu
parent 959285cfd0
commit e2abbc61fa
8 changed files with 290 additions and 21 deletions
+6 -1
View File
@@ -23,6 +23,8 @@
"variable_name": "Variable Name",
"variable_type": "Variable Type",
"variable_value": "Variable Value",
"variable_key": "Variable Key",
"kv": "Key-Value",
"edit_variable": "Edit Variable",
"no_variables_yet": "No Variables Yet",
"please_enter_value": "Please Enter Value",
@@ -30,7 +32,10 @@
"start_date": "Start Date",
"end_date": "End Date",
"enter_variable_name": "Please Enter Variable Name",
"enter_variable_value": "Please Enter Variable Value"
"enter_variable_value": "Please Enter Variable Value",
"enter_variable_key": "Please Enter Variable Key",
"key_value_cannot_be_empty": "Key and value cannot be empty.",
"key_repeat": "Keys cannot be duplicated."
},
"authorized_space": {
"authorized_space": "Authorized Space",
+6 -1
View File
@@ -23,6 +23,8 @@
"variable_name": "변수 이름",
"variable_type": "변수 유형",
"variable_value": "변수 값",
"variable_key": "변수 키",
"kv": "키-값",
"edit_variable": "변수 편집",
"no_variables_yet": "아직 변수가 없습니다",
"please_enter_value": "값을 입력하세요",
@@ -30,7 +32,10 @@
"start_date": "시작일",
"end_date": "종료일",
"enter_variable_name": "변수 이름을 입력하세요",
"enter_variable_value": "변수 값을 입력하세요"
"enter_variable_value": "변수 값을 입력하세요",
"enter_variable_key": "변수 키를 입력하세요",
"key_value_cannot_be_empty": "키와 값은 비워둘 수 없습니다.",
"key_repeat": "키는 중복될 수 없습니다."
},
"authorized_space": {
"authorized_space": "권한 있는 공간",
+6 -1
View File
@@ -23,6 +23,8 @@
"variable_name": "变量名称",
"variable_type": "变量类型",
"variable_value": "变量值",
"variable_key": "变量键",
"kv": "键值",
"edit_variable": "编辑变量",
"no_variables_yet": "暂无变量",
"please_enter_value": "请输入数值",
@@ -30,7 +32,10 @@
"start_date": "开始日期",
"end_date": "结束日期",
"enter_variable_name": "请输入变量名称",
"enter_variable_value": "请输入变量值"
"enter_variable_value": "请输入变量值",
"enter_variable_key": "请输入变量键",
"key_value_cannot_be_empty": "键和值不能为空",
"key_repeat": "键不能重复"
},
"authorized_space": {
"authorized_space": "授权空间",
+6 -1
View File
@@ -23,6 +23,8 @@
"variable_name": "變數名稱",
"variable_type": "變數類型",
"variable_value": "變數值",
"variable_key": "變數鍵",
"kv": "鍵值",
"edit_variable": "編輯變數",
"no_variables_yet": "暫無變數",
"please_enter_value": "請輸入數值",
@@ -30,7 +32,10 @@
"start_date": "開始日期",
"end_date": "結束日期",
"enter_variable_name": "請輸入變數名稱",
"enter_variable_value": "請輸入變數值"
"enter_variable_value": "請輸入變數值",
"enter_variable_key": "請輸入變數鍵",
"key_value_cannot_be_empty": "鍵和值不能為空",
"key_repeat": "鍵不能重複"
},
"authorized_space": {
"authorized_space": "授權空間",
+3
View File
@@ -441,6 +441,9 @@ strong {
.string-variables.string-variables {
color: #3370ff;
}
.kv-variables.kv-variables {
color: #3370ff;
}
.number-variables.number-variables,
.int-variables.int-variables,
.float-variables.float-variables {
@@ -124,6 +124,7 @@ const initEnumOptions = () => {
const iconMap = {
text: field_text,
kv: field_text,
number: field_value,
datetime: field_time,
}
+107 -8
View File
@@ -438,6 +438,23 @@
>
</el-option>
</el-select>
<el-select
v-else-if="
variableValueMap[state.form.system_variables[index].variableId].var_type === 'kv'
"
v-model="state.form.system_variables[index].variableValues"
multiple
style="width: 236px"
:placeholder="$t('datasource.Please_select')"
>
<el-option
v-for="item in getVariableOptionList(state.form.system_variables[index].variableId)"
:key="item.value"
:label="item.label"
:value="item.label"
>
</el-option>
</el-select>
<el-input-number
v-else-if="
variableValueMap[state.form.system_variables[index].variableId].var_type ===
@@ -587,6 +604,7 @@ const selectionLoading = ref(false)
const iconMap = {
text: field_text,
kv: field_text,
number: field_value,
datetime: field_time,
}
@@ -643,6 +661,56 @@ const defaultForm = {
const options = ref<any[]>([])
const variables = shallowRef<any[]>([])
const variableValueMap = shallowRef<any>({})
const getVariableDefinition = (variableId: string) => variableValueMap.value[variableId]
const getVariableOptionList = (variableId: string) => {
const variable = getVariableDefinition(variableId)
if (!variable) return []
if (variable.var_type === 'text') {
return variable.value.map((item: string) => ({
label: item,
value: item,
}))
}
if (variable.var_type === 'kv') {
return variable.value.map((item: any) => ({
label: item.key,
value: item.key,
actualValue: item.value,
}))
}
return []
}
const getSelectedKeysFromStoredValues = (variableId: string, storedValues: any[] = []) => {
const variable = getVariableDefinition(variableId)
if (!variable || variable.var_type !== 'kv') return storedValues
return storedValues
.map((storedValue: any) => {
const matched = variable.value.find(
(item: any) => item.key === storedValue || item.value === storedValue
)
return matched?.key
})
.filter(Boolean)
}
const getStoredValuesFromSelectedKeys = (variableId: string, selectedKeys: any[] = []) => {
const variable = getVariableDefinition(variableId)
if (!variable || variable.var_type !== 'kv') return selectedKeys
return selectedKeys
.map((selectedKey: any) => {
const matched = variable.value.find((item: any) => item.key === selectedKey)
return matched?.value
})
.filter((item: any) => item !== undefined && item !== null && item !== '')
}
const state = reactive<any>({
tableData: [],
filterTexts: [],
@@ -653,7 +721,7 @@ const state = reactive<any>({
pageSize: 20,
total: 0,
sortColumn: '',
sortOrder: '', // 'ascending' | 'descending' | ''
sortOrder: '', // 'ascending' | 'descending' | ''
},
})
@@ -908,20 +976,26 @@ const editHandler = (row: any) => {
...row,
system_variables: (row.system_variables || []).map((ele: any) => ({
...ele,
variableValue: ele.variableValues[0],
variableValue: ele.variableValues?.[0],
variableValues: Array.isArray(ele.variableValues) ? [...ele.variableValues] : [],
})),
}
}
})
.finally(() => {
state.form.system_variables = state.form.system_variables.filter((ele: any) => {
if (variableValueMap.value[ele.variableId]) {
if (variableValueMap.value[ele.variableId].var_type === 'text') {
ele.variableValues = variableValueMap.value[ele.variableId].value.filter(
const variable = variableValueMap.value[ele.variableId]
if (variable) {
if (variable.var_type === 'text') {
ele.variableValues = variable.value.filter(
(item: any) => ele.variableValues.indexOf(item) > -1
)
return !!ele.variableValues.length
}
if (variable.var_type === 'kv') {
ele.variableValues = getSelectedKeysFromStoredValues(ele.variableId, ele.variableValues)
return !!ele.variableValues.length
}
return true
}
return false
@@ -1036,9 +1110,19 @@ const formatVariableValues = () => {
if (!state.form.system_variables?.length) return []
return state.form.system_variables.map((ele: any) => ({
variableId: ele.variableId,
variableValues: ['number', 'datetime'].includes(variableValueMap.value[ele.variableId].var_type)
? [ele.variableValue]
: ele.variableValues,
variableValues: (() => {
const variable = variableValueMap.value[ele.variableId]
if (!variable) {
return ele.variableValues
}
if (['number', 'datetime'].includes(variable.var_type)) {
return [ele.variableValue]
}
if (variable.var_type === 'kv') {
return getStoredValuesFromSelectedKeys(ele.variableId, ele.variableValues)
}
return ele.variableValues
})(),
}))
}
@@ -1097,11 +1181,26 @@ const validateSystemVariables = () => {
if (system_variables?.length) {
return system_variables.some((ele: any) => {
const obj = variableValueMap.value[ele.variableId]
if (!obj) {
return false
}
if (obj.var_type === 'text' && !ele.variableValues.length) {
ElMessage.error(t('variables.​​cannot_be_empty'))
return true
}
if (obj.var_type === 'kv') {
if (!ele.variableValues.length) {
ElMessage.error(t('variables.key_value_cannot_be_empty'))
return true
}
if (new Set(ele.variableValues).size !== ele.variableValues.length) {
ElMessage.error(t('variables.key_repeat'))
return true
}
}
if (obj.var_type === 'number' && [null, undefined, ''].includes(ele.variableValue)) {
ElMessage.error(t('variables.​​cannot_be_empty'))
return true
+155 -9
View File
@@ -26,6 +26,7 @@ const oldKeywords = ref('')
const searchLoading = ref(false)
const iconMap = {
text: field_text,
kv: field_text,
number: field_value,
datetime: field_time,
}
@@ -49,6 +50,7 @@ const pageInfo = reactive({
const var_type = {
text: 'model.text',
kv: 'variables.kv',
number: 'model.number',
datetime: 'variables.date',
} as Record<string, unknown>
@@ -62,6 +64,47 @@ const defaultForm = {
}
const pageForm = ref<Form>(cloneDeep(defaultForm))
const createKvValueItem = () => ({
key: '',
value: '',
})
const formatVariableDisplayValue = (row: any) => {
if (row.type === 'system') {
return t('variables.built_in')
}
if (row.var_type === 'text') {
return Array.isArray(row.value) ? row.value.join(', ') : (row.value ?? '')
}
if (row.var_type === 'kv') {
return Array.isArray(row.value)
? row.value.map((item: any) => `${item.key}: ${item.value}`).join(', ')
: ''
}
if (Array.isArray(row.value)) {
return row.value.join(' ~ ')
}
return row.value ?? ''
}
const normalizeKvValue = (value: any[] = []) => {
return value.map((item: any) => ({
key: `${item?.key ?? ''}`.trim(),
value: `${item?.value ?? ''}`.trim(),
}))
}
const getVariableTypeOptions = () => [
{ value: 'text', label: t('model.text') },
{ value: 'kv', label: t('variables.kv') },
{ value: 'number', label: t('model.number') },
{ value: 'datetime', label: t('variables.date') },
]
const cancelDelete = () => {
handleToggleRowSelection(false)
multipleSelectionAll.value = []
@@ -157,7 +200,8 @@ const search = ($event: any = {}) => {
toggleRowLoading.value = true
fieldList.value = res.items.map((ele: any) => ({
...ele,
value: ele.type === 'system' ? t('variables.built_in') : ele.value,
displayValue: formatVariableDisplayValue(ele),
rawValue: ele.value,
}))
pageInfo.total = res.total
searchLoading.value = false
@@ -189,9 +233,31 @@ const validateValue = (_: any, value: any, callback: any) => {
}
return
}
if (var_type === 'kv') {
if (!Array.isArray(value) || !value.length) {
callback(new Error(t('variables.key_value_cannot_be_empty')))
return
}
const normalized = normalizeKvValue(value)
if (normalized.some((ele: any) => !ele.key || !ele.value)) {
callback(new Error(t('variables.key_value_cannot_be_empty')))
return
}
const keyList = normalized.map((ele: any) => ele.key)
if (new Set(keyList).size !== keyList.length) {
callback(new Error(t('variables.key_repeat')))
return
}
callback()
return
}
if (var_type === 'datetime') {
if (value === null) {
callback(new Error(t('datasource.please_enter') + t('common.empty') + t('variables.date')))
return
}
}
if (value.some((ele: any) => ele === '' || ele === null)) {
@@ -227,10 +293,38 @@ const varTypeChange = (val: any) => {
pageForm.value.value = ['']
return
}
if (val === 'kv') {
pageForm.value.value = [createKvValueItem()]
return
}
pageForm.value.value = ['', '']
}
const validateBeforeSave = () => {
if (pageForm.value.var_type !== 'kv') {
return true
}
const normalized = normalizeKvValue(pageForm.value.value)
if (!normalized.length || normalized.some((ele: any) => !ele.key || !ele.value)) {
ElMessage.error(t('variables.key_value_cannot_be_empty'))
return false
}
const keyList = normalized.map((ele: any) => ele.key)
if (new Set(keyList).size !== keyList.length) {
ElMessage.error(t('variables.key_repeat'))
return false
}
return true
}
const saveHandler = () => {
if (!validateBeforeSave()) {
return
}
termFormRef.value.validate((res: any) => {
if (res) {
const obj = unref(pageForm)
@@ -242,6 +336,10 @@ const saveHandler = () => {
obj.value = [...new Set(obj.value)]
}
if (obj.var_type === 'kv') {
obj.value = normalizeKvValue(obj.value)
}
if (obj.var_type === 'number') {
const [min = 0, max = 0] = obj.value
if (min > max) {
@@ -273,7 +371,7 @@ const editHandler = (row: any) => {
pageForm.value.id = id
pageForm.value.name = name
pageForm.value.var_type = var_type
pageForm.value.value = cloneDeep(value)
pageForm.value.value = cloneDeep(row.rawValue ?? value)
}
dialogTitle.value = row?.id ? t('variables.edit_variable') : t('variables.add_variable')
dialogFormVisible.value = true
@@ -388,7 +486,11 @@ const handleCurrentChange = (val: number) => {
width="420"
:label="$t('variables.variable_value')"
show-overflow-tooltip
/>
>
<template #default="scope">
{{ scope.row.displayValue }}
</template>
</el-table-column>
<el-table-column fixed="right" width="80" :label="t('ds.actions')">
<template #default="scope">
<div class="field-comment">
@@ -509,12 +611,8 @@ const handleCurrentChange = (val: number) => {
:disabled="!!pageForm.id"
@change="varTypeChange"
>
<el-radio value="text">{{ $t('model.text') }}</el-radio>
<el-radio value="number">
{{ $t('model.number') }}
</el-radio>
<el-radio value="datetime">
{{ $t('variables.date') }}
<el-radio v-for="ele in getVariableTypeOptions()" :key="ele.value" :value="ele.value">
{{ ele.label }}
</el-radio>
</el-radio-group>
</el-form-item>
@@ -562,6 +660,50 @@ const handleCurrentChange = (val: number) => {
</el-form-item>
</div>
</el-form-item>
<el-form-item v-else-if="pageForm.var_type === 'kv'" :label="t('variables.variable_value')">
<template #label>
<div style="display: flex; align-items: center; height: 22px">
<span>{{ t('variables.variable_value') }}</span>
<span class="btn" @click="pageForm.value.push(createKvValueItem())">
<el-icon style="margin-right: 4px" size="16">
<icon_add_outlined></icon_add_outlined>
</el-icon>
{{ $t('model.add') }}
</span>
</div>
</template>
<div class="value-list">
<div class="item kv-title">
<span style="width: calc(48% - 0px)">{{ t('variables.variable_key') }}</span>
<span style="width: calc(52% - 5px)">{{ t('variables.variable_value') }}</span>
</div>
<div v-for="(_, index) in pageForm.value" :key="index" class="item kv-item">
<el-input
v-model="pageForm.value[index].key"
:placeholder="$t('variables.enter_variable_key')"
autocomplete="off"
maxlength="50"
clearable
/>
<el-input
v-model="pageForm.value[index].value"
:placeholder="$t('variables.enter_variable_value')"
autocomplete="off"
maxlength="50"
style="margin-left: 16px"
clearable
/>
<el-icon
class="action-btn"
:class="index === 0 && pageForm.value.length === 1 && 'not-allow'"
size="16"
@click="deleteValues(index)"
>
<IconOpeDelete></IconOpeDelete>
</el-icon>
</div>
</div>
</el-form-item>
<el-form-item v-else prop="value" :label="t('variables.variable_value')">
<div class="value-number_date">
<template v-if="pageForm.var_type === 'number'">
@@ -818,6 +960,10 @@ const handleCurrentChange = (val: number) => {
color: #bbbfc4;
}
}
&.kv-item {
margin-bottom: 8px;
}
}
}
.value-number_date {