w-web-perm 1.0.84 → 1.0.86

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.
@@ -140,32 +140,16 @@
140
140
  :opt="opt"
141
141
  >
142
142
  <template v-slot:cell-render="props">
143
- <template v-if="props.key==='grupsNames'">
144
- <span v-for="(item, idx) in props.row.grups" :key="idx" style="display:inline-block; line-height:20px; height:20px;">
145
- <div style="display:flex;">
146
- <div :style="'padding:0px 5px; border-top-left-radius:4px; border-bottom-left-radius:4px; ' + (item.enable === 'y' ? 'border-left:1px solid #ac2451; border-right:1px solid #9e2149; border-top:1px solid #ac2451; border-bottom:1px solid #ac2451; background:#be295a; color:#fff;' : 'border-left:1px solid #aaaaaa; border-right:1px solid #aaaaaa; border-top:1px solid #aaaaaa; border-bottom:1px solid #aaaaaa; background:#ffffff; color:#555;')">
147
- {{ item.mode }}
148
- </div>
149
- <div :style="'padding:0px 5px; border-top-right-radius:4px; border-bottom-right-radius:4px; ' + (item.enable === 'y' ? '_border-left:1px solid #ac2451; border-right:1px solid #ac2451; border-top:1px solid #ac2451; border-bottom:1px solid #ac2451; background:#d22f64; color:#fff;' : '_border-left:1px solid #aaaaaa; border-right:1px solid #aaaaaa; border-top:1px solid #aaaaaa; border-bottom:1px solid #aaaaaa; background:#eeeeee; color:#555;')">
150
- {{ item.name }}
151
- </div>
152
- </div>
153
- </span>
154
- </template>
155
- <WTextSelect
156
- v-else-if="props.key==='mode'"
157
- :style="`width:${modeSelectWidth}px;`"
158
- :items="modeItems"
159
- :value="props.row.mode"
143
+ <RelationChips
144
+ v-if="props.key==='grupsNames'"
145
+ :items="props.row.grups"
160
146
  :editable="isEditable"
161
- :paddingStyle="{v:0,h:10}"
162
- :shadow="false"
163
- :itemPaddingStyle="{v:8,h:10}"
164
- :placementDistX="-10"
147
+ :popupTitle="$t('belongGrupsNames')"
148
+ :tooltipAll="$t('chipsShowAll')"
165
149
  :labelContent="'modeSelect'"
166
- @input="(item)=>{showVeGrupBlngUsersToggleItemModeByName(props.row.name, item)}"
167
- ></WTextSelect>
168
- <input v-else-if="props.key==='enable'" type="checkbox" :checked="props.value === 'y'" @click="showVeGrupBlngUsersToggleItemEnableByName(props.row.name)" :disabled="!isEditable" />
150
+ @input="(item)=>{showVeGrupBlngUsersToggleItemModeByName(props.row.name, item, props.row.id)}"
151
+ ></RelationChips>
152
+ <input v-else-if="props.key==='enable'" type="checkbox" :checked="props.value === 'y'" @click="showVeGrupBlngUsersToggleItemEnableByName(props.row.name, props.row.id)" :disabled="!isEditable" />
169
153
  <span v-else>{{ props.value }}</span>
170
154
  </template>
171
155
  </WAggridVue>
@@ -207,7 +191,7 @@ import isestr from 'wsemi/src/isestr.mjs'
207
191
  import WDialog from 'w-component-vue/src/components/WDialog.vue'
208
192
  import WButtonCircle from 'w-component-vue/src/components/WButtonCircle.vue'
209
193
  import WAggridVue from 'w-aggrid-vue/src/components/WAggridVue.vue'
210
- import WTextSelect from 'w-component-vue/src/components/WTextSelect.vue'
194
+ import RelationChips from './RelationChips.vue'
211
195
 
212
196
 
213
197
  export default {
@@ -215,7 +199,7 @@ export default {
215
199
  WDialog,
216
200
  WButtonCircle,
217
201
  WAggridVue,
218
- WTextSelect,
202
+ RelationChips,
219
203
  },
220
204
  props: {
221
205
  },
@@ -244,10 +228,6 @@ export default {
244
228
  isEditable: false,
245
229
  isModified: false,
246
230
 
247
- //mode 欄之自製下拉(WTextSelect, 取代原生 select 以免彈出清單之 highlight 色由 OS 決定)
248
- modeItems: ['OR', 'AND'],
249
- modeSelectWidth: 72, //= 最長項 'AND' 文字實寬 + 左右內距 10×2 + 展開箭頭 18 + 邊框 1×2, 實測後定值(見 CLAUDE_experience.md)
250
-
251
231
  grup: null,
252
232
 
253
233
  items: [],
@@ -414,21 +394,19 @@ export default {
414
394
  let opt = null
415
395
  if (size(vo.items) > 0) {
416
396
 
417
- //ks
397
+ //ks, 本列自己可改的(是否使用)在左, 由其他權限群組帶來的(所屬權限群組名稱)在右; 合併模式併入 grupsNames 欄之本項 chip(RelationChips), 不再獨立成欄
418
398
  let ks = [
419
399
  'name',
420
- 'grupsNames',
421
- 'mode',
422
400
  'enable',
401
+ 'grupsNames',
423
402
  ]
424
403
  // console.log('ks', ks)
425
404
 
426
405
  //kpHead
427
406
  let kpHead = {
428
407
  'name': vo.$t('userName'),
429
- 'grupsNames': vo.$t('belongGrupsNames'),
430
- 'mode': vo.$t('operGrupMode'),
431
408
  'enable': vo.$t('operEnable'),
409
+ 'grupsNames': vo.$t('belongGrupsNames'),
432
410
  }
433
411
 
434
412
  //opt
@@ -442,12 +420,13 @@ export default {
442
420
  defHeadFilter: true,
443
421
  defCellAlignH: 'left',
444
422
  kpCellEditable: {
445
- 'name': vo.isEditable,
446
- 'mode': false,
423
+ //name 一律唯讀: 列固定為全部既有使用者(genItems 產生, 無新增/刪除列); doSave 只回寫 u.cgrups、
424
+ // 完全不讀列上之 name, 故就地改名會被靜默丟棄——有可編輯外觀卻無對應行為(全域 §2.1 第 6 條之半吊子)。
425
+ // 與 VeCgrups/VeCpemis 同類一併關閉(§2.1 第 4 條 對稱性)。
426
+ 'name': false,
447
427
  'enable': false,
448
428
  },
449
429
  kpHeadWidth: {
450
- 'mode': 100,
451
430
  'enable': 100,
452
431
  },
453
432
  // kpRowDrag: {
@@ -459,14 +438,14 @@ export default {
459
438
  kpHeadFilterType: {
460
439
  'name': 'text',
461
440
  'grupsNames': 'text',
462
- 'mode': 'text',
463
441
  'enable': 'text',
464
442
  },
465
443
  // kpHeadCheckBox: {
466
444
  // 'name': true,
467
445
  // },
468
- kpHeadFocusHighlight: { //本欄內容為唯讀徽章(無可聚焦元素), 焦點框徒增干擾故關閉; 與三個清單頁之按鈕欄同一慣例, 該處另受 w-aggrid-vue 2.0.87 之 :focus-within 修正(本欄不可聚焦, 不受影響)
446
+ kpHeadFocusHighlight: { //兩欄之儲存格內皆有自己的控制項(chip 之模式下拉、勾選框), 焦點框徒增干擾故關閉; 控制項取得焦點時之焦點框由 w-aggrid-vue 2.0.87 之 :focus-within 規則一併擋下
469
447
  'grupsNames': false,
448
+ 'enable': false,
470
449
  },
471
450
  rowsChange: (rs) => {
472
451
  // console.log('rowsChange', rs)
@@ -649,6 +628,7 @@ export default {
649
628
  // console.log(u.name, 'grupsNames', grupsNames)
650
629
 
651
630
  return {
631
+ id: u.id, //列識別: 使用者 name 允許重複(僅前端警告、後端不擋), 切換處理須以 id 找列, 不可以 name(否則同名者會改到第一位)
652
632
  name: u.name,
653
633
  grupsNames,
654
634
  mode,
@@ -752,8 +732,8 @@ export default {
752
732
 
753
733
  },
754
734
 
755
- showVeGrupBlngUsersToggleItemModeByName: function(name, mode) {
756
- // console.log('showVeGrupBlngUsersToggleItemModeByName', name, mode)
735
+ showVeGrupBlngUsersToggleItemModeByName: function(name, mode, id) {
736
+ // console.log('showVeGrupBlngUsersToggleItemModeByName', name, mode, id)
757
737
 
758
738
  let vo = this
759
739
 
@@ -766,11 +746,11 @@ export default {
766
746
  //rows
767
747
  let rows = get(vo, 'opt.rows', [])
768
748
 
769
- //find
749
+ //find, 有 id 以 id 找列(使用者 name 可重複, 以 name 會改到第一位同名者); 未給 id 之既有 $dg 呼叫端維持以 name 找
770
750
  let r = null
771
751
  let kr = null
772
752
  each(rows, (v, k) => {
773
- if (get(v, 'name', '') === name) {
753
+ if (isestr(id) ? get(v, 'id', '') === id : get(v, 'name', '') === name) {
774
754
  r = v
775
755
  kr = k
776
756
  return false //跳出
@@ -799,8 +779,8 @@ export default {
799
779
 
800
780
  },
801
781
 
802
- showVeGrupBlngUsersToggleItemEnableByName: function(name) {
803
- // console.log('showVeGrupBlngUsersToggleItemEnableByName', name)
782
+ showVeGrupBlngUsersToggleItemEnableByName: function(name, id) {
783
+ // console.log('showVeGrupBlngUsersToggleItemEnableByName', name, id)
804
784
 
805
785
  let vo = this
806
786
 
@@ -813,11 +793,11 @@ export default {
813
793
  //rows
814
794
  let rows = get(vo, 'opt.rows', [])
815
795
 
816
- //find
796
+ //find, 理由同 showVeGrupBlngUsersToggleItemModeByName: 有 id 以 id 找列
817
797
  let r = null
818
798
  let kr = null
819
799
  each(rows, (v, k) => {
820
- if (get(v, 'name', '') === name) {
800
+ if (isestr(id) ? get(v, 'id', '') === id : get(v, 'name', '') === name) {
821
801
  r = v
822
802
  kr = k
823
803
  return false //跳出
@@ -982,13 +962,14 @@ export default {
982
962
  await vo.$dg.showCheckYes(`${vo.$t('userSaveUsersFail')}: ${vo.$tErr(err)}`)
983
963
  })
984
964
  if (!okSave) {
985
- return
965
+ return false //回報失敗: 呼叫端 doSave 據此不關閉對話框, 保留未存變更供修正後重按儲存
986
966
  }
987
967
 
988
968
  //alert(showCheckYes 前關 loading,避免 modal 等待期間 loading 疊在底下)
989
969
  vo.$ui.updateLoading(false)
990
970
  await vo.$dg.showCheckYes(vo.$t('userSaveUsersSuccess'), { type: 'success' })
991
971
 
972
+ return true
992
973
  },
993
974
 
994
975
  clickBtns: function(msg) {
@@ -1089,8 +1070,11 @@ export default {
1089
1070
  // return
1090
1071
  // }
1091
1072
 
1092
- //saveUsers
1093
- await vo.saveUsers(usersNew)
1073
+ //saveUsers, 失敗時回 false: 失敗提示已由 saveUsers 顯示, 此處不關閉對話框(保留未存變更供重按儲存)
1074
+ let okSave = await vo.saveUsers(usersNew)
1075
+ if (!okSave) {
1076
+ return false
1077
+ }
1094
1078
 
1095
1079
  }
1096
1080
 
@@ -1098,6 +1082,11 @@ export default {
1098
1082
  core()
1099
1083
  .then((rows) => {
1100
1084
 
1085
+ //存檔失敗: 不 resolve、不關窗(規格「若寫入失敗…對話框不關閉,可修正後重按儲存」; 舊版於此無條件關窗, 2026-09-17 審計查出)
1086
+ if (rows === false) {
1087
+ return
1088
+ }
1089
+
1101
1090
  //resolve
1102
1091
  vo.pm.resolve(rows)
1103
1092
 
@@ -140,31 +140,15 @@
140
140
  :opt="opt"
141
141
  >
142
142
  <template v-slot:cell-render="props">
143
- <template v-if="props.key==='pemisNames'">
144
- <span v-for="(item, idx) in props.row.pemis" :key="idx" style="display:inline-block; line-height:20px; height:20px;">
145
- <div style="display:flex;">
146
- <div :style="'padding:0px 5px; border-top-left-radius:4px; border-bottom-left-radius:4px; ' + (item.enable === 'y' ? 'border-left:1px solid #ac2451; border-right:1px solid #9e2149; border-top:1px solid #ac2451; border-bottom:1px solid #ac2451; background:#be295a; color:#fff;' : 'border-left:1px solid #aaaaaa; border-right:1px solid #aaaaaa; border-top:1px solid #aaaaaa; border-bottom:1px solid #aaaaaa; background:#ffffff; color:#555;')">
147
- {{ item.mode }}
148
- </div>
149
- <div :style="'padding:0px 5px; border-top-right-radius:4px; border-bottom-right-radius:4px; ' + (item.enable === 'y' ? '_border-left:1px solid #ac2451; border-right:1px solid #ac2451; border-top:1px solid #ac2451; border-bottom:1px solid #ac2451; background:#d22f64; color:#fff;' : '_border-left:1px solid #aaaaaa; border-right:1px solid #aaaaaa; border-top:1px solid #aaaaaa; border-bottom:1px solid #aaaaaa; background:#eeeeee; color:#555;')">
150
- {{ item.name }}
151
- </div>
152
- </div>
153
- </span>
154
- </template>
155
- <WTextSelect
156
- v-else-if="props.key==='mode'"
157
- :style="`width:${modeSelectWidth}px;`"
158
- :items="modeItems"
159
- :value="props.row.mode"
143
+ <RelationChips
144
+ v-if="props.key==='pemisNames'"
145
+ :items="props.row.pemis"
160
146
  :editable="isEditable"
161
- :paddingStyle="{v:0,h:10}"
162
- :shadow="false"
163
- :itemPaddingStyle="{v:8,h:10}"
164
- :placementDistX="-10"
147
+ :popupTitle="$t('belongPemisNames')"
148
+ :tooltipAll="$t('chipsShowAll')"
165
149
  :labelContent="'modeSelect'"
166
150
  @input="(item)=>{showVePemiBlngGrupsToggleItemModeByName(props.row.name, item)}"
167
- ></WTextSelect>
151
+ ></RelationChips>
168
152
  <input v-else-if="props.key==='enable'" type="checkbox" :checked="props.value === 'y'" @click="showVePemiBlngGrupsToggleItemEnableByName(props.row.name)" :disabled="!isEditable" />
169
153
  <span v-else>{{ props.value }}</span>
170
154
  </template>
@@ -207,7 +191,7 @@ import isestr from 'wsemi/src/isestr.mjs'
207
191
  import WDialog from 'w-component-vue/src/components/WDialog.vue'
208
192
  import WButtonCircle from 'w-component-vue/src/components/WButtonCircle.vue'
209
193
  import WAggridVue from 'w-aggrid-vue/src/components/WAggridVue.vue'
210
- import WTextSelect from 'w-component-vue/src/components/WTextSelect.vue'
194
+ import RelationChips from './RelationChips.vue'
211
195
 
212
196
 
213
197
  export default {
@@ -215,7 +199,7 @@ export default {
215
199
  WDialog,
216
200
  WButtonCircle,
217
201
  WAggridVue,
218
- WTextSelect,
202
+ RelationChips,
219
203
  },
220
204
  props: {
221
205
  },
@@ -244,10 +228,6 @@ export default {
244
228
  isEditable: false,
245
229
  isModified: false,
246
230
 
247
- //mode 欄之自製下拉(WTextSelect, 取代原生 select 以免彈出清單之 highlight 色由 OS 決定)
248
- modeItems: ['OR', 'AND'],
249
- modeSelectWidth: 72, //= 最長項 'AND' 文字實寬 + 左右內距 10×2 + 展開箭頭 18 + 邊框 1×2, 實測後定值(見 CLAUDE_experience.md)
250
-
251
231
  pemi: null,
252
232
 
253
233
  items: [],
@@ -414,21 +394,19 @@ export default {
414
394
  let opt = null
415
395
  if (size(vo.items) > 0) {
416
396
 
417
- //ks
397
+ //ks, 本列自己可改的(是否使用)在左, 由其他權限帶來的(所屬權限名稱)在右; 合併模式併入 pemisNames 欄之本項 chip(RelationChips), 不再獨立成欄
418
398
  let ks = [
419
399
  'name',
420
- 'pemisNames',
421
- 'mode',
422
400
  'enable',
401
+ 'pemisNames',
423
402
  ]
424
403
  // console.log('ks', ks)
425
404
 
426
405
  //kpHead
427
406
  let kpHead = {
428
407
  'name': vo.$t('grupName'),
429
- 'pemisNames': vo.$t('belongPemisNames'),
430
- 'mode': vo.$t('operPemiMode'),
431
408
  'enable': vo.$t('operEnable'),
409
+ 'pemisNames': vo.$t('belongPemisNames'),
432
410
  }
433
411
 
434
412
  //opt
@@ -442,12 +420,11 @@ export default {
442
420
  defHeadFilter: true,
443
421
  defCellAlignH: 'left',
444
422
  kpCellEditable: {
445
- 'name': vo.isEditable,
446
- 'mode': false,
423
+ //name 一律唯讀: 理由同 VeGrupBlngUsers——doSave 只回寫 cpemis、不讀列上之 name, 改名會被靜默丟棄。
424
+ 'name': false,
447
425
  'enable': false,
448
426
  },
449
427
  kpHeadWidth: {
450
- 'mode': 100,
451
428
  'enable': 100,
452
429
  },
453
430
  // kpRowDrag: {
@@ -459,14 +436,14 @@ export default {
459
436
  kpHeadFilterType: {
460
437
  'name': 'text',
461
438
  'pemisNames': 'text',
462
- 'mode': 'text',
463
439
  'enable': 'text',
464
440
  },
465
441
  // kpHeadCheckBox: {
466
442
  // 'name': true,
467
443
  // },
468
- kpHeadFocusHighlight: { //本欄內容為唯讀徽章(無可聚焦元素), 焦點框徒增干擾故關閉; 與三個清單頁之按鈕欄同一慣例, 該處另受 w-aggrid-vue 2.0.87 之 :focus-within 修正(本欄不可聚焦, 不受影響)
444
+ kpHeadFocusHighlight: { //兩欄之儲存格內皆有自己的控制項(chip 之模式下拉、勾選框), 焦點框徒增干擾故關閉; 控制項取得焦點時之焦點框由 w-aggrid-vue 2.0.87 之 :focus-within 規則一併擋下
469
445
  'pemisNames': false,
446
+ 'enable': false,
470
447
  },
471
448
  rowsChange: (rs) => {
472
449
  // console.log('rowsChange', rs)
@@ -982,13 +959,14 @@ export default {
982
959
  await vo.$dg.showCheckYes(`${vo.$t('grupSaveGrupsFail')}: ${vo.$tErr(err)}`)
983
960
  })
984
961
  if (!okSave) {
985
- return
962
+ return false //回報失敗: 呼叫端 doSave 據此不關閉對話框, 保留未存變更供修正後重按儲存
986
963
  }
987
964
 
988
965
  //alert(showCheckYes 前關 loading,避免 modal 等待期間 loading 疊在底下)
989
966
  vo.$ui.updateLoading(false)
990
967
  await vo.$dg.showCheckYes(vo.$t('grupSaveGrupsSuccess'), { type: 'success' })
991
968
 
969
+ return true
992
970
  },
993
971
 
994
972
  clickBtns: function(msg) {
@@ -1089,8 +1067,11 @@ export default {
1089
1067
  // return
1090
1068
  // }
1091
1069
 
1092
- //saveGrups
1093
- await vo.saveGrups(grupsNew)
1070
+ //saveGrups, 失敗時回 false: 失敗提示已由 saveGrups 顯示, 此處不關閉對話框(保留未存變更供重按儲存)
1071
+ let okSave = await vo.saveGrups(grupsNew)
1072
+ if (!okSave) {
1073
+ return false
1074
+ }
1094
1075
 
1095
1076
  }
1096
1077
 
@@ -1098,6 +1079,11 @@ export default {
1098
1079
  core()
1099
1080
  .then((rows) => {
1100
1081
 
1082
+ //存檔失敗: 不 resolve、不關窗(規格「若寫入失敗…對話框不關閉,可修正後重按儲存」; 舊版於此無條件關窗, 2026-09-17 審計查出)
1083
+ if (rows === false) {
1084
+ return
1085
+ }
1086
+
1101
1087
  //resolve
1102
1088
  vo.pm.resolve(rows)
1103
1089
 
@@ -1 +1 @@
1
- {"grups":"2026-09-16T15:01:20+08:00|0Lr4Fj","pemis":"2026-09-16T14:57:54+08:00|Lt1oSV","targets":"2026-09-16T14:57:54+08:00|9AnMSx","users":"2026-09-16T14:57:54+08:00|lXyOQd"}
1
+ {"grups":"2026-09-17T11:40:07+08:00|bqCAik","pemis":"2026-09-17T11:40:07+08:00|30uZzg","targets":"2026-09-17T11:40:07+08:00|wDw3pI","users":"2026-09-17T11:40:07+08:00|TSRmPQ"}