| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- <template>
- <div class="value-editor">
- <div class="value-editor-content">
- <!-- 字符串编辑器 -->
- <a-input
- v-if="type === 'string'"
- v-model:value="localValue"
- placeholder="请输入字符串"
- @change="updateValue"
- class="value-input"
- />
-
- <!-- 数字编辑器 -->
- <a-input-number
- v-else-if="type === 'number'"
- v-model:value="localNumberValue"
- placeholder="请输入数字"
- @change="updateValue"
- class="value-input"
- />
-
- <!-- 布尔编辑器 -->
- <a-switch
- v-else-if="type === 'boolean'"
- v-model:checked="localBoolValue"
- @change="updateValue"
- class="boolean-input"
- />
-
- <!-- 对象编辑器 -->
- <key-value-editor
- v-else-if="type === 'object'"
- v-model:modelValue="localObjectValue"
- @update:modelValue="updateValue"
- :forceOneLevel="forceOneLevel"
- class="object-input"
- />
-
- <!-- 数组编辑器 -->
- <array-editor
- v-else-if="type === 'array'"
- v-model:modelValue="localArrayValue"
- @update:modelValue="updateValue"
- :forceOneLevel="forceOneLevel"
- class="array-input"
- />
-
- <!-- null编辑器 -->
- <div v-else-if="type === 'null'" class="null-editor">
- <span class="null-value">null</span>
- </div>
- </div>
- <div class="value-editor-type-selector">
- <a-dropdown @select="changeType">
- <a-button type="text" class="type-select-button">
- {{ type }}
- <down-outlined />
- </a-button>
- <template #overlay>
- <a-menu>
- <a-menu-item key="string">字符串</a-menu-item>
- <a-menu-item key="number">数字</a-menu-item>
- <a-menu-item key="boolean">布尔</a-menu-item>
- <a-menu-item key="object" v-if="!forceOneLevel">对象</a-menu-item>
- <a-menu-item key="array" v-if="!forceOneLevel">数组</a-menu-item>
- <a-menu-item key="null">null</a-menu-item>
- </a-menu>
- </template>
- </a-dropdown>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, watch, computed } from 'vue';
- import { DownOutlined } from '@ant-design/icons-vue';
- import KeyValueEditor from './KeyValueEditor.vue';
- import ArrayEditor from './ArrayEditor.vue';
- const props = defineProps<{
- modelValue?: any;
- /**
- * 这会限制用户只能创建简单的值,而不能嵌套对象或数组。
- * @default false
- */
- forceOneLevel?: boolean;
- }>();
- const emit = defineEmits<{
- (e: 'update:modelValue', value: any): void;
- }>();
- export type LocalItemType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null';
- // 计算当前值的类型
- const type = computed<LocalItemType>(() => {
- const value = props.modelValue;
- if (value === null) {
- return 'null';
- } else if (typeof value === 'string') {
- return 'string';
- } else if (typeof value === 'number') {
- return 'number';
- } else if (typeof value === 'boolean') {
- return 'boolean';
- } else if (Array.isArray(value)) {
- return 'array';
- } else if (typeof value === 'object') {
- return 'object';
- } else {
- return 'string';
- }
- });
- // 本地值
- const localValue = ref<string>(props.modelValue as string || '');
- const localNumberValue = ref<number>(typeof props.modelValue === 'number' ? props.modelValue : 0);
- const localBoolValue = ref<boolean>(!!props.modelValue);
- const localObjectValue = ref<Record<string, any>>(props.modelValue as Record<string, any> || {});
- const localArrayValue = ref<any[]>(props.modelValue as any[] || []);
- // 监听props变化
- watch(
- () => props.modelValue,
- (newValue) => {
- if (type.value === 'string') {
- localValue.value = newValue as string || '';
- } else if (type.value === 'number') {
- localNumberValue.value = typeof newValue === 'number' ? newValue : 0;
- } else if (type.value === 'boolean') {
- localBoolValue.value = !!newValue;
- } else if (type.value === 'object') {
- localObjectValue.value = newValue as Record<string, any> || {};
- } else if (type.value === 'array') {
- localArrayValue.value = newValue as any[] || [];
- }
- },
- { deep: true, immediate: true }
- );
- // 更新值
- const updateValue = () => {
- let value: any;
-
- if (type.value === 'string') {
- value = localValue.value;
- } else if (type.value === 'number') {
- value = localNumberValue.value;
- } else if (type.value === 'boolean') {
- value = localBoolValue.value;
- } else if (type.value === 'object') {
- value = localObjectValue.value;
- } else if (type.value === 'array') {
- value = localArrayValue.value;
- } else if (type.value === 'null') {
- value = null;
- } else {
- value = localValue.value;
- }
-
- emit('update:modelValue', value);
- };
- // 更改类型
- const changeType = (newType: LocalItemType) => {
- let newValue: any;
-
- switch (newType) {
- case 'string':
- newValue = '';
- break;
- case 'number':
- newValue = 0;
- break;
- case 'boolean':
- newValue = false;
- break;
- case 'object':
- newValue = {};
- break;
- case 'array':
- newValue = [];
- break;
- case 'null':
- newValue = null;
- break;
- default:
- newValue = '';
- }
-
- emit('update:modelValue', newValue);
- };
- </script>
- <style scoped>
- .value-editor {
- width: 100%;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .value-editor-content {
- flex: 1;
- min-width: 0;
- }
- .value-input {
- width: 100%;
- }
- .boolean-input {
- margin: 4px 0;
- }
- .object-input,
- .array-input {
- width: 100%;
- }
- .null-editor {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 4px 0;
- }
- .null-value {
- color: #999;
- font-style: italic;
- }
- .value-editor-type-selector {
- white-space: nowrap;
- }
- .type-select-button {
- min-width: 80px;
- text-align: center;
- }
- </style>
|