rwz-dev-proxy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/bin/rwz-dev-proxy.js +29 -0
  2. package/db.js +121 -0
  3. package/index.js +625 -0
  4. package/package.json +32 -0
  5. package/public/cdn/axios/dist/axios.min.js +3 -0
  6. package/public/cdn/element-plus/dist/index.css +1 -0
  7. package/public/cdn/element-plus/dist/index.full.js +72239 -0
  8. package/public/cdn/mockjs/dist/mock-min.js +10 -0
  9. package/public/cdn/monaco-editor/min/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
  10. package/public/cdn/monaco-editor/min/vs/abap-D-t0cyap.js +1 -0
  11. package/public/cdn/monaco-editor/min/vs/apex-CcIm7xu6.js +1 -0
  12. package/public/cdn/monaco-editor/min/vs/assets/css.worker-HnVq6Ewq.js +93 -0
  13. package/public/cdn/monaco-editor/min/vs/assets/editor.worker-Be8ye1pW.js +26 -0
  14. package/public/cdn/monaco-editor/min/vs/assets/html.worker-B51mlPHg.js +470 -0
  15. package/public/cdn/monaco-editor/min/vs/assets/json.worker-DKiEKt88.js +58 -0
  16. package/public/cdn/monaco-editor/min/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
  17. package/public/cdn/monaco-editor/min/vs/azcli-BA0tQDCg.js +1 -0
  18. package/public/cdn/monaco-editor/min/vs/basic-languages/monaco.contribution.js +1 -0
  19. package/public/cdn/monaco-editor/min/vs/bat-C397hTD6.js +1 -0
  20. package/public/cdn/monaco-editor/min/vs/bicep-DF5aW17k.js +2 -0
  21. package/public/cdn/monaco-editor/min/vs/cameligo-plsz8qhj.js +1 -0
  22. package/public/cdn/monaco-editor/min/vs/clojure-Y2auQMzK.js +1 -0
  23. package/public/cdn/monaco-editor/min/vs/coffee-Bu45yuWE.js +1 -0
  24. package/public/cdn/monaco-editor/min/vs/cpp-CkKPQIni.js +1 -0
  25. package/public/cdn/monaco-editor/min/vs/csharp-CX28MZyh.js +1 -0
  26. package/public/cdn/monaco-editor/min/vs/csp-D8uWnyxW.js +1 -0
  27. package/public/cdn/monaco-editor/min/vs/css-CaeNmE3S.js +3 -0
  28. package/public/cdn/monaco-editor/min/vs/cssMode-CjiAH6dQ.js +1 -0
  29. package/public/cdn/monaco-editor/min/vs/cypher-DVThT8BS.js +1 -0
  30. package/public/cdn/monaco-editor/min/vs/dart-CmGfCvrO.js +1 -0
  31. package/public/cdn/monaco-editor/min/vs/dockerfile-CZqqYdch.js +1 -0
  32. package/public/cdn/monaco-editor/min/vs/ecl-30fUercY.js +1 -0
  33. package/public/cdn/monaco-editor/min/vs/editor/editor.main.css +1 -0
  34. package/public/cdn/monaco-editor/min/vs/editor/editor.main.js +5 -0
  35. package/public/cdn/monaco-editor/min/vs/editor.api-CalNCsUg.js +903 -0
  36. package/public/cdn/monaco-editor/min/vs/elixir-xjPaIfzF.js +1 -0
  37. package/public/cdn/monaco-editor/min/vs/flow9-DqtmStfK.js +1 -0
  38. package/public/cdn/monaco-editor/min/vs/freemarker2-Cz_sV6Md.js +3 -0
  39. package/public/cdn/monaco-editor/min/vs/fsharp-BOMdg4U1.js +1 -0
  40. package/public/cdn/monaco-editor/min/vs/go-D_hbi-Jt.js +1 -0
  41. package/public/cdn/monaco-editor/min/vs/graphql-CKUU4kLG.js +1 -0
  42. package/public/cdn/monaco-editor/min/vs/handlebars-OwglfO-1.js +1 -0
  43. package/public/cdn/monaco-editor/min/vs/hcl-DTaboeZW.js +1 -0
  44. package/public/cdn/monaco-editor/min/vs/html-Pa1xEWsY.js +1 -0
  45. package/public/cdn/monaco-editor/min/vs/htmlMode-Bz67EXwp.js +1 -0
  46. package/public/cdn/monaco-editor/min/vs/ini-CsNwO04R.js +1 -0
  47. package/public/cdn/monaco-editor/min/vs/java-CI4ZMsH9.js +1 -0
  48. package/public/cdn/monaco-editor/min/vs/javascript-PczUCGdz.js +1 -0
  49. package/public/cdn/monaco-editor/min/vs/jsonMode-DULH5oaX.js +7 -0
  50. package/public/cdn/monaco-editor/min/vs/julia-BwzEvaQw.js +1 -0
  51. package/public/cdn/monaco-editor/min/vs/kotlin-IUYPiTV8.js +1 -0
  52. package/public/cdn/monaco-editor/min/vs/language/css/monaco.contribution.js +1 -0
  53. package/public/cdn/monaco-editor/min/vs/language/html/monaco.contribution.js +1 -0
  54. package/public/cdn/monaco-editor/min/vs/language/json/monaco.contribution.js +1 -0
  55. package/public/cdn/monaco-editor/min/vs/language/typescript/monaco.contribution.js +1 -0
  56. package/public/cdn/monaco-editor/min/vs/less-C0eDYdqa.js +2 -0
  57. package/public/cdn/monaco-editor/min/vs/lexon-iON-Kj97.js +1 -0
  58. package/public/cdn/monaco-editor/min/vs/liquid-DqKjdPGy.js +1 -0
  59. package/public/cdn/monaco-editor/min/vs/loader.js +1368 -0
  60. package/public/cdn/monaco-editor/min/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
  61. package/public/cdn/monaco-editor/min/vs/lua-DtygF91M.js +1 -0
  62. package/public/cdn/monaco-editor/min/vs/m3-CsR4AuFi.js +1 -0
  63. package/public/cdn/monaco-editor/min/vs/markdown-C_rD0bIw.js +1 -0
  64. package/public/cdn/monaco-editor/min/vs/mdx-DEWtB1K5.js +1 -0
  65. package/public/cdn/monaco-editor/min/vs/mips-CiYP61RB.js +1 -0
  66. package/public/cdn/monaco-editor/min/vs/monaco.contribution-D2OdxNBt.js +1 -0
  67. package/public/cdn/monaco-editor/min/vs/monaco.contribution-DO3azKX8.js +1 -0
  68. package/public/cdn/monaco-editor/min/vs/monaco.contribution-EcChJV6a.js +1 -0
  69. package/public/cdn/monaco-editor/min/vs/monaco.contribution-qLAYrEOP.js +1 -0
  70. package/public/cdn/monaco-editor/min/vs/msdax-C38-sJlp.js +1 -0
  71. package/public/cdn/monaco-editor/min/vs/mysql-CdtbpvbG.js +1 -0
  72. package/public/cdn/monaco-editor/min/vs/nls.messages-loader.js +1 -0
  73. package/public/cdn/monaco-editor/min/vs/nls.messages.cs.js.js +17 -0
  74. package/public/cdn/monaco-editor/min/vs/nls.messages.de.js.js +17 -0
  75. package/public/cdn/monaco-editor/min/vs/nls.messages.es.js.js +17 -0
  76. package/public/cdn/monaco-editor/min/vs/nls.messages.fr.js.js +15 -0
  77. package/public/cdn/monaco-editor/min/vs/nls.messages.it.js.js +15 -0
  78. package/public/cdn/monaco-editor/min/vs/nls.messages.ja.js.js +17 -0
  79. package/public/cdn/monaco-editor/min/vs/nls.messages.js.js +10 -0
  80. package/public/cdn/monaco-editor/min/vs/nls.messages.ko.js.js +25 -0
  81. package/public/cdn/monaco-editor/min/vs/nls.messages.pl.js.js +17 -0
  82. package/public/cdn/monaco-editor/min/vs/nls.messages.pt-br.js.js +6 -0
  83. package/public/cdn/monaco-editor/min/vs/nls.messages.ru.js.js +17 -0
  84. package/public/cdn/monaco-editor/min/vs/nls.messages.tr.js.js +15 -0
  85. package/public/cdn/monaco-editor/min/vs/nls.messages.zh-cn.js.js +17 -0
  86. package/public/cdn/monaco-editor/min/vs/nls.messages.zh-tw.js.js +15 -0
  87. package/public/cdn/monaco-editor/min/vs/objective-c-CntZFaHX.js +1 -0
  88. package/public/cdn/monaco-editor/min/vs/pascal-r6kuqfl_.js +1 -0
  89. package/public/cdn/monaco-editor/min/vs/pascaligo-BiXoTmXh.js +1 -0
  90. package/public/cdn/monaco-editor/min/vs/perl-DABw_TcH.js +1 -0
  91. package/public/cdn/monaco-editor/min/vs/pgsql-me_jFXeX.js +1 -0
  92. package/public/cdn/monaco-editor/min/vs/php-D_kh-9LK.js +1 -0
  93. package/public/cdn/monaco-editor/min/vs/pla-VfZjczW0.js +1 -0
  94. package/public/cdn/monaco-editor/min/vs/postiats-BBSzz8Pk.js +1 -0
  95. package/public/cdn/monaco-editor/min/vs/powerquery-Dt-g_2cc.js +1 -0
  96. package/public/cdn/monaco-editor/min/vs/powershell-B-7ap1zc.js +1 -0
  97. package/public/cdn/monaco-editor/min/vs/protobuf-BmtuEB1A.js +2 -0
  98. package/public/cdn/monaco-editor/min/vs/pug-BRpRNeEb.js +1 -0
  99. package/public/cdn/monaco-editor/min/vs/python-Cr0UkIbn.js +1 -0
  100. package/public/cdn/monaco-editor/min/vs/qsharp-BzsFaUU9.js +1 -0
  101. package/public/cdn/monaco-editor/min/vs/r-f8dDdrp4.js +1 -0
  102. package/public/cdn/monaco-editor/min/vs/razor-BYAHOTkz.js +1 -0
  103. package/public/cdn/monaco-editor/min/vs/redis-fvZQY4PI.js +1 -0
  104. package/public/cdn/monaco-editor/min/vs/redshift-45Et0LQi.js +1 -0
  105. package/public/cdn/monaco-editor/min/vs/restructuredtext-C7UUFKFD.js +1 -0
  106. package/public/cdn/monaco-editor/min/vs/ruby-CZO8zYTz.js +1 -0
  107. package/public/cdn/monaco-editor/min/vs/rust-Bfetafyc.js +1 -0
  108. package/public/cdn/monaco-editor/min/vs/sb-3GYllVck.js +1 -0
  109. package/public/cdn/monaco-editor/min/vs/scala-foMgrKo1.js +1 -0
  110. package/public/cdn/monaco-editor/min/vs/scheme-CHdMtr7p.js +1 -0
  111. package/public/cdn/monaco-editor/min/vs/scss-C1cmLt9V.js +3 -0
  112. package/public/cdn/monaco-editor/min/vs/shell-ClXCKCEW.js +1 -0
  113. package/public/cdn/monaco-editor/min/vs/solidity-MZ6ExpPy.js +1 -0
  114. package/public/cdn/monaco-editor/min/vs/sophia-DWkuSsPQ.js +1 -0
  115. package/public/cdn/monaco-editor/min/vs/sparql-AUGFYSyk.js +1 -0
  116. package/public/cdn/monaco-editor/min/vs/sql-32GpJSV2.js +1 -0
  117. package/public/cdn/monaco-editor/min/vs/st-CuDFIVZ_.js +1 -0
  118. package/public/cdn/monaco-editor/min/vs/swift-n-2HociN.js +3 -0
  119. package/public/cdn/monaco-editor/min/vs/systemverilog-Ch4vA8Yt.js +1 -0
  120. package/public/cdn/monaco-editor/min/vs/tcl-D74tq1nH.js +1 -0
  121. package/public/cdn/monaco-editor/min/vs/tsMode-CZz1Umrk.js +11 -0
  122. package/public/cdn/monaco-editor/min/vs/twig-C6taOxMV.js +1 -0
  123. package/public/cdn/monaco-editor/min/vs/typescript-DfOrAzoV.js +1 -0
  124. package/public/cdn/monaco-editor/min/vs/typespec-D-PIh9Xw.js +1 -0
  125. package/public/cdn/monaco-editor/min/vs/vb-Dyb2648j.js +1 -0
  126. package/public/cdn/monaco-editor/min/vs/wgsl-BhLXMOR0.js +298 -0
  127. package/public/cdn/monaco-editor/min/vs/workers-DcJshg-q.js +1 -0
  128. package/public/cdn/monaco-editor/min/vs/xml-CdsdnY8S.js +1 -0
  129. package/public/cdn/monaco-editor/min/vs/yaml-DYGvmE88.js +1 -0
  130. package/public/cdn/vue/vue.global.js +18410 -0
  131. package/public/index.html +429 -0
  132. package/public/manage.html +750 -0
  133. package/public/monaco-editor.html +54 -0
  134. package/public/stylesheets/style.css +8 -0
  135. package/views/error.ejs +3 -0
  136. package/views/index.ejs +11 -0
@@ -0,0 +1,750 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>路由接口管理</title>
8
+ <link rel="stylesheet" href="/cdn/element-plus/dist/index.css" />
9
+ <!-- 导入 Vue 3 -->
10
+ <script src="/cdn/vue/vue.global.js"></script>
11
+ <!-- 导入组件库 -->
12
+ <script src="/cdn/element-plus/dist/index.full.js"></script>
13
+ <style>
14
+ html,
15
+ body {
16
+ height: 100%;
17
+ margin: 0;
18
+ }
19
+
20
+ #app {
21
+ height: 100%;
22
+ }
23
+
24
+ .container {
25
+ padding: 16px;
26
+ }
27
+
28
+ .toolbar {
29
+ display: flex;
30
+ gap: 12px;
31
+ align-items: center;
32
+ flex-wrap: wrap;
33
+ margin-bottom: 12px;
34
+ }
35
+
36
+ .toolbar-right {
37
+ margin-left: auto;
38
+ display: flex;
39
+ gap: 8px;
40
+ align-items: center;
41
+ }
42
+
43
+ .muted {
44
+ color: rgba(0, 0, 0, .55);
45
+ font-size: 12px;
46
+ }
47
+
48
+ .code {
49
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
50
+ }
51
+ </style>
52
+ </head>
53
+
54
+ <body>
55
+ <div id="app">
56
+ <el-container style="height: 100%; display: flex; flex-direction: column;" v-loading="loadingRoute">
57
+ <el-header style="display:flex;align-items:center;gap:12px;border-bottom:1px solid #eee;">
58
+ <div style="font-weight:600;">{{ routeInfo ? routeInfo.name + ' (' + routeInfo.routePrefix + ')' : '加载中...' }} 接口管理</div>
59
+ <div class="toolbar-right">
60
+ <el-button @click="fetchInterfaces" :disabled="!routeId">刷新</el-button>
61
+ </div>
62
+ </el-header>
63
+ <el-container style="flex:1;overflow:hidden;">
64
+ <el-aside width="350px" style="border-right:1px solid #eee;display:flex;flex-direction:column;background:#fff;">
65
+ <div style="padding:10px;border-bottom:1px solid #eee;">
66
+ <el-input v-model="interfaceSearch" placeholder="搜索接口" clearable />
67
+ </div>
68
+ <el-scrollbar>
69
+ <div v-if="loadingInterfaces" style="padding:20px;text-align:center;color:#999;">加载中...</div>
70
+ <div v-else-if="filteredInterfaces.length===0" style="padding:20px;text-align:center;color:#999;">暂无数据</div>
71
+ <div v-for="item in filteredInterfaces" :key="item.id" @click="selectInterface(item)"
72
+ :style="{padding:'10px', cursor:'pointer', borderBottom:'1px solid #f5f5f5', background: activeInterface?.id===item.id?'#ecf5ff':''}"
73
+ class="interface-item">
74
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
75
+ <el-tag size="small" :type="methodColor(item.method)">{{ item.method }}</el-tag>
76
+ <span class="muted" style="font-size:10px;">{{ formatTime(item.updatedAt) }}</span>
77
+ </div>
78
+ <div class="code" style="font-size:12px;word-break:break-all;line-height:1.4;">{{ item.pathname }}</div>
79
+ <div v-if="item.description" class="muted" style="font-size:12px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">{{ item.description }}</div>
80
+ </div>
81
+ </el-scrollbar>
82
+ </el-aside>
83
+ <el-main style="padding:0;display:flex;flex-direction:column;overflow:hidden;">
84
+ <div v-if="!activeInterface" style="display:flex;height:100%;align-items:center;justify-content:center;color:#999;">
85
+ 请选择左侧接口
86
+ </div>
87
+ <div v-else style="padding:20px;height:100%;overflow:auto;box-sizing:border-box;">
88
+ <el-form label-position="top">
89
+ <el-form-item label="请求路径">
90
+ <el-input :model-value="activeInterface.pathname" readonly>
91
+ <template #prepend>{{ activeInterface.method }}</template>
92
+ <template #append>
93
+ <el-button @click="sendRequest" :loading="sending">发送</el-button>
94
+ </template>
95
+ </el-input>
96
+ </el-form-item>
97
+ <el-form-item label="描述">
98
+ <el-input v-model="activeInterface.description" type="textarea" :rows="2" placeholder="添加描述"
99
+ @change="updateInterface(activeInterface)" />
100
+ </el-form-item>
101
+ <el-form-item label="处理模式">
102
+ <el-radio-group v-model="activeInterface.mode">
103
+ <el-radio label="proxy">代理数据</el-radio>
104
+ <el-radio label="static">静态数据</el-radio>
105
+ <el-radio label="mock">Mock数据</el-radio>
106
+ </el-radio-group>
107
+ </el-form-item>
108
+ <el-tabs>
109
+ <el-tab-pane label="Header参数">
110
+ <div style="margin-bottom:8px;">
111
+ <el-button size="small" @click="addHeader">添加Header</el-button>
112
+ </div>
113
+ <el-table :data="localHeaders" style="width: 100%" height="460">
114
+ <el-table-column prop="key" label="Key" width="200">
115
+ <template #default="scope">
116
+ <el-input v-model="scope.row.key" placeholder="Key" />
117
+ </template>
118
+ </el-table-column>
119
+ <el-table-column prop="value" label="Value">
120
+ <template #default="scope">
121
+ <el-input v-model="scope.row.value" placeholder="Value" />
122
+ </template>
123
+ </el-table-column>
124
+ <el-table-column label="操作" width="80">
125
+ <template #default="scope">
126
+ <el-button type="danger" link @click="removeHeader(scope.$index)">删除</el-button>
127
+ </template>
128
+ </el-table-column>
129
+ </el-table>
130
+ </el-tab-pane>
131
+ <el-tab-pane label="Query参数">
132
+ <div style="margin-bottom:8px;">
133
+ <el-button size="small" @click="addQuery">添加Query</el-button>
134
+ </div>
135
+ <el-table :data="localQuery" style="width: 100%" height="460">
136
+ <el-table-column prop="key" label="Key" width="200">
137
+ <template #default="scope">
138
+ <el-input v-model="scope.row.key" placeholder="Key" />
139
+ </template>
140
+ </el-table-column>
141
+ <el-table-column prop="value" label="Value">
142
+ <template #default="scope">
143
+ <el-input v-model="scope.row.value" placeholder="Value" />
144
+ </template>
145
+ </el-table-column>
146
+ <el-table-column label="操作" width="80">
147
+ <template #default="scope">
148
+ <el-button type="danger" link @click="removeQuery(scope.$index)">删除</el-button>
149
+ </template>
150
+ </el-table-column>
151
+ </el-table>
152
+ </el-tab-pane>
153
+ <el-tab-pane label="Body参数">
154
+ <monaco-editor
155
+ :model-value="formatJson(activeInterface.reqBody)"
156
+ @update:model-value="val => activeInterface.reqBody = val"
157
+ language="json"
158
+ height="500px"
159
+ />
160
+ </el-tab-pane>
161
+ <el-tab-pane label="代理结果">
162
+ <div style="margin-bottom:5px;">Status: <el-tag size="small" :type="activeInterface.statusCode>=200&&activeInterface.statusCode<300?'success':'danger'">{{ activeInterface.statusCode }}</el-tag></div>
163
+ <monaco-editor
164
+ :model-value="formatJson(activeInterface.resBody)"
165
+ @update:model-value="val => activeInterface.resBody = val"
166
+ language="json"
167
+ height="500px"
168
+ />
169
+ </el-tab-pane>
170
+ <el-tab-pane label="静态结果">
171
+ <monaco-editor
172
+ :model-value="formatJson(activeInterface.staticData)"
173
+ @update:model-value="val => activeInterface.staticData = val"
174
+ language="json"
175
+ height="500px"
176
+ />
177
+ </el-tab-pane>
178
+ <el-tab-pane label="Mock数据">
179
+ <div style="display: flex; gap: 10px; height: 500px;">
180
+ <div style="flex: 1; display: flex; flex-direction: column;">
181
+ <div style="margin-bottom:8px; display: flex; gap: 8px; align-items: center;">
182
+ <span style="font-weight: bold;">Mock 模板</span>
183
+ <el-button size="small" @click="generateMockTemplate">根据代理结果生成模板</el-button>
184
+ </div>
185
+ <monaco-editor
186
+ :model-value="formatJson(activeInterface.mockData)"
187
+ @update:model-value="val => activeInterface.mockData = val"
188
+ language="json"
189
+ height="100%"
190
+ />
191
+ </div>
192
+ <div style="flex: 1; display: flex; flex-direction: column;">
193
+ <div style="margin-bottom:8px; display: flex; gap: 8px; align-items: center;">
194
+ <span style="font-weight: bold;">Mock 结果</span>
195
+ <el-button size="small" @click="generateMockResult">生成Mock数据</el-button>
196
+ </div>
197
+ <monaco-editor
198
+ :model-value="formatJson(activeInterface.mockResult)"
199
+ @update:model-value="val => activeInterface.mockResult = val"
200
+ language="json"
201
+ height="100%"
202
+ />
203
+ </div>
204
+ </div>
205
+ </el-tab-pane>
206
+ <el-tab-pane label="JS钩子">
207
+ <div style="margin-bottom:8px;">
208
+ <el-button size="small" @click="generateMockScript">生成示例脚本</el-button>
209
+ </div>
210
+ <monaco-editor
211
+ :model-value="activeInterface.mockScript || ''"
212
+ @update:model-value="val => activeInterface.mockScript = val"
213
+ language="javascript"
214
+ height="500px"
215
+ />
216
+ <div class="muted" style="margin-top:5px;">
217
+ 定义 <code>onRequest</code> 和 <code>onResponse</code> 函数来拦截和处理请求。<br/>
218
+ 例如: <code>module.exports = { onRequest: async ({req, res}) => { ... }, onResponse: async ({body, req, res}) => { return body } }</code>
219
+ </div>
220
+ </el-tab-pane>
221
+ </el-tabs>
222
+ <div style="margin-top:20px;display:flex;gap:12px;">
223
+ <el-button type="primary" @click="saveInterface">保存当前接口</el-button>
224
+ <el-button type="danger" plain @click="deleteInterface(activeInterface)">删除此接口记录</el-button>
225
+ </div>
226
+ </el-form>
227
+ </div>
228
+ </el-main>
229
+ </el-container>
230
+ </el-container>
231
+ </div>
232
+
233
+ <script src="/cdn/mockjs/dist/mock-min.js"></script>
234
+ <script src="/cdn/axios/dist/axios.min.js"></script>
235
+
236
+ <script>
237
+ const { ElMessage, ElMessageBox } = ElementPlus
238
+
239
+ const MonacoEditor = {
240
+ props: ['modelValue', 'language', 'readonly', 'height'],
241
+ template: `
242
+ <div :style="{border: '1px solid #dcdfe6', borderRadius: '4px', overflow: 'hidden', height: height || '300px'}">
243
+ <iframe
244
+ ref="iframe"
245
+ src="monaco-editor.html"
246
+ style="width: 100%; height: 100%; border: none;"
247
+ ></iframe>
248
+ </div>
249
+ `,
250
+ setup(props, { emit }) {
251
+ const iframe = Vue.ref(null)
252
+ const isReady = Vue.ref(false)
253
+
254
+ function sendMessage(data) {
255
+ if (iframe.value && iframe.value.contentWindow) {
256
+ iframe.value.contentWindow.postMessage(data, '*')
257
+ }
258
+ }
259
+
260
+ Vue.watch(() => props.modelValue, (val) => {
261
+ if (isReady.value) {
262
+ sendMessage({ type: 'setValue', value: val })
263
+ }
264
+ })
265
+
266
+ Vue.watch(() => props.language, (val) => {
267
+ if (isReady.value) {
268
+ sendMessage({ type: 'setLanguage', language: val })
269
+ }
270
+ })
271
+
272
+ Vue.watch(() => props.readonly, (val) => {
273
+ if (isReady.value) {
274
+ sendMessage({ type: 'setReadOnly', readOnly: val })
275
+ }
276
+ })
277
+
278
+ const onMessage = (event) => {
279
+ if (iframe.value && event.source === iframe.value.contentWindow) {
280
+ if (event.data.type === 'editorReady') {
281
+ isReady.value = true
282
+ sendMessage({ type: 'setValue', value: props.modelValue })
283
+ sendMessage({ type: 'setLanguage', language: props.language || 'json' })
284
+ sendMessage({ type: 'setReadOnly', readOnly: props.readonly })
285
+ } else if (event.data.type === 'contentChanged') {
286
+ emit('update:modelValue', event.data.value)
287
+ }
288
+ }
289
+ }
290
+
291
+ Vue.onMounted(() => {
292
+ window.addEventListener('message', onMessage)
293
+ })
294
+
295
+ Vue.onUnmounted(() => {
296
+ window.removeEventListener('message', onMessage)
297
+ })
298
+
299
+ return { iframe }
300
+ }
301
+ }
302
+
303
+ const api = axios.create({
304
+ baseURL: '',
305
+ timeout: 15000,
306
+ headers: { 'Content-Type': 'application/json' }
307
+ })
308
+
309
+ const App = {
310
+ components: { MonacoEditor },
311
+ setup() {
312
+ const routeId = Vue.ref(null)
313
+ const routeInfo = Vue.ref(null)
314
+ const loadingRoute = Vue.ref(false)
315
+
316
+ const interfaces = Vue.ref([])
317
+ const loadingInterfaces = Vue.ref(false)
318
+ const activeInterface = Vue.ref(null)
319
+ const interfaceSearch = Vue.ref('')
320
+ const sending = Vue.ref(false)
321
+
322
+ const filteredInterfaces = Vue.computed(() => {
323
+ if (!interfaceSearch.value) return interfaces.value
324
+ const q = interfaceSearch.value.toLowerCase()
325
+ return interfaces.value.filter(i =>
326
+ (i.pathname && i.pathname.toLowerCase().includes(q)) ||
327
+ (i.description && i.description.toLowerCase().includes(q))
328
+ )
329
+ })
330
+
331
+ const localHeaders = Vue.ref([])
332
+ const localQuery = Vue.ref([])
333
+
334
+ Vue.watch(activeInterface, (newVal) => {
335
+ if (newVal) {
336
+ if (!newVal.mode) newVal.mode = 'proxy'
337
+
338
+ // Logic to detect script mode
339
+ if (newVal.mode === 'mock' && newVal.mockScript && newVal.mockScript.trim()) {
340
+ newVal.mode = 'script'
341
+ }
342
+
343
+ // Migration helper: if static mode and no staticData, copy from resBody
344
+ if (newVal.mode === 'static' && !newVal.staticData && newVal.resBody) {
345
+ newVal.staticData = newVal.resBody
346
+ }
347
+
348
+ // Headers
349
+ localHeaders.value = []
350
+ if (newVal.reqHeaders) {
351
+ try {
352
+ const headers = JSON.parse(newVal.reqHeaders)
353
+ localHeaders.value = Object.keys(headers).map(key => ({ key, value: headers[key] }))
354
+ } catch (e) {}
355
+ }
356
+
357
+ // Query
358
+ localQuery.value = []
359
+ if (newVal.reqQuery) {
360
+ try {
361
+ if (typeof newVal.reqQuery === 'string' && (newVal.reqQuery.startsWith('{') || newVal.reqQuery.startsWith('['))) {
362
+ const query = JSON.parse(newVal.reqQuery)
363
+ localQuery.value = Object.keys(query).map(key => ({ key, value: query[key] }))
364
+ } else {
365
+ const searchParams = new URLSearchParams(newVal.reqQuery)
366
+ for (const [key, value] of searchParams) {
367
+ localQuery.value.push({ key, value })
368
+ }
369
+ }
370
+ } catch (e) {
371
+ localQuery.value.push({ key: 'raw', value: newVal.reqQuery })
372
+ }
373
+ }
374
+ } else {
375
+ localHeaders.value = []
376
+ localQuery.value = []
377
+ }
378
+ }, { immediate: true })
379
+
380
+ function addHeader() {
381
+ localHeaders.value.push({ key: '', value: '' })
382
+ }
383
+ function removeHeader(index) {
384
+ localHeaders.value.splice(index, 1)
385
+ }
386
+ function addQuery() {
387
+ localQuery.value.push({ key: '', value: '' })
388
+ }
389
+ function removeQuery(index) {
390
+ localQuery.value.splice(index, 1)
391
+ }
392
+
393
+ async function saveInterface() {
394
+ if (!activeInterface.value) return
395
+
396
+ const headersObj = {}
397
+ localHeaders.value.forEach(h => {
398
+ if (h.key) headersObj[h.key] = h.value
399
+ })
400
+
401
+ const queryObj = {}
402
+ localQuery.value.forEach(q => {
403
+ if (q.key && q.key !== 'raw') queryObj[q.key] = q.value
404
+ })
405
+
406
+ activeInterface.value.reqHeaders = JSON.stringify(headersObj)
407
+ activeInterface.value.reqQuery = JSON.stringify(queryObj)
408
+
409
+ // Prepare data for backend
410
+ let modeToSend = activeInterface.value.mode
411
+ let scriptToSend = activeInterface.value.mockScript
412
+
413
+ if (modeToSend === 'mock') {
414
+ // If user selected Mock (JSON) mode explicitly, do NOT clear script as it is now used for hooks
415
+ // But we need to ensure mockData is valid if they want to use it
416
+
417
+ // If mockData is empty, generate it
418
+ if (!activeInterface.value.mockData) {
419
+ generateMock()
420
+ }
421
+ }
422
+
423
+ try {
424
+ await api.put(`/api/interfaces/${activeInterface.value.id}`, {
425
+ description: activeInterface.value.description,
426
+ reqHeaders: activeInterface.value.reqHeaders,
427
+ reqQuery: activeInterface.value.reqQuery,
428
+ reqBody: activeInterface.value.reqBody,
429
+ resBody: activeInterface.value.resBody,
430
+ staticData: activeInterface.value.staticData,
431
+ mode: modeToSend,
432
+ mockData: activeInterface.value.mockData,
433
+ mockResult: activeInterface.value.mockResult,
434
+ mockScript: scriptToSend
435
+ })
436
+ ElMessage.success('接口保存成功')
437
+ await fetchInterfaces()
438
+ // Update local model to match what was sent/saved (especially script clearing)
439
+ if (activeInterface.value.mode === 'mock') {
440
+ activeInterface.value.mockScript = ''
441
+ }
442
+ } catch(e) {
443
+ ElMessage.error('保存失败')
444
+ }
445
+ }
446
+
447
+ function generateMockTemplate() {
448
+ if (!activeInterface.value) return
449
+
450
+ function createTemplate(obj) {
451
+ if (Array.isArray(obj)) {
452
+ if (obj.length > 0) {
453
+ // 数组生成规则: "list|1-10": [ ... ]
454
+ return [createTemplate(obj[0])]
455
+ }
456
+ return []
457
+ } else if (typeof obj === 'object' && obj !== null) {
458
+ const newObj = {}
459
+ for (const k in obj) {
460
+ const val = obj[k]
461
+ if (typeof val === 'string') newObj[k] = '@string'
462
+ else if (typeof val === 'number') newObj[k] = '@integer(0, 100)'
463
+ else if (typeof val === 'boolean') newObj[k] = '@boolean'
464
+ else newObj[k] = createTemplate(val)
465
+ }
466
+ return newObj
467
+ }
468
+ return obj
469
+ }
470
+
471
+ if (activeInterface.value.resBody) {
472
+ try {
473
+ const json = JSON.parse(activeInterface.value.resBody)
474
+ const template = createTemplate(json)
475
+ activeInterface.value.mockData = JSON.stringify(template, null, 2)
476
+ ElMessage.success('Mock模板生成成功')
477
+ } catch(e) {
478
+ ElMessage.warning('代理结果不是有效的JSON,生成默认模板')
479
+ useDefaultTemplate()
480
+ }
481
+ } else {
482
+ useDefaultTemplate()
483
+ }
484
+
485
+ function useDefaultTemplate() {
486
+ activeInterface.value.mockData = JSON.stringify({
487
+ "string|1-10": "★",
488
+ "number|1-100": 100,
489
+ "boolean|1-2": true,
490
+ "object|2": {
491
+ "310000": "上海市",
492
+ "320000": "江苏省",
493
+ "330000": "浙江省",
494
+ "340000": "安徽省"
495
+ },
496
+ "list|1-5": [{
497
+ "id|+1": 1,
498
+ "name": "@name"
499
+ }]
500
+ }, null, 2)
501
+ ElMessage.success('已生成默认模板')
502
+ }
503
+ }
504
+
505
+ function generateMockResult() {
506
+ if (!activeInterface.value || !activeInterface.value.mockData) {
507
+ ElMessage.warning('请先填写Mock模板')
508
+ return
509
+ }
510
+ try {
511
+ // Remove comments if any (though JSON.parse doesn't support them, Monaco might mislead user)
512
+ const template = JSON.parse(activeInterface.value.mockData)
513
+ const data = Mock.mock(template)
514
+ activeInterface.value.mockResult = JSON.stringify(data, null, 2)
515
+ ElMessage.success('Mock数据生成成功')
516
+ } catch(e) {
517
+ ElMessage.error('生成失败: ' + e.message)
518
+ }
519
+ }
520
+
521
+ function generateMockScript() {
522
+ if (!activeInterface.value) return
523
+ if (activeInterface.value.mockScript) {
524
+ if (!confirm('确定要覆盖当前的脚本吗?')) return
525
+ }
526
+ activeInterface.value.mockScript = `/**
527
+ * JS 钩子脚本
528
+ * 支持 onRequest 和 onResponse 两个生命周期函数
529
+ */
530
+
531
+ module.exports = {
532
+ /**
533
+ * 请求开始前触发
534
+ * 可以修改 req,或者直接发送响应阻断后续流程
535
+ */
536
+ onRequest: async function({ req, res, Mock }) {
537
+ // console.log('onRequest', req.path)
538
+
539
+ // 示例:阻断请求直接返回
540
+ // if (req.query.block) {
541
+ // res.send({ msg: 'blocked by hook' })
542
+ // return
543
+ // }
544
+ },
545
+
546
+ /**
547
+ * 响应返回前触发
548
+ * 可以修改响应内容
549
+ * @param {string|object} body - 原始响应内容
550
+ * @param {number} statusCode - 响应状态码
551
+ * @return {string|object} - 返回修改后的响应内容
552
+ */
553
+ onResponse: async function({ body, req, res, statusCode, Mock }) {
554
+ // console.log('onResponse', statusCode)
555
+
556
+ // 示例:修改返回数据
557
+ // if (typeof body === 'object') {
558
+ // body._hooked = true
559
+ // }
560
+ return body
561
+ }
562
+ }`
563
+ }
564
+
565
+ function methodColor(method) {
566
+ const m = (method || '').toUpperCase()
567
+ if (m === 'GET') return 'success'
568
+ if (m === 'POST') return 'primary'
569
+ if (m === 'PUT') return 'warning'
570
+ if (m === 'DELETE') return 'danger'
571
+ return 'info'
572
+ }
573
+
574
+ function formatJson(str) {
575
+ if (!str) return ''
576
+ try {
577
+ const obj = JSON.parse(str)
578
+ return JSON.stringify(obj, null, 2)
579
+ } catch(e) {
580
+ return str
581
+ }
582
+ }
583
+
584
+ function formatTime(value) {
585
+ if (!value) return ''
586
+ const d = new Date(value)
587
+ if (Number.isNaN(d.getTime())) return String(value)
588
+ const pad = (n) => String(n).padStart(2, '0')
589
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
590
+ }
591
+
592
+ async function fetchRouteInfo() {
593
+ if (!routeId.value) return
594
+ loadingRoute.value = true
595
+ try {
596
+ const res = await api.get(`/api/routes/${routeId.value}`)
597
+ routeInfo.value = res.data.data
598
+ } catch (e) {
599
+ ElMessage.error('加载路由信息失败')
600
+ } finally {
601
+ loadingRoute.value = false
602
+ }
603
+ }
604
+
605
+ async function fetchInterfaces() {
606
+ if (!routeId.value) return
607
+ loadingInterfaces.value = true
608
+ try {
609
+ const res = await api.get(`/api/routes/${routeId.value}/interfaces`)
610
+ interfaces.value = res.data.data || []
611
+ } catch(e) {
612
+ ElMessage.error('加载接口失败')
613
+ } finally {
614
+ loadingInterfaces.value = false
615
+ }
616
+ }
617
+
618
+ function selectInterface(item) {
619
+ activeInterface.value = item
620
+ }
621
+
622
+ async function updateInterface(item) {
623
+ try {
624
+ await api.put(`/api/interfaces/${item.id}`, { description: item.description })
625
+ ElMessage.success('已保存描述')
626
+ } catch(e) {
627
+ ElMessage.error('保存失败')
628
+ }
629
+ }
630
+
631
+ async function sendRequest() {
632
+ if (!activeInterface.value) return
633
+ sending.value = true
634
+ try {
635
+ const method = activeInterface.value.method
636
+ const url = activeInterface.value.pathname
637
+
638
+ let params = {}
639
+ if (localQuery.value) {
640
+ localQuery.value.forEach(item => {
641
+ if (item.key && item.key !== 'raw') {
642
+ params[item.key] = item.value
643
+ }
644
+ })
645
+ }
646
+
647
+ let headers = {}
648
+ if (localHeaders.value) {
649
+ localHeaders.value.forEach(item => {
650
+ headers[item.key] = item.value
651
+ })
652
+ }
653
+
654
+ let data = null
655
+ if (activeInterface.value.reqBody) {
656
+ try {
657
+ data = JSON.parse(activeInterface.value.reqBody)
658
+ } catch(e) {
659
+ data = activeInterface.value.reqBody
660
+ }
661
+ }
662
+
663
+ const res = await axios({
664
+ method,
665
+ url,
666
+ params,
667
+ headers,
668
+ data
669
+ })
670
+
671
+ activeInterface.value.statusCode = res.status
672
+ activeInterface.value.resBody = typeof res.data === 'object' ? JSON.stringify(res.data, null, 2) : String(res.data)
673
+
674
+ ElMessage.success('请求发送成功')
675
+
676
+ } catch(e) {
677
+ console.error(e)
678
+ if (e.response) {
679
+ activeInterface.value.statusCode = e.response.status
680
+ activeInterface.value.resBody = typeof e.response.data === 'object' ? JSON.stringify(e.response.data, null, 2) : String(e.response.data)
681
+ ElMessage.warning('请求返回错误状态: ' + e.response.status)
682
+ } else {
683
+ ElMessage.error('请求发送失败: ' + e.message)
684
+ }
685
+ } finally {
686
+ sending.value = false
687
+ }
688
+ }
689
+
690
+ async function deleteInterface(item) {
691
+ try {
692
+ await ElMessageBox.confirm('确认删除此接口记录?', '提示')
693
+ await api.delete(`/api/interfaces/${item.id}`)
694
+ ElMessage.success('已删除')
695
+ activeInterface.value = null
696
+ await fetchInterfaces()
697
+ } catch(e) {}
698
+ }
699
+
700
+ Vue.onMounted(async () => {
701
+ const params = new URLSearchParams(window.location.search)
702
+ const id = params.get('routeId')
703
+ if (id) {
704
+ routeId.value = id
705
+ await fetchRouteInfo()
706
+ await fetchInterfaces()
707
+ } else {
708
+ ElMessage.error('缺少 routeId 参数')
709
+ }
710
+ })
711
+
712
+ return {
713
+ routeId,
714
+ routeInfo,
715
+ loadingRoute,
716
+ interfaces,
717
+ loadingInterfaces,
718
+ activeInterface,
719
+ interfaceSearch,
720
+ filteredInterfaces,
721
+ localHeaders,
722
+ localQuery,
723
+ addHeader,
724
+ removeHeader,
725
+ addQuery,
726
+ removeQuery,
727
+ saveInterface,
728
+ methodColor,
729
+ formatJson,
730
+ formatTime,
731
+ fetchInterfaces,
732
+ selectInterface,
733
+ updateInterface,
734
+ deleteInterface,
735
+ sending,
736
+ sendRequest,
737
+ generateMockTemplate,
738
+ generateMockResult,
739
+ generateMockScript
740
+ }
741
+ }
742
+ }
743
+
744
+ const app = Vue.createApp(App)
745
+ app.use(ElementPlus)
746
+ app.mount('#app')
747
+ </script>
748
+ </body>
749
+
750
+ </html>