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="`targets-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="targets-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="targets-banner-stale-reload" @click="reloadDraft">{{ $t('staleDraftReload') }}</button>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
201
225
|
</div>
|
|
202
226
|
|
|
203
227
|
<template
|
|
@@ -213,6 +237,7 @@
|
|
|
213
237
|
>
|
|
214
238
|
<template v-slot:cell-render="props">
|
|
215
239
|
<template v-if="props.key==='id'">
|
|
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="errItemsById[props.value]" :title="errItemsById[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')" />
|
|
@@ -237,7 +262,7 @@
|
|
|
237
262
|
</template>
|
|
238
263
|
|
|
239
264
|
<script>
|
|
240
|
-
import { mdiGamepadCircle, mdiStackOverflow, mdiAccountGroupOutline, mdiBallotRecountOutline, mdiCloudUploadOutline, mdiTrashCanOutline, mdiTableHeadersEye, mdiPlus, mdiPencilOutline, mdiContentCopy } from '@mdi/js/mdi.js'
|
|
265
|
+
import { mdiGamepadCircle, mdiStackOverflow, mdiAccountGroupOutline, mdiBallotRecountOutline, mdiCloudUploadOutline, mdiTrashCanOutline, mdiTableHeadersEye, mdiPlus, mdiPencilOutline, mdiContentCopy, mdiLock } from '@mdi/js/mdi.js'
|
|
241
266
|
import get from 'lodash-es/get.js'
|
|
242
267
|
import each from 'lodash-es/each.js'
|
|
243
268
|
import size from 'lodash-es/size.js'
|
|
@@ -251,6 +276,12 @@ import isnum from 'wsemi/src/isnum.mjs'
|
|
|
251
276
|
import cdbl from 'wsemi/src/cdbl.mjs'
|
|
252
277
|
import arrPull from 'wsemi/src/arrPull.mjs'
|
|
253
278
|
import reuseDeletedIds from '../plugins/reuseDeletedIds.mjs'
|
|
279
|
+
import { getSavePreview } from '../js/getCascadePreview.mjs'
|
|
280
|
+
import { getAddImpact, getAddConfirmText } from '../js/getAddImpact.mjs'
|
|
281
|
+
import { isExternalRow } from '../js/externalRows.mjs'
|
|
282
|
+
import { makeBaseline } from '../js/rowDigest.mjs'
|
|
283
|
+
import { refsChanged, planDraftSync, isDraftStale } from '../js/draftSync.mjs'
|
|
284
|
+
import { fmtVars, getBannerLines, splitCheckedRows, getExternalEdits } from '../js/externalUi.mjs'
|
|
254
285
|
import WIcon from 'w-component-vue/src/components/WIcon.vue'
|
|
255
286
|
import WSwitch from 'w-component-vue/src/components/WSwitch.vue'
|
|
256
287
|
import WButtonCircle from 'w-component-vue/src/components/WButtonCircle.vue'
|
|
@@ -286,6 +317,7 @@ export default {
|
|
|
286
317
|
mdiPlus,
|
|
287
318
|
mdiPencilOutline,
|
|
288
319
|
mdiContentCopy,
|
|
320
|
+
mdiLock,
|
|
289
321
|
|
|
290
322
|
panelHeight: 100,
|
|
291
323
|
headHeight: 100,
|
|
@@ -329,11 +361,22 @@ export default {
|
|
|
329
361
|
widthTargetDescription: null,
|
|
330
362
|
|
|
331
363
|
items: [],
|
|
364
|
+
baseline: [], //載入基準([[id, 內容指紋]]): 與 items 於 changeParams 同一處由同一份來源列產生(草稿所依據之版本), 存檔時隨 RPC 送伺服端比對是否過期(F-26, R-BASELINE-01); 不得於存檔當下由最新 store 重算
|
|
365
|
+
baseRows: [], //草稿所依據之那份 store 列(與 baseline 同時產生): 存檔前之預覽、外部列守門以它為「舊」, 不用已變新之 store(有未存編輯時 store 可能已被推播更新, 見 draftSync)
|
|
366
|
+
staleNotice: false, //草稿所依據之整表已被他人/外部同步更新(R-PRINCIPLE-00 原則 7): true 時顯示「資料已被更新」橫幅與重新載入鈕
|
|
367
|
+
syncTick: 0, //重新載入觸發器(reloadDraft 遞增): changeParams 讀它而於重算時於 computed 內重建草稿, 使 genOpt 讀取之反應式來源(語系等)的依賴重新收集——保留草稿(keep)時 changeParams 不呼叫 genOpt, 依賴會收縮, 若在 computed 外直接重建則不會恢復(外審 sol 第 4 點)
|
|
332
368
|
itemsCheck: [],
|
|
333
369
|
opt: null,
|
|
334
370
|
|
|
335
371
|
}
|
|
336
372
|
},
|
|
373
|
+
created: function() {
|
|
374
|
+
//syncMark(非響應式; changeParams 讀它而不因它建立響應式依賴, 見 draftSync.planDraftSync): refs/editable 為上次建立草稿時之各表 store 參照與編輯模式, dirty 為未存編輯旗標(isModified)之鏡像(由 watch 維護),
|
|
375
|
+
//baseline 為草稿載入時之基準之副本(供保留草稿時判定是否已過期). 不讓 changeParams 依賴 isModified/baseline: 旗標切換或重新載入不得觸發它重算(其重算=重建草稿, 見 draftSync)
|
|
376
|
+
this.syncMark = { refs: null, editable: undefined, dirty: false, baseline: [], tick: 0 }
|
|
377
|
+
//saveMark(非響應式, 同 syncMark 之理由): saving 為「自己之存檔請求進行中」(送出前設真, 回應〔成功或失敗〕一到歸零)——此期間伺服端寫入所致之推播若先於回應抵達, 不是他人更新(planDraftSync 回 'keepQuiet': 保留草稿但不顯示過期橫幅; run12)
|
|
378
|
+
this.saveMark = { saving: false }
|
|
379
|
+
},
|
|
337
380
|
mounted: function() {
|
|
338
381
|
// console.log('mounted')
|
|
339
382
|
|
|
@@ -360,6 +403,17 @@ export default {
|
|
|
360
403
|
}
|
|
361
404
|
|
|
362
405
|
},
|
|
406
|
+
watch: {
|
|
407
|
+
//未存編輯旗標: 維護 syncMark.dirty 鏡像(changeParams 以之判斷「有未存編輯」而不依賴旗標本身). 旗標歸零(已儲存或已放棄)而 store 於保留草稿期間已變新者, 立即依最新 store 重建
|
|
408
|
+
//(儲存成功之推播若先於儲存回應抵達, 其時有未存編輯而被保留; 回應回來旗標歸零時須補上這次重建, 否則畫面停在儲存前之草稿而基準已過期)
|
|
409
|
+
isModified: function(b) {
|
|
410
|
+
let vo = this
|
|
411
|
+
vo.syncMark.dirty = b === true
|
|
412
|
+
if (b !== true && vo.syncMark.refs !== null && refsChanged(vo.syncMark.refs, vo.curRefs())) {
|
|
413
|
+
vo.reloadDraft()
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
},
|
|
363
417
|
computed: {
|
|
364
418
|
|
|
365
419
|
syncState: function() {
|
|
@@ -396,6 +450,22 @@ export default {
|
|
|
396
450
|
return rs
|
|
397
451
|
},
|
|
398
452
|
|
|
453
|
+
lockExternalRows: function() {
|
|
454
|
+
//後台是否不得改外部列(from 為非空字串者; 伺服端 opt.lockExternalRows 經 getWebInfor 帶來, 'y'/'n'; 舊伺服端無此鍵視為未啟用)
|
|
455
|
+
return get(this, 'webInfor.lockExternalRows', '')
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
isLockExternal: function() {
|
|
459
|
+
return this.lockExternalRows === 'y'
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
bannerLines: function() {
|
|
463
|
+
let vo = this
|
|
464
|
+
|
|
465
|
+
//清單上方橫幅各行: 外部列提示(啟用保護且有外部列; 標的無指派欄, 用 Target 專用文案)+本表之外部同步警示; 皆無為空陣列, 不渲染任何節點
|
|
466
|
+
return getBannerLines({ keyTable: 'targets', kind: 'Target', rows: vo.targets, lock: vo.isLockExternal, syncWarns: get(vo, 'webInfor.syncWarns', []), $t: (k) => vo.$t(k) })
|
|
467
|
+
},
|
|
468
|
+
|
|
399
469
|
changeParams: function() {
|
|
400
470
|
// console.log('computed changeParams')
|
|
401
471
|
|
|
@@ -403,18 +473,22 @@ export default {
|
|
|
403
473
|
|
|
404
474
|
//trigger
|
|
405
475
|
let isEditable = vo.isEditable
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
476
|
+
let rows = vo.targets
|
|
477
|
+
|
|
478
|
+
//草稿同步(R-PRINCIPLE-00 原則 7, draftSync.planDraftSync): store 變新(推播)時, 無未存編輯 → 重建 items 與基準(同修改前); 有未存編輯 → 保留草稿與其載入時之基準, 不重建, 只判定是否提示「資料已被更新」
|
|
479
|
+
let act = planDraftSync({
|
|
480
|
+
first: vo.syncMark.refs === null || vo.syncMark.tick !== vo.syncTick, //首次載入或使用者按了「重新載入」(syncTick 遞增)
|
|
481
|
+
editChanged: vo.syncMark.editable !== isEditable,
|
|
482
|
+
storeChanged: refsChanged(vo.syncMark.refs, [rows]),
|
|
483
|
+
hasDraft: () => vo.syncMark.dirty === true,
|
|
484
|
+
saving: () => vo.saveMark.saving === true, //自己之存檔請求進行中所致之推播 → 'keepQuiet'(不改過期提示)
|
|
485
|
+
})
|
|
486
|
+
if (act === 'rebuild') {
|
|
487
|
+
vo.rebuildDraft(rows, isEditable)
|
|
488
|
+
}
|
|
489
|
+
else if (act === 'keep') {
|
|
490
|
+
vo.staleNotice = isDraftStale({ keys: vo.$ds.targets.keys, rows, baseline: vo.syncMark.baseline })
|
|
491
|
+
}
|
|
418
492
|
|
|
419
493
|
return ''
|
|
420
494
|
},
|
|
@@ -456,11 +530,17 @@ export default {
|
|
|
456
530
|
//kpErr
|
|
457
531
|
let kpErr = Object.create(null) //無原型: 鍵為使用者資料, 不得命中 constructor 等原型鍵(規則帳本 R-KEY-01)
|
|
458
532
|
let kpId = Object.create(null)
|
|
533
|
+
let kpExt = Object.create(null) //id → 外部列之來源 from(共用該 id 之列中有外部列者; 重複之提示改為「外部來源 X 已使用此名稱」, 以 id 為名)
|
|
459
534
|
each(rows, (v, k) => {
|
|
460
535
|
|
|
461
536
|
//id
|
|
462
537
|
let id = get(v, 'id', '')
|
|
463
538
|
|
|
539
|
+
//kpExt
|
|
540
|
+
if (isestr(id) && isExternalRow(v) && !haskey(kpExt, id)) {
|
|
541
|
+
kpExt[id] = v.from
|
|
542
|
+
}
|
|
543
|
+
|
|
464
544
|
//check
|
|
465
545
|
if (!isestr(id)) {
|
|
466
546
|
|
|
@@ -474,7 +554,7 @@ export default {
|
|
|
474
554
|
if (haskey(kpId, id)) {
|
|
475
555
|
|
|
476
556
|
//kpErr
|
|
477
|
-
kpErr[id] = vo.$t(`targetIdDuplicate`)
|
|
557
|
+
kpErr[id] = haskey(kpExt, id) ? fmtVars(vo.$t('nameUsedByExternal'), { name: id, from: kpExt[id] }) : vo.$t(`targetIdDuplicate`)
|
|
478
558
|
|
|
479
559
|
return true //跳出換下一個
|
|
480
560
|
}
|
|
@@ -549,6 +629,68 @@ export default {
|
|
|
549
629
|
return head
|
|
550
630
|
},
|
|
551
631
|
|
|
632
|
+
lockOfRow: function(row) {
|
|
633
|
+
let vo = this
|
|
634
|
+
|
|
635
|
+
//啟用外部列保護且該列為外部列者回 [{ from }](首欄顯示鎖頭), 否則回空陣列(供 v-for 渲染 0 或 1 個節點: 無外部列時 DOM 同未啟用前, 不留 v-if 之註解節點)
|
|
636
|
+
if (vo.isLockExternal && isExternalRow(row)) {
|
|
637
|
+
return [{ from: row.from }]
|
|
638
|
+
}
|
|
639
|
+
return []
|
|
640
|
+
},
|
|
641
|
+
|
|
642
|
+
getLockTitle: function(loc) {
|
|
643
|
+
let vo = this
|
|
644
|
+
|
|
645
|
+
return fmtVars(vo.$t('externalRowLocked'), { from: loc.from })
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
getBannerStyle: function(kind) {
|
|
649
|
+
|
|
650
|
+
//橫幅樣式沿用既有色票與間距: 與功能區同為 border-top:1px solid #ddd; 字級 0.8rem(同副標題/waitingData)、水平內距 15px(同 waitingData)、垂直內距 5px(同功能區 padding:5px);
|
|
651
|
+
//外部列提示為中性灰底(#f2f2f2, 同按鈕 hover 底)、文字 #444(同工具鈕圖示色); 外部同步警示為淡橘底(#FFE0B2, 同編輯模式開關之橫條色)搭既有警告圖示; 長來源名稱以 overflow-wrap:anywhere 折行, 窄螢幕不溢出
|
|
652
|
+
let bg = kind === 'warn' ? '#FFE0B2' : '#f2f2f2'
|
|
653
|
+
return `padding:5px 15px; border-top:1px solid #ddd; background:${bg}; color:#444; font-size:0.8rem; overflow-wrap:anywhere;`
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
rebuildDraft: function(rows, isEditable) {
|
|
657
|
+
let vo = this
|
|
658
|
+
|
|
659
|
+
//items
|
|
660
|
+
let items = cloneDeep(rows)
|
|
661
|
+
|
|
662
|
+
//baseline, 與 items 同一處由同一份來源列(items)計算: 草稿所依據之版本(F-26), 重建 items 時一併更新, 存檔時不重算
|
|
663
|
+
let baseline = makeBaseline(items, vo.$ds.targets.keys)
|
|
664
|
+
|
|
665
|
+
//save; 重建即放棄未存編輯(isModified 歸零)與過期提示, syncMark 記下本次所依據之 store 參照與編輯模式
|
|
666
|
+
vo.items = items
|
|
667
|
+
vo.baseline = baseline
|
|
668
|
+
vo.baseRows = rows
|
|
669
|
+
vo.isModified = false
|
|
670
|
+
vo.staleNotice = false
|
|
671
|
+
vo.syncMark = { refs: [rows], editable: isEditable, dirty: false, baseline, tick: vo.syncTick }
|
|
672
|
+
|
|
673
|
+
//genOpt
|
|
674
|
+
vo.genOpt({ isEditable })
|
|
675
|
+
|
|
676
|
+
//firstLoading
|
|
677
|
+
vo.firstLoading = false
|
|
678
|
+
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
curRefs: function() {
|
|
682
|
+
//目前各來源 store 之參照(與 changeParams 之 refsChanged 同一組)
|
|
683
|
+
let vo = this
|
|
684
|
+
return [vo.targets]
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
reloadDraft: function() {
|
|
688
|
+
//「重新載入」鈕(資料已被更新橫幅): 使用者明確放棄未存編輯, 以目前 store 重建草稿與基準. 不在此直接呼叫 rebuildDraft: 遞增 syncTick 使 changeParams 重算並於 computed 內重建,
|
|
689
|
+
//genOpt 之反應式依賴(語系等)才會重新收集(保留草稿期間 changeParams 未呼叫 genOpt, 依賴已收縮; 於 computed 外重建不會恢復, 重新載入後再切換語系表頭將停留舊值, 外審 sol 第 4 點)
|
|
690
|
+
let vo = this
|
|
691
|
+
vo.syncTick++
|
|
692
|
+
},
|
|
693
|
+
|
|
552
694
|
genOpt: function() {
|
|
553
695
|
// console.log('methods genOpt')
|
|
554
696
|
|
|
@@ -573,10 +715,13 @@ export default {
|
|
|
573
715
|
let kpRowDrag = {}
|
|
574
716
|
let kpHeadCheckBox = {}
|
|
575
717
|
if (vo.isEditable) {
|
|
718
|
+
|
|
719
|
+
//外部列(from 非空)之身分/結構欄逐列唯讀(啟用保護時以函數判定: w-aggrid-vue 之 setobj 原樣取 kpCellEditable[key] 指給 ag-grid colDef.editable, 其接受 (params) => boolean);
|
|
720
|
+
//標的無指派欄; from 欄於所有列皆唯讀(外部同步所擁有, 後台新增列 from 恆為空)
|
|
721
|
+
let editable = vo.isLockExternal ? (params) => !isExternalRow(params.data) : true
|
|
576
722
|
kpCellEditable = {
|
|
577
|
-
'id':
|
|
578
|
-
'description':
|
|
579
|
-
'from': true,
|
|
723
|
+
'id': editable,
|
|
724
|
+
'description': editable,
|
|
580
725
|
}
|
|
581
726
|
kpRowDrag = {
|
|
582
727
|
'id': true,
|
|
@@ -770,6 +915,7 @@ export default {
|
|
|
770
915
|
let _r = vo.$ds.targets.funNew()
|
|
771
916
|
r.id = _r.id //使用新建方式預先產生id避免重複
|
|
772
917
|
r.id = vo.$s.getNameNew(rows, 'id', nameOld, vo.$t('targetCopyNameNew'))
|
|
918
|
+
r.from = '' //複製出之列為後台新增列, 不繼承來源(來源非空即外部列, 後台新增列 from 須為空, 否則伺服端 saveFromNotAllowed; 不論是否啟用外部列保護)
|
|
773
919
|
r.userId = `{${vo.$t('targetAddIdNew')}}`
|
|
774
920
|
r.timeCreate = `{${vo.$t('targetAddIdNew')}}`
|
|
775
921
|
r.userIdUpdate = `{${vo.$t('targetAddIdNew')}}`
|
|
@@ -803,6 +949,28 @@ export default {
|
|
|
803
949
|
return
|
|
804
950
|
}
|
|
805
951
|
|
|
952
|
+
//外部列保護(啟用時): 外部列(from 非空)不得刪除(伺服端 saveExternalRowChanged), 先過濾掉並以 showCheckYes 提示被略過之筆數(警告, 非 success), 使用者按確定後其餘照常;
|
|
953
|
+
//全為外部列則不刪除任何列
|
|
954
|
+
let sp = splitCheckedRows(vo.itemsCheck, vo.isLockExternal)
|
|
955
|
+
if (sp.nBlocked > 0) {
|
|
956
|
+
vo.$dg.showCheckYes(fmtVars(vo.$t('externalRowDelBlocked'), { n: sp.nBlocked }), { type: 'warning' })
|
|
957
|
+
.then(() => {
|
|
958
|
+
if (size(sp.rows) > 0) {
|
|
959
|
+
vo.deleteItems(sp.rows)
|
|
960
|
+
}
|
|
961
|
+
})
|
|
962
|
+
return
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
vo.deleteItems(sp.rows)
|
|
966
|
+
|
|
967
|
+
},
|
|
968
|
+
|
|
969
|
+
deleteItems: function(itemsDel) {
|
|
970
|
+
// console.log('method deleteItems')
|
|
971
|
+
|
|
972
|
+
let vo = this
|
|
973
|
+
|
|
806
974
|
//cloneDeep
|
|
807
975
|
let rows = get(vo, 'opt.rows', [])
|
|
808
976
|
|
|
@@ -810,7 +978,7 @@ export default {
|
|
|
810
978
|
rows = cloneDeep(rows)
|
|
811
979
|
|
|
812
980
|
//filter
|
|
813
|
-
each(
|
|
981
|
+
each(itemsDel, (v) => {
|
|
814
982
|
// console.log('v', v)
|
|
815
983
|
let id = get(v, 'data.id', '')
|
|
816
984
|
if (!isestr(id)) {
|
|
@@ -856,8 +1024,9 @@ export default {
|
|
|
856
1024
|
return
|
|
857
1025
|
}
|
|
858
1026
|
|
|
859
|
-
//rows
|
|
1027
|
+
//rows, baseline: 與 rows 同一刻(同步)擷取——兩者於 changeParams 同一處同時產生, 之後確認框期間若推播重建清單, 仍帶「草稿所依據之那一版」之基準(F-26), 不由最新 store 重算
|
|
860
1028
|
let rows = get(vo, 'opt.rows', [])
|
|
1029
|
+
let baseline = cloneDeep(vo.baseline)
|
|
861
1030
|
|
|
862
1031
|
//check
|
|
863
1032
|
if (size(rows) === 0) {
|
|
@@ -866,20 +1035,70 @@ export default {
|
|
|
866
1035
|
return
|
|
867
1036
|
}
|
|
868
1037
|
|
|
1038
|
+
//外部列保護(啟用時): 外部列之身分/結構欄(含 id)以逐列唯讀與刪除過濾擋下, 但貼上(Ctrl+V)等路徑可繞過逐列唯讀, 送出前再比對一次(基準為 store 之 targets);
|
|
1039
|
+
//有違規即不送出並提示(伺服端同樣整次拒收 saveExternalRowChanged); 於開 loading 之前, 訊息框前釋放按鈕鎖
|
|
1040
|
+
if (vo.isLockExternal && getExternalEdits({ keyTable: 'targets', keys: vo.$ds.targets.keys, rowsNew: rows, rowsOld: vo.baseRows }) !== null) {
|
|
1041
|
+
unlockBtn()
|
|
1042
|
+
await vo.$dg.showCheckYes(vo.$t('saveExternalRowChanged'), { type: 'warning' })
|
|
1043
|
+
return
|
|
1044
|
+
}
|
|
1045
|
+
|
|
869
1046
|
//新增列之 transient 欄位 _isNew(addItem / copyItem 標記)隨列送後端: 後端據以拒絕「標為新增但 id 已存在」之列
|
|
870
1047
|
//(同一包重送時不把剛建立之列當修改覆寫, 回 saveNewRowExists), 檢查後由 ltdtmapping 依 schema 取欄時剝除不入庫 (ADR-025);
|
|
871
1048
|
//本表 id 可就地改寫: 新增列之 id 改成本次刪除(或改掉 id)之既有標的者, 淨效果為修改該標的, 送出前改為一般列並沿用其建立者 / 建立時間,
|
|
872
1049
|
//否則被後端當重送擋下(重新整理後重做仍被擋) (見 reuseDeletedIds)
|
|
873
|
-
let rowsSend = reuseDeletedIds(rows, vo.
|
|
1050
|
+
let rowsSend = reuseDeletedIds(rows, vo.baseRows)
|
|
1051
|
+
|
|
1052
|
+
//2) 引用連帶預覽(ADR-036、R-REF-CASCADE-01; 與伺服端同一純函數 planRefChanges, 基準為 store 之 targets/pemis, 以實際送出之列 rowsSend 規劃, 筆數以伺服端實際為準):
|
|
1053
|
+
//標的 id 即名稱, 可就地改寫, 改 id 對伺服端等同刪舊增新(不級聯), 引用該標的之 pemis.crules 項目於同一次寫入內被連帶清理(刪除亦同); 有權限之規則將被清理時先確認,
|
|
1054
|
+
//確認框文字之判定見 src/js/getCascadePreview.mjs(標的頁只確認、不改成功訊息)
|
|
1055
|
+
let pv = getSavePreview({ kind: 'Target', rowsOld: vo.baseRows, rowsNew: rowsSend, holdersOld: vo.pemis, $t: (k) => vo.$t(k) })
|
|
1056
|
+
|
|
1057
|
+
//新增撞懸空名(F-23/F-26 原則 7; src/js/getAddImpact.mjs): 本次新增之標的 id(含就地改寫成新 id 者)查無既有同名實體, 而仍有啟用中之權限 crules 規則指向該 id——新增後該規則即對新標的生效,
|
|
1058
|
+
//須明確同意才送出; 以實際送出之列 rowsSend 規劃(同上), 與預覽同於開 loading 之前同步計算(基準為 store 之 targets/pemis), 無懸空引用時不彈框; 與刪除/改 id 預覽依序(先預覽、再新增確認), 各自只在需要時彈出
|
|
1059
|
+
let addText = getAddConfirmText(getAddImpact({ keyTable: 'targets', rowsOld: vo.baseRows, rowsNew: rowsSend, holders: vo.pemis }), (k) => vo.$t(k), 'Target')
|
|
1060
|
+
|
|
1061
|
+
if (pv.confirmText !== '') {
|
|
1062
|
+
|
|
1063
|
+
//先開確認框之流程: 開框前 unlockBtn() 釋放儲存鈕之 promiseUnlock 鎖(同上, 按鈕鎖只涵蓋請求期間); 流程狀態仍由 runSubmit 保持至本流程結束,
|
|
1064
|
+
//確認框與後續請求期間之重入(焦點仍在儲存鈕時 Enter)皆被略過 (ADR-025)
|
|
1065
|
+
unlockBtn()
|
|
1066
|
+
let isGo = await vo.$dg.showCheckYesNo(pv.confirmText)
|
|
1067
|
+
.then(() => true)
|
|
1068
|
+
.catch(() => false)
|
|
1069
|
+
|
|
1070
|
+
//選否(或確認框被取代而視同選否): 中止儲存, 不送出; 尚未開 loading, 資料與 isModified 保留, 由 runSubmit 於回傳後結束流程
|
|
1071
|
+
if (!isGo) {
|
|
1072
|
+
return
|
|
1073
|
+
}
|
|
874
1074
|
|
|
875
|
-
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
//新增撞懸空名之確認(同上之鎖釋放與選否中止; 單一框、單一問句)
|
|
1078
|
+
if (addText !== '') {
|
|
1079
|
+
unlockBtn()
|
|
1080
|
+
let isGoAdd = await vo.$dg.showCheckYesNo(addText)
|
|
1081
|
+
.then(() => true)
|
|
1082
|
+
.catch(() => false)
|
|
1083
|
+
if (!isGoAdd) {
|
|
1084
|
+
return
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
//3) 確定打 API 才開 loading
|
|
876
1089
|
vo.$ui.updateLoading(true)
|
|
877
1090
|
|
|
878
|
-
//
|
|
1091
|
+
//4) updateTargets, 各自 catch + 旗標短路; 第 2 參 { baseline } 為載入基準(伺服端於表鎖內比對本次將寫入之列是否仍與草稿所依據之版本相同, 否則整次拒收 saveStale)
|
|
879
1092
|
let okSave = false
|
|
880
|
-
|
|
881
|
-
|
|
1093
|
+
vo.saveMark.saving = true //自己之存檔請求進行中: 此期間之推播不是他人更新(changeParams 的 planDraftSync 回 'keepQuiet'); 回應(成功或失敗)一到即歸零
|
|
1094
|
+
await vo.$fapi.updateTargets(rowsSend, { baseline })
|
|
1095
|
+
.then(() => {
|
|
1096
|
+
okSave = true
|
|
1097
|
+
vo.saveMark.saving = false
|
|
1098
|
+
})
|
|
882
1099
|
.catch(async (err) => {
|
|
1100
|
+
vo.saveMark.saving = false
|
|
1101
|
+
vo.staleNotice = isDraftStale({ keys: vo.$ds.targets.keys, rows: vo.targets, baseline: vo.syncMark.baseline }) //失敗(含 saveStale 被拒): 請求進行中被安靜保留之推播, 此時依目前 store 重算過期提示(同 changeParams 之保留分支)
|
|
883
1102
|
vo.$ui.updateLoading(false) //showCheckYes 前關 loading(modal 阻斷期間避免 loading 疊在底下)
|
|
884
1103
|
await vo.$dg.showCheckYes(`${vo.$t('targetSaveTargetsFail')}: ${vo.$tErr(err)}`)
|
|
885
1104
|
})
|
|
@@ -890,7 +1109,7 @@ export default {
|
|
|
890
1109
|
|
|
891
1110
|
//alert(showCheckYes 前關 loading,避免 modal 等待期間 loading 疊在底下)
|
|
892
1111
|
vo.$ui.updateLoading(false)
|
|
893
|
-
await vo.$dg.showCheckYes(vo.$t('targetSaveTargetsSuccess'), { type: 'success' })
|
|
1112
|
+
await vo.$dg.showCheckYes(vo.$t('targetSaveTargetsSuccess'), { type: 'success' }) //標的頁之成功訊息不變(被清理之權限數已於確認框告知)
|
|
894
1113
|
|
|
895
1114
|
}
|
|
896
1115
|
|
|
@@ -903,6 +1122,9 @@ export default {
|
|
|
903
1122
|
})
|
|
904
1123
|
.finally(() => {
|
|
905
1124
|
|
|
1125
|
+
//非預期例外時亦歸零, 避免之後之推播永遠被當成自己存檔所致
|
|
1126
|
+
vo.saveMark.saving = false
|
|
1127
|
+
|
|
906
1128
|
//hide loading
|
|
907
1129
|
vo.$ui.updateLoading(false)
|
|
908
1130
|
|
|
@@ -916,4 +1138,18 @@ export default {
|
|
|
916
1138
|
</script>
|
|
917
1139
|
|
|
918
1140
|
<style scoped>
|
|
1141
|
+
/* 「資料已被更新」橫幅之重新載入鈕: 底色 #fff(同工具鈕底)、hover #f2f2f2(同工具鈕 hover)、邊線 #ddd(同橫幅邊線)、文字 #444(同橫幅文字), 與橫幅同字級 */
|
|
1142
|
+
.staleReloadBtn {
|
|
1143
|
+
margin-left: 8px;
|
|
1144
|
+
padding: 1px 10px;
|
|
1145
|
+
font-size: 0.8rem;
|
|
1146
|
+
color: #444;
|
|
1147
|
+
background: #fff;
|
|
1148
|
+
border: 1px solid #ddd;
|
|
1149
|
+
border-radius: 4px;
|
|
1150
|
+
cursor: pointer;
|
|
1151
|
+
}
|
|
1152
|
+
.staleReloadBtn:hover {
|
|
1153
|
+
background: #f2f2f2;
|
|
1154
|
+
}
|
|
919
1155
|
</style>
|