diff --git a/package.json b/package.json index 8baae5ef5..3d5e2bc3d 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "file-saver": "^2.0.5", "highlight.js": "^11.9.0", "js-beautify": "^1.14.11", + "json-editor-vue": "^0.11.2", "leaflet": "^1.9.4", "localforage": "^1.10.0", "lodash-es": "^4.17.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f12d03b21..5d7c19752 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,6 +107,9 @@ dependencies: js-beautify: specifier: ^1.14.11 version: 1.14.11 + json-editor-vue: + specifier: ^0.11.2 + version: 0.11.2(@lezer/common@1.1.2)(vue@3.3.13) leaflet: specifier: ^1.9.4 version: 1.9.4 @@ -6764,6 +6767,10 @@ packages: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} dev: false + /immutable-json-patch@5.1.3: + resolution: {integrity: sha512-95AsF9hJTPpwtBGAnHmw57PASL672tb+vGHR5xLhH2VPuHSsLho7grjlfgQ65DIhHP+UmLCjdmuuA6L1ndJbZg==} + dev: false + /immutable-json-patch@6.0.0: resolution: {integrity: sha512-kBI1s6OAoWI0yRfjPr+zTAMqBJRPZeyfkG3CAHhM/S5RYO+EXoaiVmEpYYMvdiAS8GoEJbuKvwcfy38TrRlcuQ==} dev: false @@ -7136,6 +7143,24 @@ packages: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true + /json-editor-vue@0.11.2(@lezer/common@1.1.2)(vue@3.3.13): + resolution: {integrity: sha512-KH11D2U4SNJ0hOoIwHGFLwnY/Xfl4zTfyt4rMbJN5AJUDLwBOnGx5rL+2/Xb1cV9CX7R8ADm9ysN69AGGVRObQ==} + engines: {npm: '>=8.3.0'} + requiresBuild: true + peerDependencies: + '@vue/composition-api': '>=1' + vue: 2||3 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vanilla-jsoneditor: 0.20.0(@lezer/common@1.1.2) + vue: 3.3.13(typescript@5.3.3) + vue-demi: 0.14.6(vue@3.3.13) + transitivePeerDependencies: + - '@lezer/common' + dev: false + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -9217,6 +9242,37 @@ packages: hasBin: true dev: false + /vanilla-jsoneditor@0.20.0(@lezer/common@1.1.2): + resolution: {integrity: sha512-2Q1//eCTauqy0kqvz2dLurzov0BA7FVHCl+6MBUBqem1CWmUONFuMvr8F/mmeqWY8D5WqcrRtb60akDWDFakgQ==} + dependencies: + '@codemirror/autocomplete': 6.11.1(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3)(@lezer/common@1.1.2) + '@codemirror/commands': 6.3.2 + '@codemirror/lang-json': 6.0.1 + '@codemirror/language': 6.9.3 + '@codemirror/lint': 6.4.2 + '@codemirror/search': 6.5.5 + '@codemirror/state': 6.3.3 + '@codemirror/view': 6.22.3 + '@fortawesome/free-regular-svg-icons': 6.5.1 + '@fortawesome/free-solid-svg-icons': 6.5.1 + '@replit/codemirror-indentation-markers': 6.5.0(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3) + ajv: 8.12.0 + codemirror-wrapped-line-indent: 1.0.0(@codemirror/language@6.9.3)(@codemirror/state@6.3.3)(@codemirror/view@6.22.3) + diff-sequences: 29.6.3 + immutable-json-patch: 5.1.3 + jmespath: 0.16.0 + json-source-map: 0.6.1 + jsonrepair: 3.5.0 + lodash-es: 4.17.21 + memoize-one: 6.0.0 + natural-compare-lite: 1.4.0 + sass: 1.69.5 + svelte: 4.2.8 + vanilla-picker: 2.12.2 + transitivePeerDependencies: + - '@lezer/common' + dev: false + /vanilla-jsoneditor@0.21.1(@lezer/common@1.1.2): resolution: {integrity: sha512-+bWUtsPk5TAJSao6y+zCqDFVviZyvyFHLmw82kriS3iabh/DX27Ws0GcEzK9eW98cXfKxi/N3ppBKZGimhW4RA==} dependencies: diff --git a/src/components.d.ts b/src/components.d.ts index 0ed653778..f3658fa15 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -20,7 +20,7 @@ declare module 'vue' { ATypographyTitle: typeof import('ant-design-vue/es')['TypographyTitle'] Checkerboard: typeof import('./components/checkerboard.vue')['default'] Container: typeof import('./components/container.vue')['default'] - CopyableText: typeof import('./components/copyable-text.vue')['default'] + CopyableText: typeof import('./components/CopyableText.vue')['default'] Editor: typeof import('./components/editor/Editor.vue')['default'] EditorMini: typeof import('./components/editor/EditorMini.vue')['default'] ElAlert: typeof import('element-plus/es')['ElAlert'] @@ -77,6 +77,7 @@ declare module 'vue' { ElUpload: typeof import('element-plus/es')['ElUpload'] FixedWidgets: typeof import('./components/fixed-widgets.vue')['default'] FormatTransformer: typeof import('./components/FormatTransformer.vue')['default'] + JsonEditor: typeof import('./components/JsonEditor.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] VanillaJsonEditor: typeof import('./components/VanillaJsonEditor.vue')['default'] diff --git a/src/components/copyable-text.vue b/src/components/CopyableText.vue similarity index 69% rename from src/components/copyable-text.vue rename to src/components/CopyableText.vue index a3ffd2c37..0aada7cb6 100644 --- a/src/components/copyable-text.vue +++ b/src/components/CopyableText.vue @@ -11,15 +11,7 @@ defineProps<{ val: string }>() - -async function copy (val: string) { - try { - await navigator.clipboard.writeText(val) - ElMessage.success('复制成功') - } catch (e) { - ElMessage.error('复制失败') - } -} +const { copy } = useCopy({ text: '复制成功' }) diff --git a/src/views/mock/child/mock.d.ts b/src/views/mock/child/mock.d.ts new file mode 100644 index 000000000..5fc14722e --- /dev/null +++ b/src/views/mock/child/mock.d.ts @@ -0,0 +1,28 @@ +export type MockPrj = { + id: string + name: string + path: string + description: string +} + +type RequestType = 'post' | 'get' | 'delete' | 'put' + +export type MockData = { + id: number + name: string + type: RequestType + enabled: boolean + path: string + description?: string + delay: number + response: string + projectId: string + createdAt?: string + url?: string +} + +export type ResultDto = { + success: boolean + data?: T + message?: string +} diff --git a/src/views/mock/child/mockData.service.ts b/src/views/mock/child/mockData.service.ts new file mode 100644 index 000000000..7e4f10fa6 --- /dev/null +++ b/src/views/mock/child/mockData.service.ts @@ -0,0 +1,120 @@ +import type { MockData, MockPrj, ResultDto } from './mock' +import axios from '@/plugins/Axios' +import dayjs from 'dayjs' +import { deleteParam, setParam } from '@/utils/hashHandler' +import ElMessage from 'element-plus/es/components/message/index' + +const emptyMockData: MockData = { + id: -1, + name: '', + type: 'get', + enabled: true, + path: '', + description: '', + delay: 0, + response: '', + projectId: '' +} + +export const selectedProject = ref() +export const mockData = ref([]) + +export function getNewMockData () { + if (!selectedProject.value) { + throw new Error('未选择项目') + } + return { + ...emptyMockData, + projectId: selectedProject.value.id + } +} + +export async function setProject (prj?: MockPrj) { + selectedProject.value = prj + mockData.value = [] + if (selectedProject.value) { + setParam('prjId', selectedProject.value.id) + await refreshMockData() + } else { + deleteParam('prjId') + } +} + +export async function editData (data: MockData) { + try { + const res: ResultDto = await axios.put(`${axios.$apiBase}/mock/api/data/${data.id}`, { + ...data, + id: undefined, + projectId: undefined + }).then(axios.getData) + if (res.success) { + ElMessage.success('修改数据成功') + refreshMockData().then() + return true + } else { + ElMessage.error('修改数据失败') + } + } catch (e) { + ElMessage.error('修改数据失败') + } + return false +} + +export async function createData (data: MockData) { + try { + const res: ResultDto = await axios.post(`${axios.$apiBase}/mock/api/data`, { ...data, id: undefined, projectId: selectedProject.value?.id }).then(axios.getData) + if (res.success) { + ElMessage.success('创建数据成功') + refreshMockData().then() + return true + } else { + ElMessage.error('创建数据失败') + } + } catch (e) { + ElMessage.error('创建数据失败') + } + return false +} + +export async function deleteData (data: MockData) { + try { + const res: ResultDto = await axios.delete(`${axios.$apiBase}/mock/api/data/${data.id}`).then(axios.getData) + if (res.success) { + ElMessage.success('删除数据成功') + refreshMockData().then() + return true + } else { + ElMessage.error('删除数据失败') + } + } catch (e) { + ElMessage.error('删除数据失败') + } + return false +} + +async function refreshMockData () { + if (!selectedProject.value) { + ElMessage.error('未选择项目') + return + } + try { + mockData.value = await getMockData(selectedProject.value) + } catch (e) { + console.log(e) + ElMessage.error('获取数据列表失败') + } +} + +// 获取接口列表 +async function getMockData (prj: MockPrj) { + const data: ResultDto = await (axios.get(`${axios.$apiBase}/mock/api/prj/${prj.id}/list`).then(axios.getData)) + if (data.success) { + return (data.data || []).map(item => { + item.createdAt = dayjs(item.createdAt).format('YYYY-MM-DD HH:mm:ss') + item.url = `${axios.$apiBase}/mock/${prj.id}${prj.path}${item.path}` + return item + }) + } else { + throw new Error(data.message) + } +} diff --git a/src/views/mock/child/mockDetail.vue b/src/views/mock/child/mockDetail.vue new file mode 100644 index 000000000..a00b40d02 --- /dev/null +++ b/src/views/mock/child/mockDetail.vue @@ -0,0 +1,374 @@ + + + + + + + {{ selectedProject.name }} + + + + + + 接口根地址 + + + + + + 项目ID + + + + + + + 新增接口 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + + 提交 + + + + + + + diff --git a/src/views/mock/child/mockList.vue b/src/views/mock/child/mockList.vue new file mode 100644 index 000000000..902227f61 --- /dev/null +++ b/src/views/mock/child/mockList.vue @@ -0,0 +1,251 @@ + + + + + + + + {{ prj.name }} + + + {{ prj.path }} + + + {{ prj.description }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 取消 + + 提交 + + + + + + + diff --git a/src/views/mock/child/mockProject.service.ts b/src/views/mock/child/mockProject.service.ts new file mode 100644 index 000000000..c8ce9228a --- /dev/null +++ b/src/views/mock/child/mockProject.service.ts @@ -0,0 +1,107 @@ +import axios from '@/plugins/Axios' +import { setProject } from './mockData.service' +import { getParam } from '@/utils/hashHandler' +import { MockPrj, ResultDto } from './mock' + +const emptyMockPrj: MockPrj = { + id: '', + name: '', + path: '', + description: '' +} + +export const projects = ref([]) + +export async function init () { + try { + await refreshMockProject() + const prjId = getParam('prjId') + if (prjId) { + selectProject(prjId) + } + } catch (e) { + throw new Error('获取项目列表失败') + } +} + +export function getNewMockProject () { + return { ...emptyMockPrj } +} + +export function selectProject (prj: MockPrj | string) { + if (typeof prj === 'string') { + const tmp = projects.value.find(item => item.id === prj) + if (!tmp) { + ElMessage.error('未找到项目') + return + } else { + prj = tmp + } + } + setProject(prj).then() +} + +export async function createProject (prj: MockPrj) { + try { + const res: ResultDto = await axios.post(`${axios.$apiBase}/mock/api/prj`, { + ...prj, + id: undefined + }).then(axios.getData) + if (res.success) { + ElMessage.success('创建项目成功') + refreshMockProject().then() + return true + } else { + ElMessage.error('创建项目失败') + } + } catch (e) { + ElMessage.error('创建项目失败') + } + return false +} + +export async function updateProject (prj: MockPrj) { + try { + const res: ResultDto = await axios.put(`${axios.$apiBase}/mock/api/prj/${prj.id}`, prj).then(axios.getData) + if (res.success) { + ElMessage.success('更新项目成功') + refreshMockProject().then() + return true + } else { + ElMessage.error('更新项目失败') + } + } catch (e) { + ElMessage.error('更新项目失败') + } + return false +} + +export async function deleteProject (prj: MockPrj) { + try { + const res: ResultDto = await axios.delete(`${axios.$apiBase}/mock/api/prj/${prj.id}`).then(axios.getData) + if (res.success) { + ElMessage.success('删除项目成功') + refreshMockProject().then() + return true + } else { + ElMessage.error('删除项目失败') + } + } catch (e) { + ElMessage.error('删除项目失败') + } + return false +} + +async function refreshMockProject () { + try { + const data: ResultDto = await (axios.get(`${axios.$apiBase}/mock/api/prj/list`).then(axios.getData)) + if (data.success) { + projects.value = data.data || [] + } else { + ElMessage.error('获取项目列表失败') + } + } catch (e) { + console.log(e) + ElMessage.error('获取项目列表失败') + } +} diff --git a/src/views/mock/mock.vue b/src/views/mock/mock.vue new file mode 100644 index 000000000..2a2bdab78 --- /dev/null +++ b/src/views/mock/mock.vue @@ -0,0 +1,16 @@ + + + + +