w-web-api 1.0.82 → 1.0.84

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.
@@ -83,7 +83,7 @@
83
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;`"
84
84
  v-if="props.data.type!=='node'"
85
85
  >
86
- {{props.data.key}}
86
+ {{treeRowText(props.data.key)}}
87
87
  </div>
88
88
 
89
89
  <div
@@ -194,7 +194,7 @@
194
194
  </div>
195
195
  <div class="card">
196
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>
197
+ <pre class="code" v-html="$s.jsonHighlight($ui.gv(apiSelect,'outputExample'))"></pre>
198
198
  </div>
199
199
  </div>
200
200
  </div>
@@ -286,7 +286,9 @@ import filter from 'lodash-es/filter.js'
286
286
  import cloneDeep from 'lodash-es/cloneDeep.js'
287
287
  import isobj from 'wsemi/src/isobj.mjs'
288
288
  import iseobj from 'wsemi/src/iseobj.mjs'
289
+ import dtmapping from 'wsemi/src/dtmapping.mjs'
289
290
  import convertToTree from 'wsemi/src/convertToTree.mjs'
291
+ import ds from '../schema/index.mjs'
290
292
  import { mdiArrowLeft, mdiArrowRight, mdiCloudBraces, mdiChartBar } from '@mdi/js'
291
293
  import WButtonCircle from 'w-component-vue/src/components/WButtonCircle.vue'
292
294
  import WDrawer from 'w-component-vue/src/components/WDrawer.vue'
@@ -297,6 +299,11 @@ import LayoutContentTest from './LayoutContentTest.vue'
297
299
  import LayoutContentStats from './LayoutContentStats.vue'
298
300
 
299
301
 
302
+ //樹根節點之哨符 key(非顯示文字):顯示文字於 template 以 $t('treeRootAll') 依當前語系取得。
303
+ //用哨符而非中文字面值,一來使語系可即時切換,二來避免與真實 group 名稱(種子資料即有名為「全部」者)相撞。
304
+ let KEY_TREE_ROOT = '__treeRootAll__'
305
+
306
+
300
307
  export default {
301
308
  components: {
302
309
  WButtonCircle,
@@ -449,7 +456,10 @@ export default {
449
456
  // console.log('kpTree', cloneDeep(kpTree))
450
457
 
451
458
  //convertToTree, 由預處理tree物件轉成tree物件
452
- let tree = convertToTree(tr, { bindRoot: '全部' })
459
+ //bindRoot 用不會與真實資料相撞之哨符:樹根是 UI 產生的標籤(非資料),須隨語系切換即時改變,
460
+ //故不可在此把譯文烤進樹資料(語系為就地切換、不重載頁面,烤進去會停在舊語系直到樹重建)。
461
+ //譯文於 template 以 $t(KEY_TREE_ROOT_TEXT) 呈現。附帶修正:原值 '全部' 會與同名之 group 資料相撞。
462
+ let tree = convertToTree(tr, { bindRoot: KEY_TREE_ROOT })
453
463
  // console.log('tree', cloneDeep(tree))
454
464
 
455
465
  //apiSelect: 優先保留原選取(仍存在時), 否則取第一筆
@@ -486,7 +496,8 @@ export default {
486
496
 
487
497
  curlOf: function(item) {
488
498
  let vo = this
489
- let method = vo.getMethod(item) || 'GET'
499
+ //方法代號 HTTP 標準動詞:cURL 亦須顯示 DELETE 而非 DEL(複製貼上即可執行),與送出路徑同一述語
500
+ let method = vo.$s.methodToHttpVerb(vo.getMethod(item))
490
501
  let url = get(item, 'inputExample', '')
491
502
  if (!url) {
492
503
  url = get(item, 'url', '')
@@ -501,19 +512,11 @@ export default {
501
512
  return method
502
513
  },
503
514
 
504
- jsonHighlight: function(str) {
505
- let s = (str === null || str === undefined) ? '' : String(str)
506
- // escape HTML
507
- s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
508
- // key: "xxx":
509
- s = s.replace(/("(?:[^"\\]|\\.)*")(\s*:)/g, '<span class="k">$1</span>$2')
510
- // string value
511
- s = s.replace(/:(\s*)("(?:[^"\\]|\\.)*")/g, ':$1<span class="s">$2</span>')
512
- // number
513
- s = s.replace(/([:\[,\s])(-?\d+\.?\d*)/g, '$1<span class="n">$2</span>')
514
- // bool/null
515
- s = s.replace(/\b(true|false|null)\b/g, '<span class="b">$1</span>')
516
- return s
515
+ //樹之群組列顯示文字:樹根為 UI 標籤(哨符)→ 依當前語系取譯文;其餘為資料(levels 群組名)→ 原樣顯示。
516
+ //於 template 取值而非在 genTree 烤進資料,語系就地切換時才會即時更新。
517
+ treeRowText: function(key) {
518
+ let vo = this
519
+ return (key === KEY_TREE_ROOT) ? vo.$t('treeRootAll') : key
517
520
  },
518
521
 
519
522
  funActive: function(msg) {
@@ -562,8 +565,10 @@ export default {
562
565
 
563
566
  onClickAddApi: function() {
564
567
  let vo = this
565
- //種子空白API(預設值, 送出時後端funNew會配id/時間)
566
- vo.blankApi = {
568
+ //種子空白API(預設值, 送出時後端funNew會配id/時間)
569
+ //以 schema 全部欄位補齊(缺者為 ''):編輯表單以 cloneDeep(item) 為 form,Vue 2 對初始不存在之屬性無法追蹤,
570
+ //只列預設值時 tokens/testBaseUrl/authConfigJson/default*Json 等欄位於新增模式為非響應式(ADR-031)
571
+ vo.blankApi = dtmapping({
567
572
  name: '',
568
573
  description: '',
569
574
  url: '',
@@ -575,7 +580,7 @@ export default {
575
580
  state: 'ok',
576
581
  authType: 'none',
577
582
  contentType: 'application/json',
578
- }
583
+ }, ds.apis.keys, '')
579
584
  vo.newMode = true
580
585
  vo.mode = 'edit'
581
586
  },
@@ -263,7 +263,9 @@ export default {
263
263
  props: {
264
264
  item: {
265
265
  type: Object,
266
- default: function() { return {} },
266
+ default: function() {
267
+ return {}
268
+ },
267
269
  },
268
270
  height: {
269
271
  type: Number,
@@ -289,7 +291,7 @@ export default {
289
291
  //故以旗標取代 promiseUnlock:同步第 2 次點擊直接 return;非同步期間另有頁面層 updateLoading overlay,後端層有 pmKeyMutex
290
292
  submitting: false,
291
293
 
292
- methodItems: ['get', 'post', 'put', 'del'],
294
+ methodItems: this.$s.METHODS, //單一來源 mShare.METHODS(與測試分頁共用)
293
295
  authTypeItems: ['none', 'bearer', 'apikey', 'basic'],
294
296
 
295
297
  }
@@ -349,8 +351,13 @@ export default {
349
351
  }
350
352
  vo.submitting = true
351
353
  vo.submitSave()
352
- .catch(function(err) { console.log('catch', err); vo.$alert(vo.$t('anUnexpectedErrorOccurred'), { type: 'error' }) })
353
- .finally(function() { vo.submitting = false; vo.$ui.updateLoading(false) })
354
+ .catch(function(err) {
355
+ //非預期例外:先關 overlay、再以 showCheckYes modal 通知(CLAUDE.md 失敗通知政策;不用自動消失之 $alert toast,ADR-005)
356
+ console.log('catch', err); vo.$ui.updateLoading(false); vo.$dg.showCheckYes(vo.$t('anUnexpectedErrorOccurred'), { type: 'error' })
357
+ })
358
+ .finally(function() {
359
+ vo.submitting = false; vo.$ui.updateLoading(false)
360
+ })
354
361
  },
355
362
 
356
363
  submitSave: function() {
@@ -379,7 +386,12 @@ export default {
379
386
  let fv = vo.form[fk]
380
387
  if (isestr(fv)) {
381
388
  let jsonOk = true
382
- try { JSON.parse(fv) } catch (e) { jsonOk = false }
389
+ try {
390
+ JSON.parse(fv)
391
+ }
392
+ catch (e) {
393
+ jsonOk = false
394
+ }
383
395
  if (!jsonOk) {
384
396
  vo.errJsonField = fk
385
397
  vo.errJson = fk + ': ' + vo.$t('valInvalidJson')
@@ -394,8 +406,12 @@ export default {
394
406
  // 4) 執行 async,各自 catch + 旗標短路
395
407
  let okSave = false
396
408
  await vo.$fapi.saveApi(cloneDeep(vo.form))
397
- .then(function() { okSave = true })
398
- .catch(function(err) { console.log('saveApi', err); vo.errSave = vo.$transErr(err) }) //err 為後端 err-key,依 lang 反查顯示
409
+ .then(function() {
410
+ okSave = true
411
+ })
412
+ .catch(function(err) {
413
+ console.log('saveApi', err); vo.errSave = vo.$transErr(err)
414
+ }) //err 為後端 err-key,依 lang 反查顯示
399
415
  if (!okSave) return
400
416
 
401
417
  // 5) 全成功
@@ -413,8 +429,12 @@ export default {
413
429
  onClickDelete: function() {
414
430
  let vo = this
415
431
  vo.$dg.showCheckYesNo(vo.$t('confirmDeleteApi'))
416
- .then(function() { vo.doDelete() })
417
- .catch(function(err) { if (err === 'close') return; console.log('showCheckYesNo', err) })
432
+ .then(function() {
433
+ vo.doDelete()
434
+ })
435
+ .catch(function(err) {
436
+ if (err === 'close') return; console.log('showCheckYesNo', err)
437
+ })
418
438
  },
419
439
 
420
440
  doDelete: function() {
@@ -436,8 +456,12 @@ export default {
436
456
  // 4) 執行 async
437
457
  let okDel = false
438
458
  await vo.$fapi.deleteApi(vo.form.id)
439
- .then(function() { okDel = true })
440
- .catch(function(err) { console.log('deleteApi', err); vo.errSave = vo.$transErr(err) }) //err 為後端 err-key,依 lang 反查顯示
459
+ .then(function() {
460
+ okDel = true
461
+ })
462
+ .catch(function(err) {
463
+ console.log('deleteApi', err); vo.errSave = vo.$transErr(err)
464
+ }) //err 為後端 err-key,依 lang 反查顯示
441
465
  if (!okDel) return
442
466
 
443
467
  // 5) 全成功
@@ -447,8 +471,13 @@ export default {
447
471
 
448
472
  }
449
473
  core()
450
- .catch(function(err) { console.log('catch', err); vo.$alert(vo.$t('anUnexpectedErrorOccurred'), { type: 'error' }) })
451
- .finally(function() { vo.submitting = false; vo.$ui.updateLoading(false) })
474
+ .catch(function(err) {
475
+ //非預期例外:先關 overlay、再以 showCheckYes modal 通知(CLAUDE.md 失敗通知政策;不用自動消失之 $alert toast,ADR-005)
476
+ console.log('catch', err); vo.$ui.updateLoading(false); vo.$dg.showCheckYes(vo.$t('anUnexpectedErrorOccurred'), { type: 'error' })
477
+ })
478
+ .finally(function() {
479
+ vo.submitting = false; vo.$ui.updateLoading(false)
480
+ })
452
481
  },
453
482
 
454
483
  onClickCancel: function() {
@@ -15,12 +15,50 @@
15
15
  <div class="ctrl-row">
16
16
  <span class="ctrl-label">{{$t('timeRange')}}</span>
17
17
  <div class="ctrl-field">
18
- <select id="timeGroupSel" v-model="timeGroup" class="ctrl-select">
19
- <option value="1hr">{{$t('selectItem1hr')}}</option>
20
- <option value="4hr">{{$t('selectItem4hr')}}</option>
21
- <option value="8hr">{{$t('selectItem8hr')}}</option>
22
- <option value="1day">{{$t('selectItem1day')}}</option>
23
- </select>
18
+ <!-- 自製下拉(WTextSelect)取代原生 <select>:原生下拉之彈出清單選中/hover 色由 OS 決定、CSS 不可控,與本頁主題不一致(全域 §10.6-3)。
19
+ 色票沿用本頁既有 token 實值(scalar-ui.css :root;WShellEllipse 之顏色經 convertColor 轉 hex、不認 var()):
20
+ 邊線 --border #e6e8ec、底 --bg-1 #ffffff、文字 --c-1 #1b1b1b、焦點邊線 --accent #0099ff、項目 hover 底 --bg-3 #eceef1、
21
+ 展開圖標 --c-3 #9aa1ac、圓角 --radius 8;字級 0.875rem 同原 .ctrl-select;展開圖標隨開闔旋轉(狀態聯動)。
22
+ id 供 e2e 定位觸發區;寬度依當前語系最長選項文字計算(computed timeGroupSelWidth,同原生 select 依最長選項定寬)。
23
+ 清單對齊(全域 §10.6-6):WPopup 錨在觸發區之內容元素(外框左緣 + 邊框 1 + 內距 8 = 內容左緣),故 placementDistX=-9 把清單左緣拉回外框左緣、
24
+ 清單寬固定為觸發區寬(關 autoFit、min/maxWidth 同 timeGroupSelWidth),項目左內距 9(= 邊框 1 + 內距 8)使項目文字左緣與觸發區文字左緣貼齊(實測 191/191)。 -->
25
+ <WTextSelect
26
+ id="timeGroupSel"
27
+ :style="`width:${timeGroupSelWidth}px;`"
28
+ :items="timeGroupItems"
29
+ :value="timeGroup"
30
+ :shadow="false"
31
+ :borderRadius="8"
32
+ :paddingStyle="{ v: 4, h: 8 }"
33
+ :backgroundColor="'#ffffff'"
34
+ :backgroundColorHover="'#ffffff'"
35
+ :backgroundColorFocus="'#ffffff'"
36
+ :borderColor="'#e6e8ec'"
37
+ :borderColorHover="'#e6e8ec'"
38
+ :borderColorFocus="'#0099ff'"
39
+ :textColor="'#1b1b1b'"
40
+ :textFontSize="'0.875rem'"
41
+ :itemTextFontSize="'0.875rem'"
42
+ :itemTextColor="'#1b1b1b'"
43
+ :itemTextColorHover="'#1b1b1b'"
44
+ :itemBackgroundColor="'#ffffff'"
45
+ :itemBackgroundColorHover="'#eceef1'"
46
+ :itemPaddingStyle="{ v: 6, h: 9 }"
47
+ :placementDistX="-9"
48
+ :autoFitMinWidth="false"
49
+ :autoFitMaxWidth="false"
50
+ :minWidth="timeGroupSelWidth"
51
+ :maxWidth="timeGroupSelWidth"
52
+ :expansionIconColor="'#9aa1ac'"
53
+ @input="onInputTimeGroup"
54
+ >
55
+ <template v-slot:select="props">
56
+ {{$t('selectItem' + props.item)}}
57
+ </template>
58
+ <template v-slot:item="props">
59
+ {{$t('selectItem' + props.item)}}
60
+ </template>
61
+ </WTextSelect>
24
62
  </div>
25
63
  </div>
26
64
 
@@ -107,8 +145,13 @@ import sumBy from 'lodash-es/sumBy.js'
107
145
  import reduce from 'lodash-es/reduce.js'
108
146
  import size from 'lodash-es/size.js'
109
147
  import isearr from 'wsemi/src/isearr.mjs'
148
+ import isestr from 'wsemi/src/isestr.mjs'
110
149
  import WEchartsVue from 'w-echarts-vue/src/components/WEchartsVue.vue'
111
150
  import WIconLoading from 'w-component-vue/src/components/WIconLoading.vue'
151
+ import WTextSelect from 'w-component-vue/src/components/WTextSelect.vue'
152
+
153
+ //時間範圍下拉觸發區之結構寬度(不含文字):左右內距 8+8、邊框 1+1、展開圖標 18、對稱呼吸空間 2(全域 §10.6-5;量測見 spec/流程_統計資訊.md)
154
+ let SEL_STRUCT_WIDTH = 8 + 8 + 1 + 1 + 18 + 2
112
155
 
113
156
  //固定色票(依事件在 allEvents 之索引取色,使同一事件顏色穩定、圖表(含圖例)與統計表一致)
114
157
  let COLORS = [
@@ -121,6 +164,7 @@ export default {
121
164
  components: {
122
165
  WEchartsVue,
123
166
  WIconLoading,
167
+ WTextSelect,
124
168
  },
125
169
  props: {
126
170
  height: {
@@ -132,6 +176,8 @@ export default {
132
176
  return {
133
177
  freq: [],
134
178
  timeGroup: '1hr',
179
+ timeGroupItems: ['1hr', '4hr', '8hr', '1day'], //下拉項目為代號;顯示文字於 template 經 $t('selectItem' + 代號) 取當前語系(就地切換語系即時更新)
180
+ fontSel: '', //觸發區文字之實際字型(mounted 自 computed style 取得),供 timeGroupSelWidth 量文字實寬
135
181
  loading: false,
136
182
  errMsg: '',
137
183
  hUpper: 0, //標題列+控制區實佔高度(ResizeObserver 量測,供 chartHeight 計算;語系導致控制列換行時會變化)
@@ -154,6 +200,11 @@ export default {
154
200
  vo.ro = ro
155
201
  vo.updateUpperHeight()
156
202
 
203
+ //觸發區字型:字級 0.875rem(同原 .ctrl-select)× 根字級,字族/字重承襲本面板
204
+ let cs = getComputedStyle(vo.$el)
205
+ let rootPx = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16
206
+ vo.fontSel = `${cs.fontWeight} ${rootPx * 0.875}px ${cs.fontFamily}`
207
+
157
208
  vo.load()
158
209
  },
159
210
  beforeDestroy: function() {
@@ -165,6 +216,21 @@ export default {
165
216
  },
166
217
  computed: {
167
218
 
219
+ //時間範圍下拉觸發區寬度(全域 §10.6-5:固定尺寸須計算):當前語系「最長選項文字」實寬(canvas measureText,字型同觸發區)
220
+ //+ 結構寬度 SEL_STRUCT_WIDTH;語系切換即重算($t 為響應式)。
221
+ //why 不寫死:原生 <select> 依最長選項自動定寬(實測 eng 85px/cht 74px),自製下拉須重現,否則另一語系右側大片留白。
222
+ timeGroupSelWidth: function() {
223
+ let vo = this
224
+ let texts = vo.timeGroupItems.map((k) => vo.$t('selectItem' + k))
225
+ let wText = 0
226
+ if (isestr(vo.fontSel)) {
227
+ let c = document.createElement('canvas').getContext('2d')
228
+ c.font = vo.fontSel
229
+ wText = Math.max(...texts.map((t) => c.measureText(t).width))
230
+ }
231
+ return Math.ceil(wText + SEL_STRUCT_WIDTH)
232
+ },
233
+
168
234
  chartHeight: function() {
169
235
  let vo = this
170
236
  //panel 上下 padding 40 + 上方實佔(hUpper, 量測) + 表格預留 98(既有設計值:舊公式 height−360
@@ -274,11 +340,13 @@ export default {
274
340
  grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
275
341
  xAxis: [{
276
342
  type: 'category',
277
- data: map(data, function(item) { return ot(item.time).format(formatX) }),
343
+ data: map(data, function(item) {
344
+ return ot(item.time).format(formatX)
345
+ }),
278
346
  axisTick: { alignWithLabel: true },
279
347
  }],
280
348
  yAxis: [{ type: 'value' }],
281
- series: series,
349
+ series,
282
350
  }
283
351
  },
284
352
 
@@ -330,6 +398,11 @@ export default {
330
398
  vo.hUpper = h
331
399
  },
332
400
 
401
+ onInputTimeGroup: function(val) {
402
+ let vo = this
403
+ vo.timeGroup = val //→ resampledData 重採樣 → chartOption 重繪
404
+ },
405
+
333
406
  colorOf: function(ev) {
334
407
  let vo = this
335
408
  let idx = vo.allEvents.indexOf(ev)
@@ -388,20 +461,6 @@ export default {
388
461
  min-width: 0;
389
462
  }
390
463
 
391
- .ctrl-select {
392
- border: 1px solid var(--border);
393
- border-radius: var(--radius);
394
- background: var(--bg-1);
395
- color: var(--c-1);
396
- font-size: 0.875rem;
397
- padding: 4px 8px;
398
- outline: none;
399
- cursor: pointer;
400
- }
401
-
402
- .ctrl-select:focus {
403
- border-color: var(--accent);
404
- }
405
464
 
406
465
  .evt-dot {
407
466
  display: inline-block;