ValueEditor.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <template>
  2. <div class="value-editor">
  3. <div class="value-editor-content">
  4. <!-- 字符串编辑器 -->
  5. <a-input
  6. v-if="type === 'string'"
  7. v-model:value="localValue"
  8. placeholder="请输入字符串"
  9. @change="updateValue"
  10. class="value-input"
  11. />
  12. <!-- 数字编辑器 -->
  13. <a-input-number
  14. v-else-if="type === 'number'"
  15. v-model:value="localNumberValue"
  16. placeholder="请输入数字"
  17. @change="updateValue"
  18. class="value-input"
  19. />
  20. <!-- 布尔编辑器 -->
  21. <a-switch
  22. v-else-if="type === 'boolean'"
  23. v-model:checked="localBoolValue"
  24. @change="updateValue"
  25. class="boolean-input"
  26. />
  27. <!-- 对象编辑器 -->
  28. <key-value-editor
  29. v-else-if="type === 'object'"
  30. v-model:modelValue="localObjectValue"
  31. @update:modelValue="updateValue"
  32. :forceOneLevel="forceOneLevel"
  33. class="object-input"
  34. />
  35. <!-- 数组编辑器 -->
  36. <array-editor
  37. v-else-if="type === 'array'"
  38. v-model:modelValue="localArrayValue"
  39. @update:modelValue="updateValue"
  40. :forceOneLevel="forceOneLevel"
  41. class="array-input"
  42. />
  43. <!-- null编辑器 -->
  44. <div v-else-if="type === 'null'" class="null-editor">
  45. <span class="null-value">null</span>
  46. </div>
  47. </div>
  48. <div class="value-editor-type-selector">
  49. <a-dropdown @select="changeType">
  50. <a-button type="text" class="type-select-button">
  51. {{ type }}
  52. <down-outlined />
  53. </a-button>
  54. <template #overlay>
  55. <a-menu>
  56. <a-menu-item key="string">字符串</a-menu-item>
  57. <a-menu-item key="number">数字</a-menu-item>
  58. <a-menu-item key="boolean">布尔</a-menu-item>
  59. <a-menu-item key="object" v-if="!forceOneLevel">对象</a-menu-item>
  60. <a-menu-item key="array" v-if="!forceOneLevel">数组</a-menu-item>
  61. <a-menu-item key="null">null</a-menu-item>
  62. </a-menu>
  63. </template>
  64. </a-dropdown>
  65. </div>
  66. </div>
  67. </template>
  68. <script setup lang="ts">
  69. import { ref, watch, computed } from 'vue';
  70. import { DownOutlined } from '@ant-design/icons-vue';
  71. import KeyValueEditor from './KeyValueEditor.vue';
  72. import ArrayEditor from './ArrayEditor.vue';
  73. const props = defineProps<{
  74. modelValue?: any;
  75. /**
  76. * 这会限制用户只能创建简单的值,而不能嵌套对象或数组。
  77. * @default false
  78. */
  79. forceOneLevel?: boolean;
  80. }>();
  81. const emit = defineEmits<{
  82. (e: 'update:modelValue', value: any): void;
  83. }>();
  84. export type LocalItemType = 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null';
  85. // 计算当前值的类型
  86. const type = computed<LocalItemType>(() => {
  87. const value = props.modelValue;
  88. if (value === null) {
  89. return 'null';
  90. } else if (typeof value === 'string') {
  91. return 'string';
  92. } else if (typeof value === 'number') {
  93. return 'number';
  94. } else if (typeof value === 'boolean') {
  95. return 'boolean';
  96. } else if (Array.isArray(value)) {
  97. return 'array';
  98. } else if (typeof value === 'object') {
  99. return 'object';
  100. } else {
  101. return 'string';
  102. }
  103. });
  104. // 本地值
  105. const localValue = ref<string>(props.modelValue as string || '');
  106. const localNumberValue = ref<number>(typeof props.modelValue === 'number' ? props.modelValue : 0);
  107. const localBoolValue = ref<boolean>(!!props.modelValue);
  108. const localObjectValue = ref<Record<string, any>>(props.modelValue as Record<string, any> || {});
  109. const localArrayValue = ref<any[]>(props.modelValue as any[] || []);
  110. // 监听props变化
  111. watch(
  112. () => props.modelValue,
  113. (newValue) => {
  114. if (type.value === 'string') {
  115. localValue.value = newValue as string || '';
  116. } else if (type.value === 'number') {
  117. localNumberValue.value = typeof newValue === 'number' ? newValue : 0;
  118. } else if (type.value === 'boolean') {
  119. localBoolValue.value = !!newValue;
  120. } else if (type.value === 'object') {
  121. localObjectValue.value = newValue as Record<string, any> || {};
  122. } else if (type.value === 'array') {
  123. localArrayValue.value = newValue as any[] || [];
  124. }
  125. },
  126. { deep: true, immediate: true }
  127. );
  128. // 更新值
  129. const updateValue = () => {
  130. let value: any;
  131. if (type.value === 'string') {
  132. value = localValue.value;
  133. } else if (type.value === 'number') {
  134. value = localNumberValue.value;
  135. } else if (type.value === 'boolean') {
  136. value = localBoolValue.value;
  137. } else if (type.value === 'object') {
  138. value = localObjectValue.value;
  139. } else if (type.value === 'array') {
  140. value = localArrayValue.value;
  141. } else if (type.value === 'null') {
  142. value = null;
  143. } else {
  144. value = localValue.value;
  145. }
  146. emit('update:modelValue', value);
  147. };
  148. // 更改类型
  149. const changeType = (newType: LocalItemType) => {
  150. let newValue: any;
  151. switch (newType) {
  152. case 'string':
  153. newValue = '';
  154. break;
  155. case 'number':
  156. newValue = 0;
  157. break;
  158. case 'boolean':
  159. newValue = false;
  160. break;
  161. case 'object':
  162. newValue = {};
  163. break;
  164. case 'array':
  165. newValue = [];
  166. break;
  167. case 'null':
  168. newValue = null;
  169. break;
  170. default:
  171. newValue = '';
  172. }
  173. emit('update:modelValue', newValue);
  174. };
  175. </script>
  176. <style scoped>
  177. .value-editor {
  178. width: 100%;
  179. display: flex;
  180. align-items: center;
  181. gap: 8px;
  182. }
  183. .value-editor-content {
  184. flex: 1;
  185. min-width: 0;
  186. }
  187. .value-input {
  188. width: 100%;
  189. }
  190. .boolean-input {
  191. margin: 4px 0;
  192. }
  193. .object-input,
  194. .array-input {
  195. width: 100%;
  196. }
  197. .null-editor {
  198. display: flex;
  199. align-items: center;
  200. gap: 12px;
  201. padding: 4px 0;
  202. }
  203. .null-value {
  204. color: #999;
  205. font-style: italic;
  206. }
  207. .value-editor-type-selector {
  208. white-space: nowrap;
  209. }
  210. .type-select-button {
  211. min-width: 80px;
  212. text-align: center;
  213. }
  214. </style>