w-web-api 1.0.52 → 1.0.53

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 (84) hide show
  1. package/_gh-pages-deploy/.nojekyll +0 -0
  2. package/_gh-pages-deploy/WWebApi.html +898 -0
  3. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  4. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  5. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  6. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  7. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  8. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  9. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  10. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  11. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  12. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  13. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  14. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  15. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  16. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  17. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  18. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  19. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  20. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  21. package/_gh-pages-deploy/index.html +84 -0
  22. package/_gh-pages-deploy/scripts/collapse.js +39 -0
  23. package/_gh-pages-deploy/scripts/commonNav.js +28 -0
  24. package/_gh-pages-deploy/scripts/linenumber.js +25 -0
  25. package/_gh-pages-deploy/scripts/nav.js +12 -0
  26. package/_gh-pages-deploy/scripts/polyfill.js +4 -0
  27. package/_gh-pages-deploy/scripts/prettify/Apache-License-2.0.txt +202 -0
  28. package/_gh-pages-deploy/scripts/prettify/lang-css.js +2 -0
  29. package/_gh-pages-deploy/scripts/prettify/prettify.js +28 -0
  30. package/_gh-pages-deploy/scripts/search.js +99 -0
  31. package/{docs/WWebApi.mjs.html → _gh-pages-deploy/server_WWebApi.mjs.html} +96 -39
  32. package/_gh-pages-deploy/src_components_Common_CheckYesNo.vue.html +187 -0
  33. package/_gh-pages-deploy/styles/jsdoc.css +776 -0
  34. package/_gh-pages-deploy/styles/prettify.css +80 -0
  35. package/dist/css/app.fe69497a.css +1 -0
  36. package/dist/index.tmp +4 -2
  37. package/dist/js/app-legacy.15819aa7.js +1 -0
  38. package/dist/js/app.15819aa7.js +1 -0
  39. package/dist/js/chunk-vendors-legacy.c1a32dd1.js +15 -0
  40. package/dist/js/chunk-vendors.c1a32dd1.js +15 -0
  41. package/dist/w-web-api.umd.js +2 -2
  42. package/dist/w-web-api.umd.js.map +1 -1
  43. package/docs/WWebApi.html +2 -2
  44. package/docs/index.html +1 -1
  45. package/docs/server_WWebApi.mjs.html +1012 -0
  46. package/docs/src_components_Common_CheckYesNo.vue.html +187 -0
  47. package/g.getSettings.mjs +3 -0
  48. package/package.json +15 -11
  49. package/public/index.html +9 -2
  50. package/server/WWebApi.mjs +93 -36
  51. package/server/procApis.mjs +62 -0
  52. package/server/procLang.mjs +386 -0
  53. package/server/procProxy.mjs +120 -0
  54. package/server/srLog.mjs +30 -0
  55. package/server/staLogs/staEvent.mjs +129 -0
  56. package/server/staLogs/test_staEvent.mjs +132 -0
  57. package/settings.json +14 -0
  58. package/src/App.vue +11 -7
  59. package/src/assets/scalar-ui.css +113 -0
  60. package/src/components/Common/CheckYes.vue +102 -0
  61. package/src/components/Common/CheckYesNo.vue +115 -0
  62. package/src/components/Common/LoadingWinBar.vue +114 -0
  63. package/src/components/Layout.vue +8 -7
  64. package/src/components/LayoutContent.vue +308 -333
  65. package/src/components/LayoutContentEdit.vue +577 -0
  66. package/src/components/LayoutContentStats.vue +485 -0
  67. package/src/components/LayoutContentTest.vue +799 -0
  68. package/src/components/MdPanel.vue +80 -58
  69. package/src/main.js +8 -0
  70. package/src/plugins/mUI.mjs +19 -4
  71. package/src/schema/tables/apis.mjs +30 -0
  72. package/srv.mjs +47 -19
  73. package/tableTags.json +1 -1
  74. package/.github/workflows/ci-test.yml +0 -24
  75. package/.vscode/launch.json +0 -18
  76. package/db/wapis/apis/data.mdb +0 -0
  77. package/db/wapis/apis/lock.mdb +0 -0
  78. package/dist/css/app.b5cf2ef5.css +0 -1
  79. package/dist/js/app-legacy.927b5679.js +0 -1
  80. package/dist/js/app.927b5679.js +0 -1
  81. package/dist/js/chunk-vendors-legacy.ffb0c6c5.js +0 -13
  82. package/dist/js/chunk-vendors.ffb0c6c5.js +0 -13
  83. package/tableTags-web-api.json +0 -1
  84. package/test/all.test.mjs +0 -10
@@ -5,8 +5,28 @@
5
5
  @domresize="resize"
6
6
  :changeApis="changeApis"
7
7
  >
8
+ <div style="display:flex; height:100%; width:100%;">
9
+
10
+ <!-- 左側垂直主選單(仿 w-web-sso):切換 API 工作區 / 統計資訊 -->
11
+ <div :style="`width:${widthMainMenu}px; flex-shrink:0; height:100%; box-sizing:border-box; background:var(--bg-2); border-right:1px solid var(--border); display:flex; flex-direction:column; padding:8px 0; gap:4px;`">
12
+ <button
13
+ v-for="m in mainMenus"
14
+ :key="m.id"
15
+ class="w-mm-btn"
16
+ :class="{on: section===m.id}"
17
+ @click="onClickMainMenu(m)"
18
+ >
19
+ <svg viewBox="0 0 24 24" width="22" height="22" style="display:block; margin:0 auto;"><path :d="m.icon" fill="currentColor"></path></svg>
20
+ <div class="w-mm-label">{{$t(m.textKey)}}</div>
21
+ </button>
22
+ </div>
23
+
24
+ <!-- 工作區 -->
25
+ <div style="flex:1; min-width:0; height:100%; position:relative;">
26
+
27
+ <template v-if="section==='api'">
8
28
 
9
- <template v-if="syncState">
29
+ <template v-if="syncState">
10
30
 
11
31
  <WDrawer
12
32
  :style="`height:${panelHeight}px; width:100%;`"
@@ -23,25 +43,44 @@
23
43
  >
24
44
 
25
45
  <template v-slot:drawer="props">
26
- <div :style="`height:${props.height}px; background:#fff; position:relative;`">
46
+ <div :style="`height:${props.height}px; background:var(--bg-2); border-right:1px solid var(--border); position:relative;`">
47
+
48
+ <!-- 搜尋 + 新增API -->
49
+ <div :style="`height:${heightDrawerHeader}px; box-sizing:border-box; padding:8px 40px 8px 8px; border-bottom:1px solid var(--border); display:flex; gap:6px; align-items:center;`">
50
+ <input
51
+ v-model="search"
52
+ type="text"
53
+ :placeholder="$t('searchApiPlaceholder')"
54
+ style="flex:1; min-width:0; padding:5px 8px; border:1px solid var(--border); border-radius:var(--radius); font-size:0.85rem; outline:none; box-sizing:border-box; transition:border-color .15s, box-shadow .15s;"
55
+ @focus="$event.target.style.borderColor='var(--accent)'; $event.target.style.boxShadow='var(--focus)'"
56
+ @blur="$event.target.style.borderColor='var(--border)'; $event.target.style.boxShadow='none'"
57
+ />
58
+ <button
59
+ @click="onClickAddApi"
60
+ class="w-btn-primary"
61
+ style="flex-shrink:0; white-space:nowrap;"
62
+ >+ {{$t('addApi')}}</button>
63
+ </div>
27
64
 
28
65
  <template v-if="apisTree.length>0">
29
66
  <WTree
30
- :style="`height:${props.height}px;`"
31
- :viewHeightMax="props.height"
67
+ :style="`height:${props.height-heightDrawerHeader}px;`"
68
+ :viewHeightMax="props.height-heightDrawerHeader"
32
69
  :data="apisTree"
33
70
  :iconHeight="lineHeightTree"
34
71
  :defItemHeight="lineHeightTree"
35
72
  :activable="true"
36
73
  :itemActive="apiActive"
37
74
  :funActive="funActive"
38
- :itemTextColorActive="'#000'"
39
- :itemBackgroundColorActive ="'rgba(100,100,100,0.15)'"
75
+ :itemTextColorActive="'var(--c-1)'"
76
+ :itemBackgroundColorActive ="'linear-gradient(to right, #0099ff 3px, rgba(0,153,255,0.08) 3px)'"
77
+ :indent="0.25"
78
+ :iconSize="12"
40
79
  >
41
80
  <template v-slot:item="props">
42
81
 
43
82
  <div
44
- :style="`display:flex; align-items:center; min-height:${lineHeightTree}px;`"
83
+ :style="`display:flex; align-items:center; min-height:${lineHeightTree}px; color:var(--c-3); font-size:11px; font-weight:600; letter-spacing:.05em; text-transform:uppercase;`"
45
84
  v-if="props.data.type!=='node'"
46
85
  >
47
86
  {{props.data.key}}
@@ -49,12 +88,12 @@
49
88
 
50
89
  <div
51
90
  :style="`display:flex; align-items:center; min-height:${lineHeightTree}px; cursor:pointer;`"
52
- @click="ckItem(getItemByKey(props.data.text))"
91
+ @click="ckItem(getItemByKey(props.data.text), props.data)"
53
92
  v-else
54
93
  >
55
94
 
56
95
  <div style="padding-right:5px;">
57
- <div :style="`padding:0px 6px; font-size:0.7rem; border-radius:10px; border:1px solid #ddd; color:#fff; background:${getMethodColorByKey(props.data.text)};`">
96
+ <div class="w-mb" :class="getMethodByKey(props.data.text).toLowerCase()">
58
97
  {{getMethodByKey(props.data.text)}}
59
98
  </div>
60
99
  </div>
@@ -81,7 +120,7 @@
81
120
  >
82
121
  <WButtonCircle
83
122
  :paddingStyle="{v:3,h:3}"
84
- :icon="'mdi-arrow-left'"
123
+ :icon="mdiArrowLeft"
85
124
  :iconSize="16"
86
125
  :backgroundColor="'#fff'"
87
126
  :backgroundColorHover="'#eee'"
@@ -101,283 +140,100 @@
101
140
  <template v-slot:content="props">
102
141
  <div :style="`height:${props.height}px; width:${props.width}px; position:relative;`">
103
142
 
104
- <!-- 選擇API之顯示區 -->
105
- <template>
143
+ <!-- 模式切換(文件/編輯/測試) + 內容 -->
144
+ <template v-if="apiSelect || newMode">
145
+
146
+ <div :style="`height:${heightModeBar}px; box-sizing:border-box; padding:8px 12px; border-bottom:1px solid var(--border); background:var(--bg-1); display:flex; align-items:center;`">
147
+ <div class="w-seg">
148
+ <button class="w-seg-btn" :class="{on: mode==='docs'}" @click="onChangeMode({id:'docs'})">{{$t('tabDocs')}}</button>
149
+ <button class="w-seg-btn" :class="{on: mode==='edit'}" @click="onChangeMode({id:'edit'})">{{$t('tabEdit')}}</button>
150
+ <button class="w-seg-btn" :class="{on: mode==='test'}" @click="onChangeMode({id:'test'})">{{$t('tabTest')}}</button>
151
+ </div>
152
+ </div>
106
153
 
107
154
  <div
108
- :style="`height:${props.height}px; overflow-y:auto;`"
109
- v-if="apiSelect"
155
+ :style="`height:${props.height-heightModeBar}px; overflow-y:auto; background:var(--bg-1);`"
156
+ v-if="mode==='docs' && apiSelect"
110
157
  >
111
- <div style="padding:20px;">
112
-
113
- <div style="padding:5px;">
114
-
115
- <div class="bk-title" style="">
116
- {{$t('apiUrl')}}
117
- </div>
118
-
119
- <div style="padding:10px; border-radius:5px; border:1px solid #ddd; background:#fff; display:flex; align-items:center;">
120
-
121
- <div style="padding-right:5px;">
122
- <div :style="`padding:0px 6px; font-size:0.7rem; border-radius:10px; border:1px solid #ddd; color:#fff; background:${getMethodColor(apiSelect)};`">
123
- {{getMethod(apiSelect)}}
124
- </div>
125
- </div>
126
-
127
- <div style="font-size:0.8rem;">
128
- {{$ui.gv(apiSelect,'url')}}
129
- </div>
130
-
131
- </div>
132
-
158
+ <div style="padding:30px 36px;">
159
+
160
+ <!-- op header -->
161
+ <div><span class="w-mb" :class="getMethod(apiSelect).toLowerCase()">{{getMethod(apiSelect)}}</span></div>
162
+ <div class="op-title">{{$ui.gv(apiSelect,'name')}}</div>
163
+ <div class="op-path"><span class="w-mb" :class="getMethod(apiSelect).toLowerCase()">{{getMethod(apiSelect)}}</span><span class="w-mono">{{$ui.gv(apiSelect,'url')}}</span></div>
164
+ <div class="op-desc">{{$ui.gv(apiSelect,'description')}}</div>
165
+
166
+ <!-- metadata pills -->
167
+ <div class="pills">
168
+ <span class="pill" v-if="$ui.gv(apiSelect,'version')"><b>{{$ui.gv(apiSelect,'version')}}</b></span>
169
+ <span class="pill" v-if="$ui.gv(apiSelect,'levels')">{{$t('levels')}} <b>{{$ui.gv(apiSelect,'levels')}}</b></span>
170
+ <span class="pill" v-if="keywordsList.length">{{$t('keywords')}}<span class="kw-chip" v-for="kw in keywordsList" :key="'kw-'+kw">{{kw}}</span></span>
171
+ <span class="pill ok" v-if="$ui.gv(apiSelect,'state')">{{$ui.gv(apiSelect,'state')}}</span>
172
+ <span class="pill" v-if="$ui.gv(apiSelect,'creator')">{{$t('creator')}} <b>{{$ui.gv(apiSelect,'creator')}}</b></span>
173
+ <span class="pill" v-if="$ui.gv(apiSelect,'dataSource')">{{$t('dataSource')}} <b>{{$ui.gv(apiSelect,'dataSource')}}</b></span>
133
174
  </div>
134
175
 
135
- <div style="padding:10px;">
136
-
137
- <div style="color:#485ed5;">
138
- {{$ui.gv(apiSelect,'name')}}
139
- </div>
140
-
141
- <div class="bk-title" style="">
142
- {{$ui.gv(apiSelect,'description')}}
143
- </div>
144
-
145
- </div>
146
-
147
- <div style="padding:10px;">
148
-
149
- <div class="bk" style="">
150
-
151
- <div class="bk-title" style="">
152
- {{$t('tokens')}}
153
- </div>
154
-
155
- <div class="bk-item" style="">
156
- {{$ui.gv(apiSelect,'tokens')}}
157
- </div>
158
-
159
- </div>
160
-
161
- <div class="bk" style="">
162
-
163
- <div class="bk-title" style="">
164
- {{$t('version')}}
165
- </div>
166
-
167
- <div class="bk-item" style="">
168
- {{$ui.gv(apiSelect,'version')}}
169
- </div>
170
-
171
- </div>
172
-
173
- <div class="bk" style="">
174
-
175
- <div class="bk-title" style="">
176
- {{$t('levels')}}
177
- </div>
178
-
179
- <div class="bk-item" style="">
180
- {{$ui.gv(apiSelect,'')}}
181
- </div>
182
-
183
- </div>
184
-
185
- <div class="bk" style="">
186
-
187
- <div class="bk-title" style="">
188
- {{$t('keywords')}}
189
- </div>
190
-
191
- <div class="bk-item" style="">
192
- {{$ui.gv(apiSelect,'')}}
193
- </div>
194
-
195
- </div>
196
-
197
- <div class="bk" style="">
198
-
199
- <div class="bk-title" style="">
200
- {{$t('state')}}
201
- </div>
202
-
203
- <div class="bk-item" style="">
204
- {{$ui.gv(apiSelect,'')}}
205
- </div>
206
-
207
- </div>
208
-
209
- <div class="bk" style="">
210
-
211
- <div class="bk-title" style="">
212
- {{$t('timeCreate')}}
213
- </div>
214
-
215
- <div class="bk-item" style="">
216
- {{$ui.gv(apiSelect,'timeCreate',getDay)}}
217
- </div>
218
-
219
- </div>
220
-
221
- <div class="bk" style="">
222
-
223
- <div class="bk-title" style="">
224
- {{$t('timeUpdate')}}
225
- </div>
226
-
227
- <div class="bk-item" style="">
228
- {{$ui.gv(apiSelect,'timeUpdate',getDay)}}
229
- </div>
230
-
231
- </div>
232
-
233
- <div class="bk" style="">
234
-
235
- <div class="bk-title" style="">
236
- {{$t('creator')}}
237
- </div>
238
-
239
- <div class="bk-item" style="">
240
- {{$ui.gv(apiSelect,'creator')}}
241
- </div>
242
-
243
- </div>
244
-
245
- <div class="bk" style="">
246
-
247
- <div class="bk-title" style="">
248
- {{$t('dataSource')}}
249
- </div>
250
-
251
- <div class="bk-item" style="">
252
- {{$ui.gv(apiSelect,'dataSource')}}
253
- </div>
254
-
255
- </div>
256
-
257
- <div class="bk" style="">
258
-
259
- <div class="bk-title" style="">
260
- {{$t('isActive')}}
176
+ <!-- two-column split -->
177
+ <div class="split">
178
+ <!-- 左欄:參數說明 -->
179
+ <div>
180
+ <div class="dsec">
181
+ <div class="dsec-h">{{$t('docInput')}}</div>
182
+ <div class="doc-md"><MdPanel :md="$ui.gv(apiSelect,'mdInputParams')"></MdPanel></div>
261
183
  </div>
262
-
263
- <div class="bk-item" style="">
264
- {{$ui.gv(apiSelect,'isActive')}}
184
+ <div class="dsec">
185
+ <div class="dsec-h">{{$t('docOutputFields')}}</div>
186
+ <div class="doc-md"><MdPanel :md="$ui.gv(apiSelect,'mdOutputParams')"></MdPanel></div>
265
187
  </div>
266
-
267
- </div>
268
-
269
- </div>
270
-
271
- <div style="padding:10px;">
272
-
273
- <div class="bk-title" style="">
274
- {{$t('mdInputParams')}}
275
- </div>
276
-
277
- <div class="bk-item" style="padding:10px;">
278
- <MdPanel
279
- :md="$ui.gv(apiSelect,'mdInputParams')"
280
- ></MdPanel>
281
- </div>
282
-
283
- </div>
284
-
285
- <div style="padding:10px;">
286
-
287
- <div class="bk-title" style="">
288
- {{$t('inputExample')}}
289
- </div>
290
-
291
- <div class="bk-item" style="padding:10px;">
292
- {{$ui.gv(apiSelect,'inputExample')}}
293
- </div>
294
-
295
- </div>
296
-
297
- <div style="padding:10px;">
298
-
299
- <div class="bk-title" style="">
300
- {{$t('mdOutputParams')}}
301
188
  </div>
302
-
303
- <div class="bk-item" style="padding:10px;">
304
- <MdPanel
305
- :md="$ui.gv(apiSelect,'mdOutputParams')"
306
- ></MdPanel>
307
- </div>
308
-
309
- </div>
310
-
311
- <div style="padding:10px;">
312
-
313
- <div class="bk-title" style="">
314
- {{$t('outputExample')}}
315
- </div>
316
-
317
- <div style="padding-top:3px;">
318
-
319
- <div style="padding-left:10px; margin-bottom:-1px;">
320
- <!-- 因上下皆有border, 故需負margin-bottom來吃掉重複border -->
321
- <WGroupChipRadio
322
- :items="optputMenuItems"
323
- :value="optputMenuItemSelect"
324
- @input="changeOptputMenuItemSelect"
325
- :group="true"
326
- :group-border-radius-style="{top:true}"
327
- :group-shift="7"
328
- :border-radius="20"
329
- :border-color="'#ddd'"
330
- :border-color-hover="'#dadada'"
331
- :border-color-active="'orange lighten-2'"
332
- :margin-style="{}"
333
- ></WGroupChipRadio>
189
+ <!-- 右欄:code rail -->
190
+ <div class="rail">
191
+ <div class="card">
192
+ <div class="card-h"><span class="lab">{{$t('docRequest')}}</span><span class="w-muted" style="margin-left:auto; font-size:11.5px;">cURL</span></div>
193
+ <pre class="code">{{curlOf(apiSelect)}}</pre>
334
194
  </div>
335
-
336
- <div style="border:1px solid #ddd; background:#fff">
337
-
338
- <div
339
- style="padding:10px 15px;"
340
- v-if="optputMenuItemSelectId==='tree'"
341
- >
342
- <WJsonView
343
- style="width:100%;"
344
- :viewHeightMax="null"
345
- :data="getOutputJsonObj(apiSelect)"
346
- ></WJsonView>
347
- </div>
348
-
349
- <div
350
- style="padding:15px; color:#555; font-size:0.85rem;"
351
- v-if="optputMenuItemSelectId==='raw'
352
- ">
353
- <pre style="margin:0px; padding:0px;">{{getOutputJson(apiSelect)}}</pre>
354
- </div>
355
-
195
+ <div class="card">
196
+ <div class="card-h"><span class="lab">{{$t('resTitle')}}</span><span class="st">200 OK</span></div>
197
+ <pre class="code" v-html="jsonHighlight($ui.gv(apiSelect,'outputExample'))"></pre>
356
198
  </div>
357
-
358
199
  </div>
359
-
360
200
  </div>
361
201
 
362
202
  </div>
363
203
  </div>
364
204
 
365
- <div
366
- style="padding:20px; font-size:0.9rem;"
367
- v-else
368
- >
369
- {{$t('noSelectApi')}}
370
- </div>
205
+ <LayoutContentEdit
206
+ v-else-if="mode==='edit'"
207
+ :item="editItem"
208
+ :height="props.height-heightModeBar"
209
+ :isNew="newMode"
210
+ @saved="onEditSaved"
211
+ @deleted="onEditDeleted"
212
+ @cancel="onEditCancel"
213
+ ></LayoutContentEdit>
214
+
215
+ <LayoutContentTest
216
+ v-else-if="mode==='test' && apiSelect"
217
+ :item="apiSelect"
218
+ :height="props.height-heightModeBar"
219
+ ></LayoutContentTest>
371
220
 
372
221
  </template>
373
222
 
223
+ <div
224
+ style="padding:20px; font-size:0.9rem;"
225
+ v-else
226
+ >
227
+ {{$t('noSelectApi')}}
228
+ </div>
229
+
374
230
  <div
375
231
  :style="`position:absolute; top:1px; left:4px;`"
376
232
  v-if="!drawer"
377
233
  >
378
234
  <WButtonCircle
379
235
  :paddingStyle="{v:3,h:3}"
380
- :icon="'mdi-arrow-right'"
236
+ :icon="mdiArrowRight"
381
237
  :iconSize="16"
382
238
  :backgroundColor="'#fff'"
383
239
  :backgroundColorHover="'#eee'"
@@ -406,6 +262,16 @@
406
262
  </div>
407
263
  </template>
408
264
 
265
+ </template>
266
+
267
+ <LayoutContentStats
268
+ v-else-if="section==='stats'"
269
+ :height="panelHeight"
270
+ ></LayoutContentStats>
271
+
272
+ </div>
273
+
274
+ </div>
409
275
  </div>
410
276
  </template>
411
277
 
@@ -416,28 +282,31 @@ import each from 'lodash-es/each.js'
416
282
  import size from 'lodash-es/size.js'
417
283
  import trim from 'lodash-es/trim.js'
418
284
  import find from 'lodash-es/find.js'
285
+ import filter from 'lodash-es/filter.js'
419
286
  import cloneDeep from 'lodash-es/cloneDeep.js'
420
287
  import isobj from 'wsemi/src/isobj.mjs'
421
288
  import iseobj from 'wsemi/src/iseobj.mjs'
422
- import j2o from 'wsemi/src/j2o.mjs'
423
289
  import timemsTZ2day from 'wsemi/src/timemsTZ2day.mjs'
424
290
  import convertToTree from 'wsemi/src/convertToTree.mjs'
291
+ import { mdiArrowLeft, mdiArrowRight, mdiCloudBraces, mdiChartBar } from '@mdi/js'
425
292
  import WButtonCircle from 'w-component-vue/src/components/WButtonCircle.vue'
426
- import WGroupChipRadio from 'w-component-vue/src/components/WGroupChipRadio.vue'
427
293
  import WDrawer from 'w-component-vue/src/components/WDrawer.vue'
428
294
  import WTree from 'w-component-vue/src/components/WTree.vue'
429
- import WJsonView from 'w-component-vue/src/components/WJsonView.vue'
430
295
  import MdPanel from './MdPanel.vue'
296
+ import LayoutContentEdit from './LayoutContentEdit.vue'
297
+ import LayoutContentTest from './LayoutContentTest.vue'
298
+ import LayoutContentStats from './LayoutContentStats.vue'
431
299
 
432
300
 
433
301
  export default {
434
302
  components: {
435
303
  WButtonCircle,
436
- WGroupChipRadio,
437
304
  WDrawer,
438
305
  WTree,
439
- WJsonView,
440
306
  MdPanel,
307
+ LayoutContentEdit,
308
+ LayoutContentTest,
309
+ LayoutContentStats,
441
310
  },
442
311
  props: {
443
312
  },
@@ -447,6 +316,14 @@ export default {
447
316
  panelWidth: 0,
448
317
  panelHeight: 0,
449
318
 
319
+ //左側垂直主選單
320
+ section: 'api', //'api' | 'stats'
321
+ widthMainMenu: 76,
322
+ mainMenus: [
323
+ { id: 'api', textKey: 'mmApi', icon: mdiCloudBraces },
324
+ { id: 'stats', textKey: 'mmStaInfor', icon: mdiChartBar },
325
+ ],
326
+
450
327
  drawer: true,
451
328
  drawerWidth: 320,
452
329
  drawerWidthMin: 200,
@@ -457,16 +334,24 @@ export default {
457
334
  apisTree: [],
458
335
  kpApisTree: {},
459
336
  apiActive: null,
337
+ mdiArrowLeft,
338
+ mdiArrowRight,
460
339
  apiSelect: null,
461
340
 
462
341
  kpColorMethod: {
463
- GET: '#7c2',
464
- POST: '#68f',
465
- PUT: '#f82',
466
- DEL: '#f26',
342
+ GET: '#0082d0',
343
+ POST: '#069061',
344
+ PUT: '#ff5800',
345
+ DEL: '#ef0006',
467
346
  },
468
347
 
469
- optputMenuItemSelectId: 'tree',
348
+ //API 管理:模式切換 / 新增 / 搜尋
349
+ mode: 'docs', //'docs' | 'edit' | 'test'
350
+ newMode: false,
351
+ blankApi: {},
352
+ search: '',
353
+ heightModeBar: 46,
354
+ heightDrawerHeader: 48,
470
355
 
471
356
  }
472
357
  },
@@ -484,32 +369,25 @@ export default {
484
369
  let vo = this
485
370
  //trigger
486
371
  let apis = vo.apis
372
+ let search = vo.search //讓搜尋變更也觸發tree重建
373
+ void search
487
374
  vo.genTree(apis)
488
375
  return ''
489
376
  },
490
377
 
491
- optputMenuItems: function() {
378
+ editItem: function() {
492
379
  let vo = this
493
- let ms = [
494
- {
495
- id: 'tree',
496
- text: vo.$t('outputMenuTree'),
497
- icon: 'mdi-vanity-light',
498
- },
499
- {
500
- id: 'raw',
501
- text: vo.$t('outputMenuRaw'),
502
- icon: 'mdi-lan',
503
- },
504
- ]
505
- return ms
380
+ return vo.newMode ? vo.blankApi : vo.apiSelect
506
381
  },
507
382
 
508
- optputMenuItemSelect: function() {
383
+ //關鍵字以分號切開成陣列(trim + 去空),供 pills 區逐一以 chip 呈現
384
+ keywordsList: function() {
509
385
  let vo = this
510
- let r = find(vo.optputMenuItems, { id: vo.optputMenuItemSelectId })
511
- // console.log(r)
512
- return r
386
+ let s = get(vo.apiSelect, 'keywords', '')
387
+ if (!s) {
388
+ return []
389
+ }
390
+ return String(s).split(';').map((x) => x.trim()).filter((x) => x !== '')
513
391
  },
514
392
 
515
393
  },
@@ -530,14 +408,17 @@ export default {
530
408
  genTree: function () {
531
409
  let vo = this
532
410
 
411
+ //保留目前選取的API id, 重建後盡量維持選取(避免儲存/同步後選取跳回第一筆)
412
+ let prevId = get(vo.apiSelect, 'id', '')
413
+
533
414
  //default
534
415
  vo.apiActive = null
535
- vo.apiSelect = null
536
416
  vo.apisTree = []
537
417
  vo.kpApisTree = {}
538
418
 
539
419
  //check
540
420
  if (size(vo.apis) === 0) {
421
+ vo.apiSelect = null
541
422
  return
542
423
  }
543
424
 
@@ -545,6 +426,15 @@ export default {
545
426
  let apis = cloneDeep(vo.apis)
546
427
  // console.log('apis', cloneDeep(apis))
547
428
 
429
+ //搜尋過濾(name/keywords/url/levels/method)
430
+ let sterm = trim(vo.search).toLowerCase()
431
+ if (sterm !== '') {
432
+ apis = filter(apis, (v) => {
433
+ let hay = `${get(v, 'name', '')} ${get(v, 'keywords', '')} ${get(v, 'url', '')} ${get(v, 'levels', '')} ${get(v, 'method', '')}`.toLowerCase()
434
+ return hay.indexOf(sterm) >= 0
435
+ })
436
+ }
437
+
548
438
  //tr
549
439
  let tr = {}
550
440
  let kpTree = {}
@@ -570,8 +460,14 @@ export default {
570
460
  let tree = convertToTree(tr, { bindRoot: '全部' })
571
461
  // console.log('tree', cloneDeep(tree))
572
462
 
573
- //apiSelect, 預先選定api項目, 非tree選單active物件, 因id由convertToTree轉換提供, 故不能直接算得active選單物件id
574
- let apiSelect = get(apis, 0, null)
463
+ //apiSelect: 優先保留原選取(仍存在時), 否則取第一筆
464
+ let apiSelect = null
465
+ if (prevId !== '') {
466
+ apiSelect = find(apis, { id: prevId }) || null
467
+ }
468
+ if (!iseobj(apiSelect)) {
469
+ apiSelect = get(apis, 0, null)
470
+ }
575
471
  // console.log('apiSelect', cloneDeep(apiSelect))
576
472
 
577
473
  //save
@@ -596,6 +492,16 @@ export default {
596
492
  return method
597
493
  },
598
494
 
495
+ curlOf: function(item) {
496
+ let vo = this
497
+ let method = vo.getMethod(item) || 'GET'
498
+ let url = get(item, 'inputExample', '')
499
+ if (!url) {
500
+ url = get(item, 'url', '')
501
+ }
502
+ return 'curl -X ' + method + ' "' + url + '"'
503
+ },
504
+
599
505
  getMethodByKey: function(key) {
600
506
  let vo = this
601
507
  let item = vo.getItemByKey(key)
@@ -621,44 +527,102 @@ export default {
621
527
  return timemsTZ2day(t)
622
528
  },
623
529
 
624
- getOutputJson: function(item) {
625
- // let vo = this
626
- let j = get(item, 'outputExample', '')
627
- j = trim(j)
628
- return j
629
- },
630
-
631
- getOutputJsonObj: function(item) {
632
- // let vo = this
633
- let j = this.getOutputJson(item)
634
- // console.log(j)
635
- let o = j2o(j)
636
- return o
530
+ jsonHighlight: function(str) {
531
+ let s = (str === null || str === undefined) ? '' : String(str)
532
+ // escape HTML
533
+ s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
534
+ // key: "xxx":
535
+ s = s.replace(/("(?:[^"\\]|\\.)*")(\s*:)/g, '<span class="k">$1</span>$2')
536
+ // string value
537
+ s = s.replace(/:(\s*)("(?:[^"\\]|\\.)*")/g, ':$1<span class="s">$2</span>')
538
+ // number
539
+ s = s.replace(/([:\[,\s])(-?\d+\.?\d*)/g, '$1<span class="n">$2</span>')
540
+ // bool/null
541
+ s = s.replace(/\b(true|false|null)\b/g, '<span class="b">$1</span>')
542
+ return s
637
543
  },
638
544
 
639
545
  funActive: function(msg) {
640
546
  let vo = this
641
- let b = !Array.isArray(msg.item.children) //children非陣列代表沒有所屬節點
547
+ let b = !Array.isArray(msg.item.children) //children非陣列代表沒有所屬節點(葉節點)
642
548
  if (b) {
643
- if (!iseobj(vo.apiActive)) { //偵測若沒有apiActive才初始化預先選擇之api項目, 非api完整資訊
644
- let id = msg.item.id
645
- vo.apiActive = { id }
646
- // console.log('!iseobj(vo.apiActive)', vo.apiActive)
549
+ //渲染時用穩定的 api id(=節點 text) 對應目前選取項, apiActive 刷新成該葉節點「當前」的 id;
550
+ //避免存檔後 tree 重建、convertToTree 重新產生節點 id, 使 apiActive 變舊而高亮(含左側 accent bar)失效
551
+ let itemText = get(msg, 'item.text', '')
552
+ let itemId = get(msg, 'item.id', '')
553
+ if (iseobj(vo.apiSelect) && itemText === vo.apiSelect.id) {
554
+ if (get(vo.apiActive, 'id') !== itemId) {
555
+ vo.apiActive = { id: itemId }
556
+ }
557
+ }
558
+ else if (!iseobj(vo.apiActive)) { //無選取時初始化第一個葉節點為 active
559
+ vo.apiActive = { id: itemId }
647
560
  }
648
561
  }
649
562
  return b
650
563
  },
651
564
 
652
- changeOptputMenuItemSelect: function(msg) {
653
- // console.log('changeOptputMenuItemSelect', msg)
565
+ ckItem: function(item, node) {
566
+ // console.log('ckItem', item)
567
+ let vo = this
568
+ vo.apiSelect = cloneDeep(item)
569
+ if (iseobj(node)) { //點擊即更新 active 節點 id, 使高亮(含左側 bar)立即移到所點項目
570
+ vo.apiActive = { id: node.id }
571
+ }
572
+ vo.newMode = false //點選既有API即離開新增狀態
573
+ },
574
+
575
+ onChangeMode: function(msg) {
654
576
  let vo = this
655
- vo.optputMenuItemSelectId = msg.id
577
+ vo.mode = msg.id
578
+ //切到非編輯模式時取消新增狀態
579
+ if (vo.newMode && msg.id !== 'edit') {
580
+ vo.newMode = false
581
+ }
656
582
  },
657
583
 
658
- ckItem: function(item) {
659
- // console.log('ckItem', item)
584
+ onClickMainMenu: function(m) {
660
585
  let vo = this
661
- vo.apiSelect = cloneDeep(item)
586
+ vo.section = m.id
587
+ },
588
+
589
+ onClickAddApi: function() {
590
+ let vo = this
591
+ //種子空白API(預設值, 送出時後端funNew會配id/時間)
592
+ vo.blankApi = {
593
+ name: '',
594
+ description: '',
595
+ url: '',
596
+ method: 'get',
597
+ version: 'v1',
598
+ group: '',
599
+ levels: '',
600
+ keywords: '',
601
+ state: 'ok',
602
+ authType: 'none',
603
+ contentType: 'application/json',
604
+ }
605
+ vo.newMode = true
606
+ vo.mode = 'edit'
607
+ },
608
+
609
+ onEditSaved: function() {
610
+ let vo = this
611
+ vo.newMode = false
612
+ vo.mode = 'docs'
613
+ },
614
+
615
+ onEditDeleted: function() {
616
+ let vo = this
617
+ vo.newMode = false
618
+ vo.apiSelect = null
619
+ vo.mode = 'docs'
620
+ },
621
+
622
+ onEditCancel: function() {
623
+ let vo = this
624
+ vo.newMode = false
625
+ vo.mode = 'docs'
662
626
  },
663
627
 
664
628
  }
@@ -666,23 +630,34 @@ export default {
666
630
  </script>
667
631
 
668
632
  <style scoped>
669
- .bk {
670
- display:inline-block;
671
- width:200px;
672
- padding:0px 10px 10px 0px;
673
- vertical-align:top;
633
+ .w-mm-btn {
634
+ width: 100%;
635
+ box-sizing: border-box;
636
+ padding: 10px 4px;
637
+ border: none;
638
+ border-left: 3px solid transparent;
639
+ background: transparent;
640
+ color: var(--c-2);
641
+ cursor: pointer;
642
+ display: flex;
643
+ flex-direction: column;
644
+ align-items: center;
645
+ gap: 4px;
646
+ transition: background .15s, color .15s, border-color .15s;
647
+ }
648
+ .w-mm-btn:hover {
649
+ background: rgba(0, 153, 255, 0.06);
650
+ color: var(--c-1);
674
651
  }
675
- .bk-title {
676
- padding:3px;
677
- font-size:0.8rem;
678
- color:#888;
652
+ .w-mm-btn.on {
653
+ color: var(--accent);
654
+ border-left-color: var(--accent);
655
+ background: rgba(0, 153, 255, 0.08);
679
656
  }
680
- .bk-item {
681
- padding:3px 5px;
682
- font-size:0.9rem;
683
- line-height:0.9rem;
684
- border-radius:5px;
685
- border:1px solid #ddd;
686
- background:#fff;
657
+ .w-mm-label {
658
+ font-size: 11px;
659
+ line-height: 1.2;
660
+ text-align: center;
661
+ word-break: break-word;
687
662
  }
688
663
  </style>