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,429 @@
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%">
57
+ <el-header style="display:flex;align-items:center;gap:12px;">
58
+ <div style="font-weight:600;">代理路由管理</div>
59
+ <div class="muted">同机部署,直接调用后端 /api</div>
60
+ <div class="toolbar-right">
61
+ <el-button type="primary" @click="openCreate">新增路由</el-button>
62
+ <el-button @click="activeTab==='routes'?fetchRoutes():fetchLogs()">刷新</el-button>
63
+ </div>
64
+ </el-header>
65
+ <el-main class="container">
66
+ <el-tabs v-model="activeTab">
67
+ <el-tab-pane label="路由映射" name="routes">
68
+ <el-table :data="filteredRoutes" stripe border v-loading="loadingRoutes" row-key="id">
69
+ <el-table-column prop="id" label="ID" width="70">
70
+ <template #default="{ row }"><span class="code">{{ row.id }}</span></template>
71
+ </el-table-column>
72
+ <el-table-column prop="name" min-width="140">
73
+ <template #header>
74
+ <div style="display:flex;align-items:center;justify-content:space-between">
75
+ <span>名称</span>
76
+ <el-input v-model="searchName" size="small" placeholder="搜索" style="width:80px;font-weight:normal" clearable @click.stop />
77
+ </div>
78
+ </template>
79
+ <template #default="{ row }"><span class="code">{{ row.name }}</span></template>
80
+ </el-table-column>
81
+ <el-table-column prop="routePrefix" min-width="160">
82
+ <template #header>
83
+ <div style="display:flex;align-items:center;justify-content:space-between">
84
+ <span>路由前缀</span>
85
+ <el-input v-model="searchRoutePrefix" size="small" placeholder="搜索" style="width:100px;font-weight:normal" clearable @click.stop />
86
+ </div>
87
+ </template>
88
+ <template #default="{ row }"><span class="code">{{ row.routePrefix }}</span></template>
89
+ </el-table-column>
90
+ <el-table-column prop="target" min-width="220">
91
+ <template #header>
92
+ <div style="display:flex;align-items:center;justify-content:space-between">
93
+ <span>Target</span>
94
+ <el-input v-model="searchTarget" size="small" placeholder="搜索" style="width:140px;font-weight:normal" clearable @click.stop />
95
+ </div>
96
+ </template>
97
+ <template #default="{ row }"><span class="code">{{ row.target }}</span></template>
98
+ </el-table-column>
99
+ <el-table-column prop="priority" label="优先级" width="90">
100
+ <template #default="{ row }"><span class="code">{{ row.priority }}</span></template>
101
+ </el-table-column>
102
+ <el-table-column label="启用" width="90">
103
+ <template #default="{ row }">
104
+ <el-switch :model-value="!!row.enabled" @change="(v)=>toggleEnabled(row, v)" />
105
+ </template>
106
+ </el-table-column>
107
+ <el-table-column label="操作" width="280" fixed="right">
108
+ <template #default="{ row }">
109
+ <el-button size="small" type="primary" plain @click="openManage(row)">管理</el-button>
110
+ <el-button size="small" @click="openEdit(row)">编辑</el-button>
111
+ <el-button size="small" type="warning" @click="copyRoute(row)">复制</el-button>
112
+ <el-button size="small" type="danger" @click="deleteRoute(row)">删除</el-button>
113
+ </template>
114
+ </el-table-column>
115
+ </el-table>
116
+ <div class="muted" style="margin-top:10px;">
117
+ 匹配规则:请求路径以 routePrefix 开头即命中;优先级越高越先匹配
118
+ </div>
119
+ </el-tab-pane>
120
+ <el-tab-pane label="代理日志" name="logs">
121
+ <div class="toolbar">
122
+ <div class="muted">仅展示最近 50 条</div>
123
+ <div class="toolbar-right">
124
+ <el-button @click="fetchLogs">刷新日志</el-button>
125
+ </div>
126
+ </div>
127
+ <el-table :data="logs" stripe border row-key="id">
128
+ <el-table-column prop="id" label="ID" width="70">
129
+ <template #default="{ row }"><span class="code">{{ row.id }}</span></template>
130
+ </el-table-column>
131
+ <el-table-column prop="routeId" label="RouteId" width="90">
132
+ <template #default="{ row }"><span class="code">{{ row.routeId }}</span></template>
133
+ </el-table-column>
134
+ <el-table-column prop="method" label="Method" width="90">
135
+ <template #default="{ row }"><span class="code">{{ row.method }}</span></template>
136
+ </el-table-column>
137
+ <el-table-column prop="pathname" label="Pathname" min-width="200">
138
+ <template #default="{ row }"><span class="code">{{ row.pathname }}</span></template>
139
+ </el-table-column>
140
+ <el-table-column prop="statusCode" label="Status" width="90">
141
+ <template #default="{ row }"><span class="code">{{ row.statusCode }}</span></template>
142
+ </el-table-column>
143
+ <el-table-column prop="createdAt" label="时间" width="170">
144
+ <template #default="{ row }"><span class="code">{{ row.createdAt }}</span></template>
145
+ </el-table-column>
146
+ </el-table>
147
+ </el-tab-pane>
148
+ </el-tabs>
149
+ </el-main>
150
+ </el-container>
151
+
152
+ <el-drawer v-model="drawerOpen" size="520px" :with-header="false">
153
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
154
+ <div style="font-weight:600;">{{ form.id ? '编辑路由' : '新增路由' }}</div>
155
+ <div>
156
+ <el-button @click="drawerOpen=false">取消</el-button>
157
+ <el-button type="primary" :loading="saving" @click="saveRoute">保存</el-button>
158
+ </div>
159
+ </div>
160
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="96px">
161
+ <el-form-item label="名称" prop="name">
162
+ <el-input v-model="form.name" placeholder="可选" />
163
+ </el-form-item>
164
+ <el-form-item label="启用">
165
+ <el-switch v-model="form.enabled" />
166
+ </el-form-item>
167
+ <el-form-item label="优先级" prop="priority">
168
+ <el-input-number v-model="form.priority" :min="-1000" :max="1000" />
169
+ </el-form-item>
170
+ <el-form-item label="路由前缀" prop="routePrefix">
171
+ <el-input v-model="form.routePrefix" placeholder="/jeecg-boot" />
172
+ </el-form-item>
173
+ <el-form-item label="Target" prop="target">
174
+ <el-input v-model="form.target" placeholder="http://1.1.1.1/jeecg-boot" />
175
+ </el-form-item>
176
+ <el-form-item label="changeOrigin">
177
+ <el-switch v-model="form.changeOrigin" />
178
+ </el-form-item>
179
+ <el-form-item label="pathRewrite">
180
+ <el-input v-model="form.pathRewrite" type="textarea" :rows="5" placeholder='{"^/jeecg-boot": ""}' />
181
+ <div class="muted" style="margin-top:6px;">
182
+ 留空将自动生成:{{ defaultPathRewrite(form.routePrefix || '/') }}
183
+ </div>
184
+ </el-form-item>
185
+ </el-form>
186
+ </el-drawer>
187
+ </div>
188
+
189
+ <script src="/cdn/axios/dist/axios.min.js"></script>
190
+
191
+ <script>
192
+ const { ElMessage, ElMessageBox } = ElementPlus
193
+
194
+ const api = axios.create({
195
+ baseURL: '',
196
+ timeout: 15000,
197
+ headers: { 'Content-Type': 'application/json' }
198
+ })
199
+
200
+ const App = {
201
+ setup() {
202
+ const activeTab = Vue.ref('routes')
203
+ const loadingRoutes = Vue.ref(false)
204
+ const routes = Vue.ref([])
205
+ const logs = Vue.ref([])
206
+ const drawerOpen = Vue.ref(false)
207
+ const saving = Vue.ref(false)
208
+
209
+ const searchName = Vue.ref('')
210
+ const searchRoutePrefix = Vue.ref('')
211
+ const searchTarget = Vue.ref('')
212
+
213
+ const filteredRoutes = Vue.computed(() => {
214
+ return routes.value.filter(item => {
215
+ const name = item.name ? item.name.toLowerCase() : ''
216
+ const prefix = item.routePrefix ? item.routePrefix.toLowerCase() : ''
217
+ const target = item.target ? item.target.toLowerCase() : ''
218
+
219
+ const matchName = !searchName.value || name.includes(searchName.value.toLowerCase())
220
+ const matchPrefix = !searchRoutePrefix.value || prefix.includes(searchRoutePrefix.value.toLowerCase())
221
+ const matchTarget = !searchTarget.value || target.includes(searchTarget.value.toLowerCase())
222
+
223
+ return matchName && matchPrefix && matchTarget
224
+ })
225
+ })
226
+
227
+ function openManage(row) {
228
+ window.open('/manage.html?routeId=' + row.id, '_blank')
229
+ }
230
+
231
+ const formRef = Vue.ref(null)
232
+ const form = Vue.reactive({
233
+ id: null,
234
+ name: '',
235
+ enabled: true,
236
+ priority: 0,
237
+ routePrefix: '',
238
+ target: '',
239
+ changeOrigin: true,
240
+ pathRewrite: ''
241
+ })
242
+
243
+ const rules = {
244
+ routePrefix: [
245
+ { required: true, message: '请输入路由前缀,例如 /jeecg-boot', trigger: 'blur' },
246
+ {
247
+ validator: (rule, value, callback) => {
248
+ if (!value || typeof value !== 'string') return callback(new Error('routePrefix 无效'))
249
+ if (value[0] !== '/') return callback(new Error('routePrefix 必须以 / 开头'))
250
+ callback()
251
+ },
252
+ trigger: 'blur'
253
+ }
254
+ ],
255
+ target: [
256
+ { required: true, message: '请输入 target,例如 http://1.1.1.1/jeecg-boot', trigger: 'blur' }
257
+ ]
258
+ }
259
+
260
+ function defaultPathRewrite(prefix) {
261
+ const key = '^' + prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
262
+ return JSON.stringify({ [key]: '' })
263
+ }
264
+
265
+ async function fetchRoutes() {
266
+ loadingRoutes.value = true
267
+ try {
268
+ const res = await api.get('/api/routes')
269
+ routes.value = (res.data && res.data.data) || []
270
+ } catch (e) {
271
+ ElMessage.error(e?.response?.data?.message || e.message || '加载失败')
272
+ } finally {
273
+ loadingRoutes.value = false
274
+ }
275
+ }
276
+
277
+ async function fetchLogs() {
278
+ try {
279
+ const res = await api.get('/api/logs', { params: { limit: 50 } })
280
+ logs.value = (res.data && res.data.data) || []
281
+ } catch (e) {
282
+ ElMessage.error(e?.response?.data?.message || e.message || '加载失败')
283
+ }
284
+ }
285
+
286
+ function resetForm() {
287
+ form.id = null
288
+ form.name = ''
289
+ form.enabled = true
290
+ form.priority = 0
291
+ form.routePrefix = ''
292
+ form.target = ''
293
+ form.changeOrigin = true
294
+ form.pathRewrite = ''
295
+ }
296
+
297
+ function openCreate() {
298
+ resetForm()
299
+ drawerOpen.value = true
300
+ }
301
+
302
+ function openEdit(row) {
303
+ form.id = row.id
304
+ form.name = row.name || ''
305
+ form.enabled = !!row.enabled
306
+ form.priority = Number(row.priority || 0)
307
+ form.routePrefix = row.routePrefix || ''
308
+ form.target = row.target || ''
309
+ form.changeOrigin = row.changeOrigin !== false
310
+ form.pathRewrite = row.pathRewrite || ''
311
+ drawerOpen.value = true
312
+ }
313
+
314
+ function copyRoute(row) {
315
+ form.id = null
316
+ form.name = (row.name || '') + ' - 复制'
317
+ form.enabled = !!row.enabled
318
+ form.priority = Number(row.priority || 0)
319
+ form.routePrefix = row.routePrefix || ''
320
+ form.target = row.target || ''
321
+ form.changeOrigin = row.changeOrigin !== false
322
+ form.pathRewrite = row.pathRewrite || ''
323
+ drawerOpen.value = true
324
+ }
325
+
326
+ async function saveRoute() {
327
+ if (!formRef.value) return
328
+ await formRef.value.validate(async (valid) => {
329
+ if (!valid) return
330
+ saving.value = true
331
+ try {
332
+ const payload = {
333
+ name: form.name,
334
+ enabled: form.enabled,
335
+ priority: form.priority,
336
+ routePrefix: form.routePrefix,
337
+ target: form.target,
338
+ changeOrigin: form.changeOrigin,
339
+ pathRewrite: form.pathRewrite || defaultPathRewrite(form.routePrefix)
340
+ }
341
+ if (form.id) {
342
+ await api.put('/api/routes/' + form.id, payload)
343
+ ElMessage.success('已保存')
344
+ } else {
345
+ await api.post('/api/routes', payload)
346
+ ElMessage.success('已创建')
347
+ }
348
+ drawerOpen.value = false
349
+ await fetchRoutes()
350
+ } catch (e) {
351
+ ElMessage.error(e?.response?.data?.message || e.message || '保存失败')
352
+ } finally {
353
+ saving.value = false
354
+ }
355
+ })
356
+ }
357
+
358
+ async function deleteRoute(row) {
359
+ try {
360
+ await ElMessageBox.confirm('确认删除此路由?', '提示', { type: 'warning' })
361
+ await api.delete('/api/routes/' + row.id)
362
+ ElMessage.success('已删除')
363
+ await fetchRoutes()
364
+ } catch (e) {
365
+ if (e === 'cancel' || e === 'close') return
366
+ ElMessage.error(e?.response?.data?.message || e.message || '删除失败')
367
+ }
368
+ }
369
+
370
+ async function toggleEnabled(row, enabled) {
371
+ try {
372
+ await api.put('/api/routes/' + row.id, { enabled })
373
+ ElMessage.success('已更新')
374
+ await fetchRoutes()
375
+ } catch (e) {
376
+ ElMessage.error(e?.response?.data?.message || e.message || '更新失败')
377
+ await fetchRoutes()
378
+ }
379
+ }
380
+
381
+ function formatTime(value) {
382
+ if (!value) return ''
383
+ const d = new Date(value)
384
+ if (Number.isNaN(d.getTime())) return String(value)
385
+ const pad = (n) => String(n).padStart(2, '0')
386
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
387
+ }
388
+
389
+ Vue.onMounted(async () => {
390
+ await fetchRoutes()
391
+ await fetchLogs()
392
+ })
393
+
394
+ return {
395
+ searchName,
396
+ searchRoutePrefix,
397
+ searchTarget,
398
+ filteredRoutes,
399
+ activeTab,
400
+ loadingRoutes,
401
+ routes,
402
+ logs,
403
+ drawerOpen,
404
+ saving,
405
+ formRef,
406
+ form,
407
+ rules,
408
+ fetchRoutes,
409
+ fetchLogs,
410
+ openCreate,
411
+ openEdit,
412
+ copyRoute,
413
+ openManage,
414
+ saveRoute,
415
+ deleteRoute,
416
+ toggleEnabled,
417
+ defaultPathRewrite,
418
+ formatTime
419
+ }
420
+ }
421
+ }
422
+
423
+ const app = Vue.createApp(App)
424
+ app.use(ElementPlus)
425
+ app.mount('#app')
426
+ </script>
427
+ </body>
428
+
429
+ </html>