DynamicDataEditor.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <template>
  2. <div class="dynamic-data-editor">
  3. <LinkedinOutlined class="icon" />
  4. <a-form class="form" layout="vertical" size="small">
  5. <a-form-item label="数据源类型">
  6. <a-select
  7. :value="currentType"
  8. style="width: 100%"
  9. :options="typeOptions"
  10. placeholder="选择类型"
  11. @change="onTypeChange"
  12. />
  13. </a-form-item>
  14. <template v-if="currentType === 'commonContent'">
  15. <a-form-item label="模型 ID (modelId)">
  16. <a-input-number
  17. :value="(modelValue as IHomeCommonCategoryDynamicDataCommonContent)?.params?.modelId"
  18. style="width: 100%"
  19. :min="1"
  20. placeholder="模型ID"
  21. @update:value="(v: number | undefined) => setCommonContent('modelId', v)"
  22. />
  23. </a-form-item>
  24. <a-form-item label="栏目 ID (mainBodyColumnId)">
  25. <a-input
  26. :value="mainBodyColumnIdDisplay('commonContent')"
  27. placeholder="数字或逗号分隔的多个ID,如 320 或 315,316"
  28. @change="(e: Event) => setMainBodyColumnId('commonContent', (e.target as HTMLInputElement)?.value)"
  29. />
  30. </a-form-item>
  31. <a-form-item label="其他参数 (otherParams)">
  32. <KeyValueEditor
  33. :modelValue="(modelValue as IHomeCommonCategoryDynamicDataCommonContent)?.otherParams"
  34. @update:modelValue="(v: Record<string, any>) => setOtherParams(v)"
  35. />
  36. </a-form-item>
  37. </template>
  38. <template v-else-if="currentType === 'detailContent'">
  39. <a-form-item label="内容 ID">
  40. <a-input
  41. :value="(modelValue as IHomeCommonCategoryDynamicDataDetailContent)?.params?.id"
  42. @change="(e: Event) => setDetailContent('id', Number((e.target as HTMLInputElement)?.value))"
  43. />
  44. </a-form-item>
  45. <a-form-item label="模型 ID (modelId)">
  46. <a-input-number
  47. :value="(modelValue as IHomeCommonCategoryDynamicDataDetailContent)?.params?.modelId"
  48. style="width: 100%"
  49. :min="1"
  50. placeholder="可选,模型ID"
  51. @update:value="(v: number | undefined) => setDetailContent('modelId', v)"
  52. />
  53. </a-form-item>
  54. <a-form-item label="其他参数 (otherParams)">
  55. <KeyValueEditor
  56. :modelValue="(modelValue as IHomeCommonCategoryDynamicDataCommonContent)?.otherParams"
  57. @update:modelValue="(v: Record<string, any>) => setOtherParams(v)"
  58. />
  59. </a-form-item>
  60. </template>
  61. <template v-else-if="currentType === 'parentKey'">
  62. <a-form-item label="读取键">
  63. <a-input
  64. :value="(modelValue as IHomeCommonCategoryDynamicDataParentKey)?.key"
  65. @change="(e: Event) => setParentKey((e.target as HTMLInputElement)?.value)"
  66. />
  67. </a-form-item>
  68. </template>
  69. <template v-else-if="currentType === 'serializedApi'">
  70. <a-form-item label="序列化接口名称 (name)">
  71. <a-select
  72. :value="(modelValue as IHomeCommonCategoryDynamicDataSerializedApi)?.name"
  73. style="width: 100%"
  74. show-search
  75. :filter-option="filterOption"
  76. :options="serializedApiOptions"
  77. placeholder="选择或输入接口名"
  78. @change="(v: string) => setSerializedApi('name', v)"
  79. />
  80. </a-form-item>
  81. <a-form-item label="栏目 ID (params.mainBodyColumnId)">
  82. <a-input
  83. :value="mainBodyColumnIdDisplay('serializedApi')"
  84. placeholder="可选,数字或逗号分隔"
  85. @change="(e: Event) => setSerializedApiMainBodyColumnId((e.target as HTMLInputElement)?.value)"
  86. />
  87. </a-form-item>
  88. <a-form-item label="其他参数 (otherParams)">
  89. <KeyValueEditor
  90. :modelValue="(modelValue as IHomeCommonCategoryDynamicDataSerializedApi)?.otherParams"
  91. @update:modelValue="(v: Record<string, any>) => setOtherParams(v)"
  92. />
  93. </a-form-item>
  94. </template>
  95. <template v-else-if="currentType === 'request'">
  96. <a-form-item label="是否添加token (addToken)">
  97. <a-switch
  98. :checked="(modelValue as IHomeCommonCategoryDynamicDataRequest)?.addToken"
  99. @change="(v: boolean) => setRequestAddToken(v)"
  100. />
  101. </a-form-item>
  102. <a-form-item label="请求方法 (method)">
  103. <a-select
  104. :value="(modelValue as IHomeCommonCategoryDynamicDataRequest)?.method"
  105. style="width: 100%"
  106. :options="methodOptions"
  107. @change="(v: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE') => setRequest('method', v)"
  108. />
  109. </a-form-item>
  110. <a-form-item label="请求 URL (url)">
  111. <a-input
  112. :value="(modelValue as IHomeCommonCategoryDynamicDataRequest)?.url"
  113. placeholder="https://..."
  114. @change="(e: Event) => setRequest('url', (e.target as HTMLInputElement)?.value)"
  115. />
  116. </a-form-item>
  117. <a-form-item label="查询参数 (querys) JSON">
  118. <KeyValueEditor
  119. :modelValue="(modelValue as IHomeCommonCategoryDynamicDataRequest)?.querys"
  120. @update:modelValue="(v: Record<string, any>) => setRequest('querys', v)"
  121. />
  122. </a-form-item>
  123. <a-form-item label="请求体参数 (params) JSON">
  124. <KeyValueEditor
  125. :modelValue="(modelValue as IHomeCommonCategoryDynamicDataRequest)?.otherParams"
  126. @update:modelValue="(v: Record<string, any>) => setRequest('otherParams', v)"
  127. />
  128. </a-form-item>
  129. <a-form-item label="自定义数据处理脚本 (dataSolve)">
  130. <a-input
  131. :value="(modelValue as IHomeCommonCategoryDynamicDataRequest)?.dataSolve"
  132. @change="(e: Event) => setRequestDataSolve((e.target as HTMLInputElement)?.value)"
  133. />
  134. </a-form-item>
  135. </template>
  136. <template v-else-if="currentType === 'staticData'">
  137. <a-form-item label="静态数据 (data)">
  138. <KeyValueEditor
  139. :modelValue="{ array: (modelValue as IHomeCommonCategoryDynamicDataStaticData)?.data }"
  140. @update:modelValue="(v: Record<string, any>) => setStaticData('data', v.array)"
  141. />
  142. </a-form-item>
  143. </template>
  144. </a-form>
  145. </div>
  146. </template>
  147. <script setup lang="ts">
  148. import { computed } from 'vue';
  149. import type {
  150. IHomeCommonCategoryDynamicData,
  151. IHomeCommonCategoryDynamicDataCommonContent,
  152. IHomeCommonCategoryDynamicDataSerializedApi,
  153. IHomeCommonCategoryDynamicDataRequest,
  154. IHomeCommonCategoryDynamicDataDetailContent,
  155. IHomeCommonCategoryDynamicDataParentKey,
  156. IHomeCommonCategoryDynamicDataStaticData,
  157. } from '@/pages/article/data/CommonCategoryDynamicData';
  158. import { SerializedApiMap } from '@/pages/article/data/CommonCategoryDynamicData';
  159. import KeyValueEditor from './KeyValueEditor.vue';
  160. import { LinkedinOutlined } from '@ant-design/icons-vue';
  161. const props = defineProps<{
  162. modelValue?: IHomeCommonCategoryDynamicData | null;
  163. }>();
  164. const emit = defineEmits<{
  165. (e: 'update:modelValue', v: IHomeCommonCategoryDynamicData | undefined): void;
  166. }>();
  167. const typeOptions = [
  168. { value: 'commonContent', label: '通用内容 (commonContent)' },
  169. { value: 'detailContent', label: '详情内容 (detailContent)' },
  170. { value: 'serializedApi', label: '序列化接口 (serializedApi)' },
  171. { value: 'parentKey', label: '从父键读 (parentKey)' },
  172. { value: 'request', label: '请求 (request)' },
  173. ];
  174. const methodOptions = [
  175. { value: 'GET', label: 'GET' },
  176. { value: 'POST', label: 'POST' },
  177. { value: 'PUT', label: 'PUT' },
  178. { value: 'DELETE', label: 'DELETE' },
  179. { value: 'HEAD', label: 'HEAD' },
  180. { value: 'OPTIONS', label: 'OPTIONS' },
  181. ];
  182. const serializedApiOptions = computed(() =>
  183. Object.keys(SerializedApiMap).map((name) => ({ value: name, label: `${name} (${SerializedApiMap[name].description})` }))
  184. );
  185. const currentType = computed(() => props.modelValue?.type ?? null);
  186. function filterOption(input: string, option: { value: string; label: string }) {
  187. return option.label.toLowerCase().includes(input.toLowerCase());
  188. }
  189. function mainBodyColumnIdDisplay(source: 'commonContent' | 'serializedApi'): string {
  190. const val = source === 'commonContent'
  191. ? (props.modelValue as IHomeCommonCategoryDynamicDataCommonContent)?.params?.mainBodyColumnId
  192. : (props.modelValue as IHomeCommonCategoryDynamicDataSerializedApi)?.params?.mainBodyColumnId;
  193. if (val == null) return '';
  194. if (Array.isArray(val)) return val.join(',');
  195. return String(val);
  196. }
  197. function parseMainBodyColumnId(input: string): string | number | number[] | undefined {
  198. const s = input?.trim();
  199. if (!s) return undefined;
  200. const parts = s.split(',').map((p) => p.trim()).filter(Boolean);
  201. if (parts.length === 0) return undefined;
  202. if (parts.length === 1) {
  203. const n = Number(parts[0]);
  204. return Number.isNaN(n) ? parts[0] : n;
  205. }
  206. const nums = parts.map((p) => Number(p));
  207. return nums.every((n) => !Number.isNaN(n)) ? nums : undefined;
  208. }
  209. function onTypeChange(type: 'commonContent' | 'serializedApi' | 'request') {
  210. if (type === 'commonContent') {
  211. emit('update:modelValue', {
  212. type: 'commonContent',
  213. params: { modelId: 1, mainBodyColumnId: undefined, typeId: undefined },
  214. otherParams: {},
  215. });
  216. } else if (type === 'serializedApi') {
  217. emit('update:modelValue', {
  218. type: 'serializedApi',
  219. name: Object.keys(SerializedApiMap)[0] ?? 'ProjectsContent',
  220. otherParams: {},
  221. });
  222. } else if (type === 'request') {
  223. emit('update:modelValue', {
  224. type: 'request',
  225. method: 'GET',
  226. url: '',
  227. otherParams: {},
  228. });
  229. } else if (type === 'detailContent') {
  230. emit('update:modelValue', {
  231. type: 'detailContent',
  232. params: { id: 0, modelId: 0 },
  233. otherParams: {},
  234. });
  235. } else if (type === 'parentKey') {
  236. emit('update:modelValue', {
  237. type: 'parentKey',
  238. key: '',
  239. });
  240. }
  241. }
  242. function setCommonContent(key: 'modelId' | 'typeId'|'otherParams', value: number | Record<string, any> | undefined) {
  243. const cur = props.modelValue as IHomeCommonCategoryDynamicDataCommonContent | undefined;
  244. if (!cur || cur.type !== 'commonContent') return;
  245. const next = { ...cur, params: { ...cur.params, [key]: value } };
  246. emit('update:modelValue', next);
  247. }
  248. function setOtherParams(value: Record<string, any> | undefined) {
  249. const cur = props.modelValue;
  250. const next = { ...cur, otherParams: value } as IHomeCommonCategoryDynamicData ;
  251. emit('update:modelValue', next);
  252. }
  253. function setMainBodyColumnId(source: 'commonContent' | 'serializedApi', input: string) {
  254. const parsed = parseMainBodyColumnId(input);
  255. if (source === 'commonContent') {
  256. const cur = props.modelValue as IHomeCommonCategoryDynamicDataCommonContent | undefined;
  257. if (!cur || cur.type !== 'commonContent') return;
  258. emit('update:modelValue', { ...cur, params: { ...cur.params, mainBodyColumnId: parsed } });
  259. } else {
  260. const cur = props.modelValue as IHomeCommonCategoryDynamicDataSerializedApi | undefined;
  261. if (!cur || cur.type !== 'serializedApi') return;
  262. emit('update:modelValue', { ...cur, params: { ...cur.params, mainBodyColumnId: parsed } });
  263. }
  264. }
  265. function setParentKey(key: string) {
  266. const cur = props.modelValue as IHomeCommonCategoryDynamicDataParentKey | undefined;
  267. if (!cur || cur.type !== 'parentKey') return;
  268. emit('update:modelValue', { ...cur, key });
  269. }
  270. function setSerializedApi(key: 'name'|'otherParams', value: any) {
  271. const cur = props.modelValue as IHomeCommonCategoryDynamicDataSerializedApi | undefined;
  272. if (!cur || cur.type !== 'serializedApi') return;
  273. emit('update:modelValue', { ...cur, [key]: value });
  274. }
  275. function setSerializedApiMainBodyColumnId(input: string) {
  276. const parsed = parseMainBodyColumnId(input);
  277. const cur = props.modelValue as IHomeCommonCategoryDynamicDataSerializedApi | undefined;
  278. if (!cur || cur.type !== 'serializedApi') return;
  279. const params = { ...cur.params, mainBodyColumnId: parsed };
  280. emit('update:modelValue', { ...cur, params: Object.keys(params).length ? params : undefined });
  281. }
  282. function setRequest(key: 'method' | 'url'|'otherParams'|'querys', value: string|Record<string, any>) {
  283. const cur = props.modelValue as IHomeCommonCategoryDynamicDataRequest | undefined;
  284. if (!cur || cur.type !== 'request') return;
  285. emit('update:modelValue', { ...cur, [key]: value });
  286. }
  287. function setDetailContent(key: 'id' | 'modelId', value: number | undefined) {
  288. const cur = props.modelValue as IHomeCommonCategoryDynamicDataDetailContent | undefined;
  289. if (!cur || cur.type !== 'detailContent') return;
  290. const next = { ...cur, params: { ...cur.params, [key]: value } };
  291. emit('update:modelValue', next);
  292. }
  293. function setRequestDataSolve(value: string) {
  294. const cur = props.modelValue as IHomeCommonCategoryDynamicDataRequest | undefined;
  295. if (!cur || cur.type !== 'request') return;
  296. emit('update:modelValue', { ...cur, dataSolve: value });
  297. }
  298. function setRequestAddToken(value: boolean) {
  299. const cur = props.modelValue as IHomeCommonCategoryDynamicDataRequest | undefined;
  300. if (!cur || cur.type !== 'request') return;
  301. emit('update:modelValue', { ...cur, addToken: value });
  302. }
  303. function setStaticData(key: 'data', value: Record<string, any>[]) {
  304. const cur = props.modelValue as IHomeCommonCategoryDynamicDataStaticData | undefined;
  305. if (!cur || cur.type !== 'staticData') return;
  306. emit('update:modelValue', { ...cur, [key]: value });
  307. }
  308. </script>
  309. <style lang="scss" scoped>
  310. .dynamic-data-editor {
  311. font-size: 12px;
  312. display: flex;
  313. align-items: flex-start;
  314. gap: 8px;
  315. .form {
  316. flex: 1;
  317. }
  318. .icon {
  319. color: #c55900;
  320. font-size: 16px;
  321. margin-top: 5px;
  322. }
  323. }
  324. </style>