w-web-perm 1.1.11 → 1.1.13
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.
- package/README.md +158 -117
- package/dist/css/app.f09c7433.css +1 -0
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.8af87052.js +1 -0
- package/dist/js/app.8af87052.js +1 -0
- package/dist/js/{chunk-vendors.3e9e3ef1.js → chunk-vendors-legacy.ece5a361.js} +1 -1
- package/dist/js/{chunk-vendors-legacy.3e9e3ef1.js → chunk-vendors.ece5a361.js} +1 -1
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +84 -4
- package/docs/global.html +24747 -832
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +245 -32
- package/docs/server_cascadeWrite.mjs.html +225 -0
- package/docs/server_dataChannel.mjs.html +2 -2
- package/docs/server_keyCheck.mjs.html +353 -0
- package/docs/server_lockSave.mjs.html +2 -2
- package/docs/server_maskLog.mjs.html +2 -2
- package/docs/server_procCore.mjs.html +180 -41
- package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
- package/docs/server_syncKeep.mjs.html +311 -0
- package/docs/server_tools_authRefsAudit.mjs.html +247 -0
- package/docs/server_tools_authRefsClean.mjs.html +335 -0
- package/docs/server_tools_fromTools.mjs.html +797 -0
- package/docs/server_tools_isMain.mjs.html +103 -0
- package/docs/server_tools_nameDupReport.mjs.html +280 -0
- package/docs/server_validateRows.mjs.html +15 -4
- package/docs/server_wrapInjected.mjs.html +3 -3
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_js_cvKpToArr.mjs.html +125 -0
- package/docs/src_js_cvKpToObj.mjs.html +210 -0
- package/docs/src_js_draftSync.mjs.html +142 -0
- package/docs/src_js_eachOwn.mjs.html +3 -3
- package/docs/src_js_editorRefRows.mjs.html +356 -0
- package/docs/src_js_externalRows.mjs.html +338 -0
- package/docs/src_js_externalUi.mjs.html +230 -0
- package/docs/src_js_findRefRows.mjs.html +107 -0
- package/docs/src_js_findTouchedDup.mjs.html +150 -0
- package/docs/src_js_fmtLossyMsg.mjs.html +95 -0
- package/docs/src_js_fromAudit.mjs.html +489 -0
- package/docs/src_js_getAddImpact.mjs.html +269 -0
- package/docs/src_js_getAuthWarn.mjs.html +463 -0
- package/docs/src_js_getCascadePreview.mjs.html +258 -0
- package/docs/src_js_getDelRefCount.mjs.html +210 -0
- package/docs/src_js_getEffMode.mjs.html +92 -0
- package/docs/src_js_getKpLossy.mjs.html +155 -0
- package/docs/src_js_getNameErrs.mjs.html +266 -0
- package/docs/src_js_getOwn.mjs.html +3 -3
- package/docs/src_js_getPropKey.mjs.html +109 -0
- package/docs/src_js_getRuleEnable.mjs.html +2 -2
- package/docs/src_js_isAuthMode.mjs.html +92 -0
- package/docs/src_js_joinNames.mjs.html +96 -0
- package/docs/src_js_legendFit.mjs.html +2 -2
- package/docs/src_js_pairRowsToSnapshot.mjs.html +156 -0
- package/docs/src_js_refAudit.mjs.html +359 -0
- package/docs/src_js_refCascade.mjs.html +572 -0
- package/docs/src_js_relDialogSend.mjs.html +199 -0
- package/docs/src_js_rowDigest.mjs.html +265 -0
- package/docs/src_plugins_dataReloadGuard.mjs.html +109 -0
- package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
- package/docs/src_plugins_submitGuard.mjs.html +2 -2
- package/g_authRefsClean.mjs +79 -0
- package/g_fixFromType.mjs +92 -0
- package/g_initialData.mjs +3 -3
- package/g_purgeFrom.mjs +127 -0
- package/package.json +1 -1
- package/server/WWebPerm.mjs +243 -30
- package/server/cascadeWrite.mjs +153 -0
- package/server/keyCheck.mjs +281 -0
- package/server/procCore.mjs +178 -39
- package/server/procLang.mjs +271 -0
- package/server/syncKeep.mjs +239 -0
- package/server/tools/authRefsAudit.mjs +175 -0
- package/server/tools/authRefsClean.mjs +263 -0
- package/server/tools/fromTools.mjs +725 -0
- package/server/tools/isMain.mjs +31 -0
- package/server/tools/nameDupReport.mjs +208 -0
- package/server/validateRows.mjs +13 -2
- package/server/wrapInjected.mjs +1 -1
- package/src/components/LayoutContentGrups.vue +399 -77
- package/src/components/LayoutContentPemis.vue +400 -78
- package/src/components/LayoutContentTargets.vue +261 -25
- package/src/components/LayoutContentUsers.vue +336 -98
- package/src/components/VeCgrups.vue +119 -115
- package/src/components/VeCpemis.vue +117 -115
- package/src/components/VeCrules.vue +18 -2
- package/src/components/VeGrupBlngUsers.vue +240 -73
- package/src/components/VePemiBlngGrups.vue +247 -83
- package/src/js/cvKpToArr.mjs +53 -0
- package/src/js/cvKpToObj.mjs +138 -0
- package/src/js/draftSync.mjs +70 -0
- package/src/js/eachOwn.mjs +1 -1
- package/src/js/editorRefRows.mjs +284 -0
- package/src/js/externalRows.mjs +266 -0
- package/src/js/externalUi.mjs +158 -0
- package/src/js/findRefRows.mjs +35 -0
- package/src/js/findTouchedDup.mjs +78 -0
- package/src/js/fmtLossyMsg.mjs +23 -0
- package/src/js/fromAudit.mjs +417 -0
- package/src/js/getAddImpact.mjs +197 -0
- package/src/js/getAuthWarn.mjs +391 -0
- package/src/js/getCascadePreview.mjs +186 -0
- package/src/js/getDelRefCount.mjs +138 -0
- package/src/js/getEffMode.mjs +20 -0
- package/src/js/getKpLossy.mjs +83 -0
- package/src/js/getNameErrs.mjs +194 -0
- package/src/js/getOwn.mjs +1 -1
- package/src/js/getPropKey.mjs +37 -0
- package/src/js/isAuthMode.mjs +20 -0
- package/src/js/joinNames.mjs +24 -0
- package/src/js/pairRowsToSnapshot.mjs +84 -0
- package/src/js/refAudit.mjs +287 -0
- package/src/js/refCascade.mjs +500 -0
- package/src/js/relDialogSend.mjs +127 -0
- package/src/js/rowDigest.mjs +193 -0
- package/src/plugins/dataReloadGuard.mjs +37 -0
- package/src/plugins/mShare.mjs +147 -174
- package/src/plugins/mUI.mjs +3 -8
- package/src/schema/tables/users.mjs +152 -152
- package/dist/css/app.6918ec45.css +0 -1
- package/dist/js/app-legacy.963e800c.js +0 -1
- package/dist/js/app.963e800c.js +0 -1
|
@@ -198,6 +198,30 @@
|
|
|
198
198
|
|
|
199
199
|
</div>
|
|
200
200
|
|
|
201
|
+
<!-- 外部列提示與外部同步警示橫幅(bannerLines 為空時 v-for 不渲染任何節點, 畫面與 DOM 同未啟用前) -->
|
|
202
|
+
<div
|
|
203
|
+
v-for="(b, kb) in bannerLines"
|
|
204
|
+
:key="kb"
|
|
205
|
+
:data-fmid="`pemis-banner-${b.kind}`"
|
|
206
|
+
:style="getBannerStyle(b.kind)"
|
|
207
|
+
>
|
|
208
|
+
<svg v-if="b.kind==='ext'" style="vertical-align:sub; margin-right:4px;" width="16" height="16" viewBox="0 0 24 24"><path :d="mdiLock" fill="#666"></path></svg>
|
|
209
|
+
<img v-else style="vertical-align:sub; width:16px; height:16px; margin-right:4px;" :src="$ui.getIcon('warning')" />
|
|
210
|
+
<span>{{ b.text }}</span>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<!-- 資料已被更新提示(R-PRINCIPLE-00 原則 7): 有未存編輯而他人或外部同步改了本表時, 保留草稿並提示, 存檔將被伺服端拒收; 以 v-for 渲染(無提示時不渲染任何節點, 畫面與 DOM 同未啟用前) -->
|
|
214
|
+
<div
|
|
215
|
+
v-for="ks in (staleNotice ? 1 : 0)"
|
|
216
|
+
:key="`stale-${ks}`"
|
|
217
|
+
data-fmid="pemis-banner-stale"
|
|
218
|
+
:style="getBannerStyle('warn')"
|
|
219
|
+
>
|
|
220
|
+
<img style="vertical-align:sub; width:16px; height:16px; margin-right:4px;" :src="$ui.getIcon('warning')" />
|
|
221
|
+
<span>{{ $t('staleDraftNotice') }}</span>
|
|
222
|
+
<button class="staleReloadBtn" data-fmid="pemis-banner-stale-reload" @click="reloadDraft">{{ $t('staleDraftReload') }}</button>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
201
225
|
</div>
|
|
202
226
|
|
|
203
227
|
<template
|
|
@@ -213,10 +237,15 @@
|
|
|
213
237
|
>
|
|
214
238
|
<template v-slot:cell-render="props">
|
|
215
239
|
<template v-if="props.key==='name'">
|
|
240
|
+
<span v-for="loc in lockOfRow(props.row)" :key="loc.from" :title="getLockTitle(loc)" style="margin-right:4px;"><svg style="vertical-align:sub;" width="16" height="16" viewBox="0 0 24 24"><path :d="mdiLock" fill="#666"></path></svg></span>
|
|
216
241
|
<span v-if="errItemsByName[props.value]" :title="errItemsByName[props.value]">
|
|
217
242
|
<span style="color:#F57C00;">{{ props.value }}</span>
|
|
218
243
|
<img style="vertical-align:sub; width:16px; height:16px;" :src="$ui.getIcon('warning')" />
|
|
219
244
|
</span>
|
|
245
|
+
<span v-else-if="warnItemsByName[props.value]" :title="warnItemsByName[props.value]">
|
|
246
|
+
<img style="vertical-align:sub; width:16px; height:16px; margin-right:4px;" :src="$ui.getIcon('warning')" />
|
|
247
|
+
<span style="color:#F57C00;">{{ props.value }}</span>
|
|
248
|
+
</span>
|
|
220
249
|
<span v-else>{{ props.value }}</span>
|
|
221
250
|
</template>
|
|
222
251
|
<template v-else-if="props.key==='belongGrups'">
|
|
@@ -225,7 +254,13 @@
|
|
|
225
254
|
</div>
|
|
226
255
|
</template>
|
|
227
256
|
<template v-else-if="props.key==='crules'">
|
|
228
|
-
<div @click.stop.prevent @mousedown.stop.prevent>
|
|
257
|
+
<div v-if="warnCrulesById[props.row.id]" style="display:flex; align-items:center; gap:4px;" @click.stop.prevent @mousedown.stop.prevent>
|
|
258
|
+
<span :title="warnCrulesById[props.row.id]" style="display:flex; align-items:center; flex:0 0 auto;">
|
|
259
|
+
<img style="vertical-align:sub; width:16px; height:16px;" :src="$ui.getIcon('warning')" />
|
|
260
|
+
</span>
|
|
261
|
+
<button style="flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" @click="$dg.showVeCrulesById(props.row.id)">{{ getCrulesText(props.value) }}</button>
|
|
262
|
+
</div>
|
|
263
|
+
<div v-else @click.stop.prevent @mousedown.stop.prevent>
|
|
229
264
|
<button style="width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" @click="$dg.showVeCrulesById(props.row.id)">{{ getCrulesText(props.value) }}</button>
|
|
230
265
|
</div>
|
|
231
266
|
</template>
|
|
@@ -247,7 +282,7 @@
|
|
|
247
282
|
</template>
|
|
248
283
|
|
|
249
284
|
<script>
|
|
250
|
-
import { mdiStackOverflow, mdiVectorPolylinePlus, mdiCheckboxMarkedCircle, mdiCloudUploadOutline, mdiTrashCanOutline, mdiTableHeadersEye, mdiPlus, mdiPencilOutline, mdiContentCopy } from '@mdi/js/mdi.js'
|
|
285
|
+
import { mdiStackOverflow, mdiVectorPolylinePlus, mdiCheckboxMarkedCircle, mdiCloudUploadOutline, mdiTrashCanOutline, mdiTableHeadersEye, mdiPlus, mdiPencilOutline, mdiContentCopy, mdiLock } from '@mdi/js/mdi.js'
|
|
251
286
|
import JSON5 from 'json5'
|
|
252
287
|
import get from 'lodash-es/get.js'
|
|
253
288
|
import set from 'lodash-es/set.js'
|
|
@@ -264,6 +299,17 @@ import isnum from 'wsemi/src/isnum.mjs'
|
|
|
264
299
|
import cdbl from 'wsemi/src/cdbl.mjs'
|
|
265
300
|
import arrPull from 'wsemi/src/arrPull.mjs'
|
|
266
301
|
import eachOwn from '../js/eachOwn.mjs'
|
|
302
|
+
import cvKpToObj from '../js/cvKpToObj.mjs'
|
|
303
|
+
import getRuleEnable from '../js/getRuleEnable.mjs'
|
|
304
|
+
import { getRuleWarns, getRuleWarnText, fmtWarn } from '../js/getAuthWarn.mjs'
|
|
305
|
+
import { getNameErrs, mapNameKp } from '../js/getNameErrs.mjs'
|
|
306
|
+
import { getDelImpact, getDelConfirmText } from '../js/getDelRefCount.mjs'
|
|
307
|
+
import { getSavePreview } from '../js/getCascadePreview.mjs'
|
|
308
|
+
import { getAddImpact, getAddConfirmText } from '../js/getAddImpact.mjs'
|
|
309
|
+
import { isExternalRow } from '../js/externalRows.mjs'
|
|
310
|
+
import { makeBaseline } from '../js/rowDigest.mjs'
|
|
311
|
+
import { refsChanged, planDraftSync, isDraftStale } from '../js/draftSync.mjs'
|
|
312
|
+
import { fmtVars, getBannerLines, splitCheckedRows, getExternalEdits } from '../js/externalUi.mjs'
|
|
267
313
|
import WIcon from 'w-component-vue/src/components/WIcon.vue'
|
|
268
314
|
import WSwitch from 'w-component-vue/src/components/WSwitch.vue'
|
|
269
315
|
import WButtonCircle from 'w-component-vue/src/components/WButtonCircle.vue'
|
|
@@ -298,6 +344,7 @@ export default {
|
|
|
298
344
|
mdiPlus,
|
|
299
345
|
mdiPencilOutline,
|
|
300
346
|
mdiContentCopy,
|
|
347
|
+
mdiLock,
|
|
301
348
|
|
|
302
349
|
panelHeight: 100,
|
|
303
350
|
headHeight: 100,
|
|
@@ -305,6 +352,7 @@ export default {
|
|
|
305
352
|
firstLoading: true,
|
|
306
353
|
firstSetting: true,
|
|
307
354
|
systemProcing: false, //程式端載入/重載清單資料期間為 true, 用於排除非使用者操作之 rowsChange (見 $ui.markDataReload)
|
|
355
|
+
postSaving: false, //儲存成功後至成功訊息框關閉(再加短暫緩衝)期間為 true: 刪除或改名連帶更新引用端表(ADR-036), 一次儲存使 store 先後推播兩張表而多次重建清單, 其後非同步觸發之 rowsChange 不得視為使用者變更(markDataReload 之 1ms 窗口可能短於 ag-grid 渲染, 儲存鈕殘留的間歇現象); 成功訊息框期間使用者無從編輯
|
|
308
356
|
showIsEditable: false,
|
|
309
357
|
isEditable: false,
|
|
310
358
|
isModified: false,
|
|
@@ -348,11 +396,22 @@ export default {
|
|
|
348
396
|
widthPemisDescription: null,
|
|
349
397
|
|
|
350
398
|
items: [],
|
|
399
|
+
baseline: [], //載入基準([[id, 內容指紋]]): 與 items 於 changeParams 同一處由同一份來源列產生(草稿所依據之版本), 存檔時隨 RPC 送伺服端比對是否過期(F-26, R-BASELINE-01); 不得於存檔當下由最新 store 重算
|
|
400
|
+
baseRows: [], //草稿所依據之那份 store 列(與 baseline 同時產生): 存檔前之預覽、外部列守門以它為「舊」, 不用已變新之 store(有未存編輯時 store 可能已被推播更新, 見 draftSync)
|
|
401
|
+
staleNotice: false, //草稿所依據之整表已被他人/外部同步更新(R-PRINCIPLE-00 原則 7): true 時顯示「資料已被更新」橫幅與重新載入鈕
|
|
402
|
+
syncTick: 0, //重新載入觸發器(reloadDraft 遞增): changeParams 讀它而於重算時於 computed 內重建草稿, 使 genOpt 讀取之反應式來源(語系等)的依賴重新收集——保留草稿(keep)時 changeParams 不呼叫 genOpt, 依賴會收縮, 若在 computed 外直接重建則不會恢復(外審 sol 第 4 點)
|
|
351
403
|
itemsCheck: [],
|
|
352
404
|
opt: null,
|
|
353
405
|
|
|
354
406
|
}
|
|
355
407
|
},
|
|
408
|
+
created: function() {
|
|
409
|
+
//syncMark(非響應式; changeParams 讀它而不因它建立響應式依賴, 見 draftSync.planDraftSync): refs/editable 為上次建立草稿時之各表 store 參照與編輯模式, dirty 為未存編輯旗標(isModified)之鏡像(由 watch 維護),
|
|
410
|
+
//baseline 為草稿載入時之基準之副本(供保留草稿時判定是否已過期). 不讓 changeParams 依賴 isModified/baseline: 旗標切換或重新載入不得觸發它重算(其重算=重建草稿, 見 draftSync)
|
|
411
|
+
this.syncMark = { refs: null, editable: undefined, dirty: false, baseline: [], tick: 0 }
|
|
412
|
+
//saveMark(非響應式, 同 syncMark 之理由): saving 為「自己之存檔請求進行中」(送出前設真, 回應〔成功或失敗〕一到歸零)——此期間伺服端寫入所致之推播若先於回應抵達, 不是他人更新(planDraftSync 回 'keepQuiet': 保留草稿但不顯示過期橫幅; run12)
|
|
413
|
+
this.saveMark = { saving: false }
|
|
414
|
+
},
|
|
356
415
|
mounted: function() {
|
|
357
416
|
// console.log('mounted')
|
|
358
417
|
|
|
@@ -383,6 +442,17 @@ export default {
|
|
|
383
442
|
}
|
|
384
443
|
|
|
385
444
|
},
|
|
445
|
+
watch: {
|
|
446
|
+
//未存編輯旗標: 維護 syncMark.dirty 鏡像(changeParams 以之判斷「有未存編輯」而不依賴旗標本身). 旗標歸零(已儲存或已放棄)而 store 於保留草稿期間已變新者, 立即依最新 store 重建
|
|
447
|
+
//(儲存成功之推播若先於儲存回應抵達, 其時有未存編輯而被保留; 回應回來旗標歸零時須補上這次重建, 否則畫面停在儲存前之草稿而基準已過期)
|
|
448
|
+
isModified: function(b) {
|
|
449
|
+
let vo = this
|
|
450
|
+
vo.syncMark.dirty = b === true
|
|
451
|
+
if (b !== true && vo.syncMark.refs !== null && refsChanged(vo.syncMark.refs, vo.curRefs())) {
|
|
452
|
+
vo.reloadDraft()
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
},
|
|
386
456
|
computed: {
|
|
387
457
|
|
|
388
458
|
syncState: function() {
|
|
@@ -432,7 +502,7 @@ export default {
|
|
|
432
502
|
let kpPemi = {}
|
|
433
503
|
try {
|
|
434
504
|
// console.log('g.cpemis', g.cpemis)
|
|
435
|
-
kpPemi = JSON5.parse(g.cpemis)
|
|
505
|
+
kpPemi = cvKpToObj(JSON5.parse(g.cpemis), { hasMode: true }) //陣列形式經共用解碼(R-AUTH-LIST-01)
|
|
436
506
|
}
|
|
437
507
|
catch (err) {}
|
|
438
508
|
|
|
@@ -493,6 +563,22 @@ export default {
|
|
|
493
563
|
return kp
|
|
494
564
|
},
|
|
495
565
|
|
|
566
|
+
lockExternalRows: function() {
|
|
567
|
+
//後台是否不得改外部列(from 為非空字串者; 伺服端 opt.lockExternalRows 經 getWebInfor 帶來, 'y'/'n'; 舊伺服端無此鍵視為未啟用)
|
|
568
|
+
return get(this, 'webInfor.lockExternalRows', '')
|
|
569
|
+
},
|
|
570
|
+
|
|
571
|
+
isLockExternal: function() {
|
|
572
|
+
return this.lockExternalRows === 'y'
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
bannerLines: function() {
|
|
576
|
+
let vo = this
|
|
577
|
+
|
|
578
|
+
//清單上方橫幅各行: 外部列提示(啟用保護且有外部列)+本表之外部同步警示(疑似 id 重編); 皆無為空陣列, 不渲染任何節點
|
|
579
|
+
return getBannerLines({ keyTable: 'pemis', kind: 'Pemi', rows: vo.pemis, lock: vo.isLockExternal, syncWarns: get(vo, 'webInfor.syncWarns', []), $t: (k) => vo.$t(k) })
|
|
580
|
+
},
|
|
581
|
+
|
|
496
582
|
changeParams: function() {
|
|
497
583
|
// console.log('computed changeParams')
|
|
498
584
|
|
|
@@ -500,20 +586,24 @@ export default {
|
|
|
500
586
|
|
|
501
587
|
//trigger
|
|
502
588
|
let isEditable = vo.isEditable
|
|
503
|
-
let
|
|
504
|
-
let
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
589
|
+
let rows = vo.pemis
|
|
590
|
+
let kpUse = vo.kpUsePemiGrup
|
|
591
|
+
|
|
592
|
+
//草稿同步(R-PRINCIPLE-00 原則 7, draftSync.planDraftSync): store(本表或群組表)變新(推播)時, 無未存編輯 → 重建 items 與基準(同修改前); 有未存編輯 → 保留草稿與其載入時之基準, 不重建;
|
|
593
|
+
//提示「資料已被更新」只看本表基準(儲存權限只寫權限表; 群組表之變動只影響顯示用之所屬群組欄)
|
|
594
|
+
let act = planDraftSync({
|
|
595
|
+
first: vo.syncMark.refs === null || vo.syncMark.tick !== vo.syncTick, //首次載入或使用者按了「重新載入」(syncTick 遞增)
|
|
596
|
+
editChanged: vo.syncMark.editable !== isEditable,
|
|
597
|
+
storeChanged: refsChanged(vo.syncMark.refs, [rows, kpUse]),
|
|
598
|
+
hasDraft: () => vo.syncMark.dirty === true,
|
|
599
|
+
saving: () => vo.saveMark.saving === true, //自己之存檔請求進行中所致之推播 → 'keepQuiet'(不改過期提示)
|
|
600
|
+
})
|
|
601
|
+
if (act === 'rebuild') {
|
|
602
|
+
vo.rebuildDraft(rows, kpUse, isEditable)
|
|
603
|
+
}
|
|
604
|
+
else if (act === 'keep') {
|
|
605
|
+
vo.staleNotice = isDraftStale({ keys: vo.$ds.pemis.keys, rows, baseline: vo.syncMark.baseline })
|
|
606
|
+
}
|
|
517
607
|
|
|
518
608
|
return ''
|
|
519
609
|
},
|
|
@@ -546,44 +636,50 @@ export default {
|
|
|
546
636
|
return b
|
|
547
637
|
},
|
|
548
638
|
|
|
549
|
-
|
|
639
|
+
nameErrs: function() {
|
|
550
640
|
let vo = this
|
|
551
641
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
let kpErr = Object.create(null)
|
|
557
|
-
let kpName = Object.create(null)
|
|
558
|
-
each(rows, (v, k) => {
|
|
559
|
-
|
|
560
|
-
//name
|
|
561
|
-
let name = get(v, 'name', '')
|
|
562
|
-
|
|
563
|
-
//check
|
|
564
|
-
if (!isestr(name)) {
|
|
642
|
+
//名稱擋存只針對「本次造成或觸及」者, 庫內既有的歷史同名(僅外部同步可造成)不擋、改為警告圖示(R-PRINCIPLE-00 g, R-NAME-UNIQ-02);
|
|
643
|
+
//判準與伺服端同一份(findTouchedDup), 基準為 store 之 pemis(最近一次由伺服端載入的列, 儲存成功後 store 推播更新, 基準隨之更新)
|
|
644
|
+
return getNameErrs(get(vo, 'opt.rows', []), vo.baseRows, 'name')
|
|
645
|
+
},
|
|
565
646
|
|
|
566
|
-
|
|
567
|
-
|
|
647
|
+
errItemsByName: function() {
|
|
648
|
+
let vo = this
|
|
568
649
|
|
|
569
|
-
|
|
650
|
+
//擋存者(空名稱、本次造成或觸及之重複): 既有呈現(橘色名稱+圖示), 決定 isError;
|
|
651
|
+
//重複者之一為外部列(from 非空)時, 提示改為「外部來源 X 已使用此名稱」(dupExternal; 外部列由外部擁有, 須改自己之名稱)
|
|
652
|
+
return mapNameKp(vo.nameErrs.errBlock, (kind, name) => {
|
|
653
|
+
if (kind === 'dup' && name in vo.nameErrs.dupExternal) {
|
|
654
|
+
return fmtVars(vo.$t('nameUsedByExternal'), { name, from: vo.nameErrs.dupExternal[name] })
|
|
570
655
|
}
|
|
656
|
+
return vo.$t(kind === 'empty' ? `pemiNameEmpty` : `pemiNameDuplicate`)
|
|
657
|
+
})
|
|
658
|
+
},
|
|
571
659
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
//kpErr
|
|
576
|
-
kpErr[name] = vo.$t(`pemiNameDuplicate`)
|
|
660
|
+
warnItemsByName: function() {
|
|
661
|
+
let vo = this
|
|
577
662
|
|
|
578
|
-
|
|
579
|
-
|
|
663
|
+
//歷史同名(不擋存, 與 errItemsByName 互斥): 同樣顯示橘色名稱+圖示, tooltip 說明為既有重複
|
|
664
|
+
return mapNameKp(vo.nameErrs.warnHist, (n) => {
|
|
665
|
+
return fmtWarn(vo.$t('warnNameHistDup'), { n })
|
|
666
|
+
})
|
|
667
|
+
},
|
|
580
668
|
|
|
581
|
-
|
|
582
|
-
|
|
669
|
+
warnCrulesById: function() {
|
|
670
|
+
let vo = this
|
|
583
671
|
|
|
672
|
+
//crules 規則清單之衝突提示(R-PRINCIPLE-00 a): 列 id → tooltip 文字, 只含有衝突之列(同一管理對象重複列且有勾與沒勾並存); 無衝突之列不入表, 畫面維持原樣
|
|
673
|
+
let kp = Object.create(null)
|
|
674
|
+
let rows = get(vo, 'opt.rows', [])
|
|
675
|
+
each(rows, (r) => {
|
|
676
|
+
let res = getRuleWarns(get(r, 'crules', ''))
|
|
677
|
+
if (res.hasWarn) {
|
|
678
|
+
kp[get(r, 'id', '')] = getRuleWarnText(res, (k) => vo.$t(k))
|
|
679
|
+
}
|
|
584
680
|
})
|
|
585
681
|
|
|
586
|
-
return
|
|
682
|
+
return kp
|
|
587
683
|
},
|
|
588
684
|
|
|
589
685
|
isError: function() {
|
|
@@ -651,6 +747,72 @@ export default {
|
|
|
651
747
|
return head
|
|
652
748
|
},
|
|
653
749
|
|
|
750
|
+
lockOfRow: function(row) {
|
|
751
|
+
let vo = this
|
|
752
|
+
|
|
753
|
+
//啟用外部列保護且該列為外部列者回 [{ from }](首欄顯示鎖頭), 否則回空陣列(供 v-for 渲染 0 或 1 個節點: 無外部列時 DOM 同未啟用前, 不留 v-if 之註解節點)
|
|
754
|
+
if (vo.isLockExternal && isExternalRow(row)) {
|
|
755
|
+
return [{ from: row.from }]
|
|
756
|
+
}
|
|
757
|
+
return []
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
getLockTitle: function(loc) {
|
|
761
|
+
let vo = this
|
|
762
|
+
|
|
763
|
+
return fmtVars(vo.$t('externalRowLocked'), { from: loc.from })
|
|
764
|
+
},
|
|
765
|
+
|
|
766
|
+
getBannerStyle: function(kind) {
|
|
767
|
+
|
|
768
|
+
//橫幅樣式沿用既有色票與間距: 與功能區同為 border-top:1px solid #ddd; 字級 0.8rem(同副標題/waitingData)、水平內距 15px(同 waitingData)、垂直內距 5px(同功能區 padding:5px);
|
|
769
|
+
//外部列提示為中性灰底(#f2f2f2, 同按鈕 hover 底)、文字 #444(同工具鈕圖示色); 外部同步警示為淡橘底(#FFE0B2, 同編輯模式開關之橫條色)搭既有警告圖示; 長來源名稱以 overflow-wrap:anywhere 折行, 窄螢幕不溢出
|
|
770
|
+
let bg = kind === 'warn' ? '#FFE0B2' : '#f2f2f2'
|
|
771
|
+
return `padding:5px 15px; border-top:1px solid #ddd; background:${bg}; color:#444; font-size:0.8rem; overflow-wrap:anywhere;`
|
|
772
|
+
},
|
|
773
|
+
|
|
774
|
+
rebuildDraft: function(rows, kpUse, isEditable) {
|
|
775
|
+
let vo = this
|
|
776
|
+
|
|
777
|
+
//pemis, kpUsePemiGrup
|
|
778
|
+
let pemis = cloneDeep(rows)
|
|
779
|
+
let kpUsePemiGrup = cloneDeep(kpUse)
|
|
780
|
+
|
|
781
|
+
//baseline, 與 items 同一處由同一份來源列(pemis, genItems 之前)計算: 草稿所依據之版本(F-26), 重建 items 時一併更新, 存檔時不重算
|
|
782
|
+
let baseline = makeBaseline(pemis, vo.$ds.pemis.keys)
|
|
783
|
+
|
|
784
|
+
//items
|
|
785
|
+
let items = vo.genItems(pemis, kpUsePemiGrup)
|
|
786
|
+
|
|
787
|
+
//save; 重建即放棄未存編輯(isModified 歸零)與過期提示, syncMark 記下本次所依據之 store 參照與編輯模式
|
|
788
|
+
vo.items = items
|
|
789
|
+
vo.baseline = baseline
|
|
790
|
+
vo.baseRows = rows
|
|
791
|
+
vo.isModified = false
|
|
792
|
+
vo.staleNotice = false
|
|
793
|
+
vo.syncMark = { refs: [rows, kpUse], editable: isEditable, dirty: false, baseline, tick: vo.syncTick }
|
|
794
|
+
|
|
795
|
+
//genOpt
|
|
796
|
+
vo.genOpt({ isEditable })
|
|
797
|
+
|
|
798
|
+
//firstLoading
|
|
799
|
+
vo.firstLoading = false
|
|
800
|
+
|
|
801
|
+
},
|
|
802
|
+
|
|
803
|
+
curRefs: function() {
|
|
804
|
+
//目前各來源 store 之參照(與 changeParams 之 refsChanged 同一組)
|
|
805
|
+
let vo = this
|
|
806
|
+
return [vo.pemis, vo.kpUsePemiGrup]
|
|
807
|
+
},
|
|
808
|
+
|
|
809
|
+
reloadDraft: function() {
|
|
810
|
+
//「重新載入」鈕(資料已被更新橫幅): 使用者明確放棄未存編輯, 以目前 store 重建草稿與基準. 不在此直接呼叫 rebuildDraft: 遞增 syncTick 使 changeParams 重算並於 computed 內重建,
|
|
811
|
+
//genOpt 之反應式依賴(語系等)才會重新收集(保留草稿期間 changeParams 未呼叫 genOpt, 依賴已收縮; 於 computed 外重建不會恢復, 重新載入後再切換語系表頭將停留舊值, 外審 sol 第 4 點)
|
|
812
|
+
let vo = this
|
|
813
|
+
vo.syncTick++
|
|
814
|
+
},
|
|
815
|
+
|
|
654
816
|
genOpt: function() {
|
|
655
817
|
// console.log('methods genOpt')
|
|
656
818
|
|
|
@@ -675,10 +837,13 @@ export default {
|
|
|
675
837
|
let kpRowDrag = {}
|
|
676
838
|
let kpHeadCheckBox = {}
|
|
677
839
|
if (vo.isEditable) {
|
|
840
|
+
|
|
841
|
+
//外部列(from 非空)之身分/結構欄逐列唯讀(啟用保護時以函數判定: w-aggrid-vue 之 setobj 原樣取 kpCellEditable[key] 指給 ag-grid colDef.editable, 其接受 (params) => boolean);
|
|
842
|
+
//指派欄(crules)走開窗按鈕, 不受影響; from 欄於所有列皆唯讀(外部同步所擁有, 後台新增列 from 恆為空)
|
|
843
|
+
let editable = vo.isLockExternal ? (params) => !isExternalRow(params.data) : true
|
|
678
844
|
kpCellEditable = {
|
|
679
|
-
'name':
|
|
680
|
-
'description':
|
|
681
|
-
'from': true,
|
|
845
|
+
'name': editable,
|
|
846
|
+
'description': editable,
|
|
682
847
|
}
|
|
683
848
|
kpRowDrag = {
|
|
684
849
|
'name': true,
|
|
@@ -754,7 +919,7 @@ export default {
|
|
|
754
919
|
// console.log('rowsChange cloneDeep(vo.opt.rows)', cloneDeep(vo.opt.rows))
|
|
755
920
|
|
|
756
921
|
//check
|
|
757
|
-
if (!vo.syncState || vo.firstLoading || vo.firstSetting || vo.systemProcing) {
|
|
922
|
+
if (!vo.syncState || vo.firstLoading || vo.firstSetting || vo.systemProcing || vo.postSaving) {
|
|
758
923
|
return
|
|
759
924
|
}
|
|
760
925
|
|
|
@@ -941,6 +1106,7 @@ export default {
|
|
|
941
1106
|
let nameOld = r.name
|
|
942
1107
|
let _r = vo.$ds.pemis.funNew()
|
|
943
1108
|
r.id = _r.id //使用新建方式預先產生id避免重複
|
|
1109
|
+
r.from = '' //複製出之列為後台新增列, 不繼承來源(來源非空即外部列, 後台新增列 from 須為空, 否則伺服端 saveFromNotAllowed; 不論是否啟用外部列保護)
|
|
944
1110
|
r.name = vo.$s.getNameNew(rows, 'name', nameOld, vo.$t('pemiCopyNameNew'))
|
|
945
1111
|
r.userId = `{${vo.$t('pemiAddIdNew')}}`
|
|
946
1112
|
r.timeCreate = `{${vo.$t('pemiAddIdNew')}}`
|
|
@@ -986,34 +1152,94 @@ export default {
|
|
|
986
1152
|
return
|
|
987
1153
|
}
|
|
988
1154
|
|
|
989
|
-
|
|
990
|
-
|
|
1155
|
+
//外部列保護(啟用時): 外部列(from 非空)不得刪除(伺服端 saveExternalRowChanged), 先過濾掉並以 showCheckYes 提示被略過之筆數(警告, 非 success), 使用者按確定後其餘照常(含既有刪除確認框);
|
|
1156
|
+
//全為外部列則不刪除任何列, 亦不進入刪除流程(不會出現空的確認框); 提示框與刪除確認框依序而非並存
|
|
1157
|
+
let sp = splitCheckedRows(vo.itemsCheck, vo.isLockExternal)
|
|
1158
|
+
if (sp.nBlocked > 0) {
|
|
1159
|
+
vo.$dg.showCheckYes(fmtVars(vo.$t('externalRowDelBlocked'), { n: sp.nBlocked }), { type: 'warning' })
|
|
1160
|
+
.then(() => {
|
|
1161
|
+
if (size(sp.rows) > 0) {
|
|
1162
|
+
vo.deleteItems(sp.rows)
|
|
1163
|
+
}
|
|
1164
|
+
})
|
|
1165
|
+
return
|
|
1166
|
+
}
|
|
991
1167
|
|
|
992
|
-
|
|
993
|
-
rows = cloneDeep(rows)
|
|
1168
|
+
vo.deleteItems(sp.rows)
|
|
994
1169
|
|
|
995
|
-
|
|
996
|
-
|
|
1170
|
+
},
|
|
1171
|
+
|
|
1172
|
+
deleteItems: function(itemsDel) {
|
|
1173
|
+
// console.log('method deleteItems')
|
|
1174
|
+
|
|
1175
|
+
let vo = this
|
|
1176
|
+
|
|
1177
|
+
//delIds
|
|
1178
|
+
let delIds = []
|
|
1179
|
+
each(itemsDel, (v) => {
|
|
997
1180
|
// console.log('v', v)
|
|
998
1181
|
let id = get(v, 'data.id', '')
|
|
999
1182
|
if (!isestr(id)) {
|
|
1000
1183
|
console.log(`invalid id`)
|
|
1001
1184
|
return true //跳出換下一個
|
|
1002
1185
|
}
|
|
1003
|
-
|
|
1004
|
-
return vv.id !== id
|
|
1005
|
-
})
|
|
1186
|
+
delIds.push(id)
|
|
1006
1187
|
})
|
|
1007
1188
|
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1189
|
+
//getRowsRemain, 由「現在」之 opt.rows 剔除被刪之列(確認框開啟期間 store 推播可能重建 opt.rows, 故確認後再算一次)
|
|
1190
|
+
let getRowsRemain = () => {
|
|
1191
|
+
let rows = get(vo, 'opt.rows', [])
|
|
1010
1192
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
// console.log('deleteItemsCheck cloneDeep(vo.opt.rows)', cloneDeep(vo.opt.rows))
|
|
1193
|
+
//cloneDeep
|
|
1194
|
+
rows = cloneDeep(rows)
|
|
1014
1195
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1196
|
+
//filter
|
|
1197
|
+
each(delIds, (id) => {
|
|
1198
|
+
rows = filter(rows, (vv) => {
|
|
1199
|
+
return vv.id !== id
|
|
1200
|
+
})
|
|
1201
|
+
})
|
|
1202
|
+
|
|
1203
|
+
return rows
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
//doDelete
|
|
1207
|
+
let doDelete = () => {
|
|
1208
|
+
|
|
1209
|
+
//rows
|
|
1210
|
+
let rows = getRowsRemain()
|
|
1211
|
+
|
|
1212
|
+
//clear
|
|
1213
|
+
vo.itemsCheck = []
|
|
1214
|
+
|
|
1215
|
+
//save
|
|
1216
|
+
vo.opt.rows = rows
|
|
1217
|
+
// console.log('deleteItemsCheck cloneDeep(vo.opt.rows)', cloneDeep(vo.opt.rows))
|
|
1218
|
+
|
|
1219
|
+
//isModified
|
|
1220
|
+
vo.isModified = true
|
|
1221
|
+
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
//impact, 對群組(引用端)之影響, 刪除前須提示(單一框): nRefs 為儲存時將被連帶清理(自其 cpemis 移除對被刪權限之引用)之群組數(任何 mode、含停用項目;
|
|
1225
|
+
//與伺服端同一純函數 planRefChanges, 只算本次刪除, ADR-036、R-REF-CASCADE-01), nAnd 為其中以 AND 引用且刪除後查無該權限者(引用被移除後其限制解除, 授權可能變多, R-PRINCIPLE-00 c),
|
|
1226
|
+
//nWiden 為引用的權限名稱有多筆同名而只刪其一之群組數(各候選之交集變寬, 授權可能變多, R-PRINCIPLE-00 d);
|
|
1227
|
+
//被刪名稱只取基準(store 之 pemis, 已儲存)列中存在者: 尚未儲存的新增列刪除不影響他人
|
|
1228
|
+
let impact = getDelImpact(delIds, vo.baseRows, vo.grups, 'Pemi', getRowsRemain())
|
|
1229
|
+
let confirmText = getDelConfirmText(impact, (k) => vo.$t(k), 'Pemi')
|
|
1230
|
+
|
|
1231
|
+
//無影響他人之授權時維持現行流程(直接刪除, 不彈框)
|
|
1232
|
+
if (confirmText === '') {
|
|
1233
|
+
doDelete()
|
|
1234
|
+
return
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
//有影響: 確認框(單一框, 多種影響併入同一框); 使用者取消(No)則不刪除
|
|
1238
|
+
vo.$dg.showCheckYesNo(confirmText)
|
|
1239
|
+
.then(() => {
|
|
1240
|
+
doDelete()
|
|
1241
|
+
})
|
|
1242
|
+
.catch(() => {})
|
|
1017
1243
|
|
|
1018
1244
|
},
|
|
1019
1245
|
|
|
@@ -1049,7 +1275,7 @@ export default {
|
|
|
1049
1275
|
let kpRule = {}
|
|
1050
1276
|
try {
|
|
1051
1277
|
// console.log('crules', crules)
|
|
1052
|
-
kpRule = JSON5.parse(crules)
|
|
1278
|
+
kpRule = cvKpToObj(JSON5.parse(crules)) //陣列形式經共用解碼(R-AUTH-LIST-01)
|
|
1053
1279
|
}
|
|
1054
1280
|
catch (err) {}
|
|
1055
1281
|
// console.log('kpRule', kpRule)
|
|
@@ -1064,7 +1290,7 @@ export default {
|
|
|
1064
1290
|
// console.log(k, 'v', v)
|
|
1065
1291
|
|
|
1066
1292
|
//isActive
|
|
1067
|
-
let isActive =
|
|
1293
|
+
let isActive = getRuleEnable(kpRule, k) //啟用狀態依共用語意(值為 y/n 或物件形式 { isActive }; 與伺服端授權一致, R-AUTH-LIST-01)
|
|
1068
1294
|
if (isActive !== 'y') {
|
|
1069
1295
|
return true //跳出換下一個
|
|
1070
1296
|
}
|
|
@@ -1152,11 +1378,9 @@ export default {
|
|
|
1152
1378
|
|
|
1153
1379
|
//find
|
|
1154
1380
|
let r = null
|
|
1155
|
-
|
|
1156
|
-
each(rows, (v, k) => {
|
|
1381
|
+
each(rows, (v) => {
|
|
1157
1382
|
if (get(v, 'id', '') === id) {
|
|
1158
1383
|
r = v
|
|
1159
|
-
kr = k
|
|
1160
1384
|
return false //跳出
|
|
1161
1385
|
}
|
|
1162
1386
|
})
|
|
@@ -1167,6 +1391,10 @@ export default {
|
|
|
1167
1391
|
return
|
|
1168
1392
|
}
|
|
1169
1393
|
|
|
1394
|
+
//baseline0: 開啟編輯器當下之載入基準(整表, F-26、原則 7 表級比對, 外審 sol R1 與 run11 第 1 點): 編輯器開著時若他人/外部同步改了任何一列或增刪列, 而父頁當時無未存編輯, 推播會重建 items 與基準,
|
|
1395
|
+
//編輯器回傳之字串仍是舊版內容, 回填後存檔會以「新基準」證明過期內容新鮮(還原已被撤銷之授權, 或繞過同表別列已被改動之整表比對); 故回填前比對「草稿現所依據之那份表」與開框時之基準, 有異即放棄回填並提示重新載入.
|
|
1396
|
+
//父頁已有未存編輯時推播只保留草稿與其基準(baseRows 不變), 此比對不誤擋, 存檔時由伺服端表級比對拒收
|
|
1397
|
+
let baseline0 = cloneDeep(vo.baseline)
|
|
1170
1398
|
//showVeCrules
|
|
1171
1399
|
vo.$dg.showVeCrules({
|
|
1172
1400
|
isEditable: vo.isEditable,
|
|
@@ -1175,9 +1403,28 @@ export default {
|
|
|
1175
1403
|
.then((crules) => {
|
|
1176
1404
|
// console.log('crules', crules)
|
|
1177
1405
|
|
|
1406
|
+
//以 id 於「現在」之 opt.rows 重新找列(R-REL-PAIR-01): 對話框開啟期間 store 更新會重建 opt.rows(他人增刪、改 order), 開框時之列索引可能已指向另一列; 該列已不存在則提示並放棄回填
|
|
1407
|
+
let krNow = -1
|
|
1408
|
+
each(get(vo, 'opt.rows', []), (v, k) => {
|
|
1409
|
+
if (get(v, 'id', '') === id) {
|
|
1410
|
+
krNow = k
|
|
1411
|
+
return false //跳出
|
|
1412
|
+
}
|
|
1413
|
+
})
|
|
1414
|
+
if (krNow < 0) {
|
|
1415
|
+
vo.$alert(`${vo.$t('pemiEditCrulesNoPemiData')}`, { type: 'error' })
|
|
1416
|
+
return
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
//草稿所依據之表於編輯器開啟後已被改動(整表指紋與開框時之基準不同; 僅 order/伺服器管理欄不同不算): 放棄回填並提示重新載入(同伺服端 saveStale 之語意)
|
|
1420
|
+
if (isDraftStale({ keys: vo.$ds.pemis.keys, rows: vo.baseRows, baseline: baseline0 })) {
|
|
1421
|
+
vo.$alert(`${vo.$t('saveStale')}`, { type: 'warning' })
|
|
1422
|
+
return
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1178
1425
|
//set
|
|
1179
|
-
set(vo, `opt.rows[${
|
|
1180
|
-
// console.log('vo.opt.rows[
|
|
1426
|
+
set(vo, `opt.rows[${krNow}].crules`, crules)
|
|
1427
|
+
// console.log('vo.opt.rows[krNow]', cloneDeep(vo.opt.rows[krNow]))
|
|
1181
1428
|
|
|
1182
1429
|
//refresh
|
|
1183
1430
|
vo.refresh()
|
|
@@ -1212,8 +1459,9 @@ export default {
|
|
|
1212
1459
|
return
|
|
1213
1460
|
}
|
|
1214
1461
|
|
|
1215
|
-
//rows
|
|
1462
|
+
//rows, baseline: 與 rows 同一刻(同步)擷取——兩者於 changeParams 同一處同時產生, 之後確認框期間若推播重建清單, 仍帶「草稿所依據之那一版」之基準(F-26), 不由最新 store 重算
|
|
1216
1463
|
let rows = get(vo, 'opt.rows', [])
|
|
1464
|
+
let baseline = cloneDeep(vo.baseline)
|
|
1217
1465
|
|
|
1218
1466
|
//check
|
|
1219
1467
|
if (size(rows) === 0) {
|
|
@@ -1222,28 +1470,85 @@ export default {
|
|
|
1222
1470
|
return
|
|
1223
1471
|
}
|
|
1224
1472
|
|
|
1473
|
+
//外部列保護(啟用時): 外部列之身分/結構欄以逐列唯讀與刪除過濾擋下, 但貼上(Ctrl+V)等路徑可繞過逐列唯讀, 送出前再比對一次(基準為 store 之 pemis);
|
|
1474
|
+
//有違規即不送出並提示(伺服端同樣整次拒收 saveExternalRowChanged); 於開 loading 之前, 訊息框前釋放按鈕鎖
|
|
1475
|
+
if (vo.isLockExternal && getExternalEdits({ keyTable: 'pemis', keys: vo.$ds.pemis.keys, rowsNew: rows, rowsOld: vo.baseRows }) !== null) {
|
|
1476
|
+
unlockBtn()
|
|
1477
|
+
await vo.$dg.showCheckYes(vo.$t('saveExternalRowChanged'), { type: 'warning' })
|
|
1478
|
+
return
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1225
1481
|
//新增列之 transient 欄位 _isNew(addItem / copyItem 標記)隨列送後端: 後端據以拒絕「標為新增但 id 已存在」之列
|
|
1226
1482
|
//(同一包重送時不把剛建立之列當修改覆寫, 回 saveNewRowExists), 檢查後由 ltdtmapping 依 schema 取欄時剝除不入庫 (ADR-025)
|
|
1227
1483
|
|
|
1228
|
-
//2)
|
|
1484
|
+
//2) 引用連帶預覽(ADR-036、R-REF-CASCADE-01; 與伺服端同一純函數 planRefChanges, 基準為 store 之 pemis/grups, 筆數以伺服端實際為準): 權限改名時引用它之群組 cpemis 於同一次寫入內連帶改名,
|
|
1485
|
+
//有引用端將被更新時先確認(確認框文字與成功訊息之判定見 src/js/getCascadePreview.mjs); plan.err 非 null(新名撞懸空名等)不彈框、照常送出, 伺服端以固定語系鍵拒存並由下方 showCheckYes 顯示
|
|
1486
|
+
let pv = getSavePreview({ kind: 'Pemi', rowsOld: vo.baseRows, rowsNew: rows, holdersOld: vo.grups, cascadeRename: get(vo, 'webInfor.cascadeRename', true), $t: (k) => vo.$t(k) })
|
|
1487
|
+
|
|
1488
|
+
//新增撞懸空名(F-23/F-26 原則 7; src/js/getAddImpact.mjs): 本次新增之權限名稱查無既有同名實體, 而仍有啟用中之群組 cpemis 引用該名稱——新增後即解到新權限而取得或受其限制,
|
|
1489
|
+
//須明確同意才送出; 與改名預覽同於開 loading 之前同步計算(基準為 store 之 pemis/grups), 無懸空引用時不彈框; 與改名確認框依序(先改名預覽、再新增確認), 各自只在需要時彈出
|
|
1490
|
+
let addText = getAddConfirmText(getAddImpact({ keyTable: 'pemis', rowsOld: vo.baseRows, rowsNew: rows, holders: vo.grups }), (k) => vo.$t(k), 'Pemi')
|
|
1491
|
+
|
|
1492
|
+
if (pv.confirmText !== '') {
|
|
1493
|
+
|
|
1494
|
+
//先開確認框之流程: 開框前 unlockBtn() 釋放儲存鈕之 promiseUnlock 鎖(同上, 按鈕鎖只涵蓋請求期間); 流程狀態仍由 runSubmit 保持至本流程結束,
|
|
1495
|
+
//確認框與後續請求期間之重入(焦點仍在儲存鈕時 Enter)皆被略過 (ADR-025)
|
|
1496
|
+
unlockBtn()
|
|
1497
|
+
let isGo = await vo.$dg.showCheckYesNo(pv.confirmText)
|
|
1498
|
+
.then(() => true)
|
|
1499
|
+
.catch(() => false)
|
|
1500
|
+
|
|
1501
|
+
//選否(或確認框被取代而視同選否): 中止儲存, 不送出; 尚未開 loading, 資料與 isModified 保留, 由 runSubmit 於回傳後結束流程
|
|
1502
|
+
if (!isGo) {
|
|
1503
|
+
return
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
//新增撞懸空名之確認(同上之鎖釋放與選否中止; 單一框、單一問句)
|
|
1509
|
+
if (addText !== '') {
|
|
1510
|
+
unlockBtn()
|
|
1511
|
+
let isGoAdd = await vo.$dg.showCheckYesNo(addText)
|
|
1512
|
+
.then(() => true)
|
|
1513
|
+
.catch(() => false)
|
|
1514
|
+
if (!isGoAdd) {
|
|
1515
|
+
return
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
//3) 確定打 API 才開 loading
|
|
1229
1520
|
vo.$ui.updateLoading(true)
|
|
1230
1521
|
|
|
1231
|
-
//
|
|
1522
|
+
//4) updatePemis, 各自 catch + 旗標短路; 第 2 參 { baseline } 為載入基準(伺服端於表鎖內比對本次將寫入之列是否仍與草稿所依據之版本相同, 否則整次拒收 saveStale)
|
|
1232
1523
|
let okSave = false
|
|
1233
|
-
|
|
1234
|
-
|
|
1524
|
+
vo.saveMark.saving = true //自己之存檔請求進行中: 此期間之推播不是他人更新(changeParams 的 planDraftSync 回 'keepQuiet'); 回應(成功或失敗)一到即歸零
|
|
1525
|
+
await vo.$fapi.updatePemis(rows, { baseline })
|
|
1526
|
+
.then(() => {
|
|
1527
|
+
okSave = true
|
|
1528
|
+
vo.saveMark.saving = false
|
|
1529
|
+
})
|
|
1235
1530
|
.catch(async (err) => {
|
|
1531
|
+
vo.saveMark.saving = false
|
|
1532
|
+
vo.staleNotice = isDraftStale({ keys: vo.$ds.pemis.keys, rows: vo.pemis, baseline: vo.syncMark.baseline }) //失敗(含 saveStale 被拒): 請求進行中被安靜保留之推播, 此時依目前 store 重算過期提示(同 changeParams 之保留分支)
|
|
1236
1533
|
vo.$ui.updateLoading(false) //showCheckYes 前關 loading(modal 阻斷期間避免 loading 疊在底下)
|
|
1237
1534
|
await vo.$dg.showCheckYes(`${vo.$t('pemiSavePemisFail')}: ${vo.$tErr(err)}`)
|
|
1238
1535
|
})
|
|
1239
1536
|
if (!okSave) return
|
|
1240
1537
|
|
|
1241
|
-
//isModified
|
|
1538
|
+
//isModified; postSaving 自此至成功訊息框關閉後 300ms 內排除 rowsChange(見 data 註解)
|
|
1539
|
+
vo.postSaving = true
|
|
1242
1540
|
vo.isModified = false
|
|
1243
1541
|
|
|
1244
1542
|
//alert(showCheckYes 前關 loading,避免 modal 等待期間 loading 疊在底下)
|
|
1245
1543
|
vo.$ui.updateLoading(false)
|
|
1246
|
-
|
|
1544
|
+
try {
|
|
1545
|
+
await vo.$dg.showCheckYes(pv.successText !== '' ? pv.successText : vo.$t('pemiSavePemisSuccess'), { type: 'success' }) //只有改名且有群組之引用被連帶更新才附筆數(刪除所致之清理不改, 已於刪除確認框告知), 否則維持原成功訊息
|
|
1546
|
+
}
|
|
1547
|
+
finally {
|
|
1548
|
+
setTimeout(() => {
|
|
1549
|
+
vo.postSaving = false
|
|
1550
|
+
}, 300)
|
|
1551
|
+
}
|
|
1247
1552
|
|
|
1248
1553
|
}
|
|
1249
1554
|
|
|
@@ -1256,6 +1561,9 @@ export default {
|
|
|
1256
1561
|
})
|
|
1257
1562
|
.finally(() => {
|
|
1258
1563
|
|
|
1564
|
+
//非預期例外時亦歸零, 避免之後之推播永遠被當成自己存檔所致
|
|
1565
|
+
vo.saveMark.saving = false
|
|
1566
|
+
|
|
1259
1567
|
//hide loading
|
|
1260
1568
|
vo.$ui.updateLoading(false)
|
|
1261
1569
|
|
|
@@ -1269,4 +1577,18 @@ export default {
|
|
|
1269
1577
|
</script>
|
|
1270
1578
|
|
|
1271
1579
|
<style scoped>
|
|
1580
|
+
/* 「資料已被更新」橫幅之重新載入鈕: 底色 #fff(同工具鈕底)、hover #f2f2f2(同工具鈕 hover)、邊線 #ddd(同橫幅邊線)、文字 #444(同橫幅文字), 與橫幅同字級 */
|
|
1581
|
+
.staleReloadBtn {
|
|
1582
|
+
margin-left: 8px;
|
|
1583
|
+
padding: 1px 10px;
|
|
1584
|
+
font-size: 0.8rem;
|
|
1585
|
+
color: #444;
|
|
1586
|
+
background: #fff;
|
|
1587
|
+
border: 1px solid #ddd;
|
|
1588
|
+
border-radius: 4px;
|
|
1589
|
+
cursor: pointer;
|
|
1590
|
+
}
|
|
1591
|
+
.staleReloadBtn:hover {
|
|
1592
|
+
background: #f2f2f2;
|
|
1593
|
+
}
|
|
1272
1594
|
</style>
|