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="`users-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="users-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="users-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==='email'">
|
|
@@ -224,15 +253,25 @@
|
|
|
224
253
|
<span style="color:#F57C00;">{{ props.value }}</span>
|
|
225
254
|
<img style="vertical-align:sub; width:16px; height:16px;" :src="$ui.getIcon('warning')" />
|
|
226
255
|
</span>
|
|
256
|
+
<span v-else-if="warnItemsByEmail[props.value]" :title="warnItemsByEmail[props.value]">
|
|
257
|
+
<img style="vertical-align:sub; width:16px; height:16px; margin-right:4px;" :src="$ui.getIcon('warning')" />
|
|
258
|
+
<span style="color:#F57C00;">{{ props.value }}</span>
|
|
259
|
+
</span>
|
|
227
260
|
<span v-else>{{ props.value }}</span>
|
|
228
261
|
</template>
|
|
229
262
|
<template v-else-if="props.key==='cgrups'">
|
|
230
|
-
<div @click.stop.prevent @mousedown.stop.prevent>
|
|
263
|
+
<div v-if="warnCgrupsById[props.row.id]" style="display:flex; align-items:center; gap:4px;" @click.stop.prevent @mousedown.stop.prevent>
|
|
264
|
+
<span :title="warnCgrupsById[props.row.id]" style="display:flex; align-items:center; flex:0 0 auto;">
|
|
265
|
+
<img style="vertical-align:sub; width:16px; height:16px;" :src="$ui.getIcon('warning')" />
|
|
266
|
+
</span>
|
|
267
|
+
<button style="flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" @click="$dg.showVeCgrupsById(props.row.id)">{{ getCgrupsText(props.value) }}</button>
|
|
268
|
+
</div>
|
|
269
|
+
<div v-else @click.stop.prevent @mousedown.stop.prevent>
|
|
231
270
|
<button style="width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" @click="$dg.showVeCgrupsById(props.row.id)">{{ getCgrupsText(props.value) }}</button>
|
|
232
271
|
</div>
|
|
233
272
|
</template>
|
|
234
|
-
<input v-else-if="props.key==='isAdmin'" type="checkbox" :checked="props.value === 'y'" @click="toggleItemIsAdminById(props.row.id)" :disabled="!isEditableIdentity" />
|
|
235
|
-
<input v-else-if="props.key==='isActive'" type="checkbox" :checked="props.value === 'y'" @click="toggleItemIsActiveById(props.row.id)" :disabled="!isEditableIdentity" />
|
|
273
|
+
<input v-else-if="props.key==='isAdmin'" type="checkbox" :checked="props.value === 'y'" @click="toggleItemIsAdminById(props.row.id)" :disabled="!isEditableIdentity || isLockedRow(props.row)" />
|
|
274
|
+
<input v-else-if="props.key==='isActive'" type="checkbox" :checked="props.value === 'y'" @click="toggleItemIsActiveById(props.row.id)" :disabled="!isEditableIdentity || isLockedRow(props.row)" />
|
|
236
275
|
<span v-else>{{ props.value }}</span>
|
|
237
276
|
</template>
|
|
238
277
|
</WAggridVue>
|
|
@@ -251,7 +290,7 @@
|
|
|
251
290
|
</template>
|
|
252
291
|
|
|
253
292
|
<script>
|
|
254
|
-
import { mdiAccountGroupOutline, mdiVectorPolylinePlus, mdiCheckboxMarkedCircle, mdiCloudUploadOutline, mdiTrashCanOutline, mdiTableHeadersEye, mdiPlus, mdiPencilOutline, mdiContentCopy } from '@mdi/js/mdi.js'
|
|
293
|
+
import { mdiAccountGroupOutline, mdiVectorPolylinePlus, mdiCheckboxMarkedCircle, mdiCloudUploadOutline, mdiTrashCanOutline, mdiTableHeadersEye, mdiPlus, mdiPencilOutline, mdiContentCopy, mdiLock } from '@mdi/js/mdi.js'
|
|
255
294
|
import JSON5 from 'json5'
|
|
256
295
|
import get from 'lodash-es/get.js'
|
|
257
296
|
import set from 'lodash-es/set.js'
|
|
@@ -260,7 +299,6 @@ import size from 'lodash-es/size.js'
|
|
|
260
299
|
import filter from 'lodash-es/filter.js'
|
|
261
300
|
import sortBy from 'lodash-es/sortBy.js'
|
|
262
301
|
import cloneDeep from 'lodash-es/cloneDeep.js'
|
|
263
|
-
import haskey from 'wsemi/src/haskey.mjs'
|
|
264
302
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
265
303
|
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
266
304
|
import isnum from 'wsemi/src/isnum.mjs'
|
|
@@ -268,6 +306,13 @@ import isEmail from 'wsemi/src/isEmail.mjs'
|
|
|
268
306
|
import cdbl from 'wsemi/src/cdbl.mjs'
|
|
269
307
|
import arrPull from 'wsemi/src/arrPull.mjs'
|
|
270
308
|
import eachOwn from '../js/eachOwn.mjs'
|
|
309
|
+
import cvKpToObj from '../js/cvKpToObj.mjs'
|
|
310
|
+
import { getRefWarns, getRefWarnText, fmtWarn } from '../js/getAuthWarn.mjs'
|
|
311
|
+
import { getNameErrs, mapNameKp } from '../js/getNameErrs.mjs'
|
|
312
|
+
import { isExternalRow } from '../js/externalRows.mjs'
|
|
313
|
+
import { makeBaseline } from '../js/rowDigest.mjs'
|
|
314
|
+
import { refsChanged, planDraftSync, isDraftStale } from '../js/draftSync.mjs'
|
|
315
|
+
import { fmtVars, getBannerLines, splitCheckedRows, getExternalEdits } from '../js/externalUi.mjs'
|
|
271
316
|
import WIcon from 'w-component-vue/src/components/WIcon.vue'
|
|
272
317
|
import WSwitch from 'w-component-vue/src/components/WSwitch.vue'
|
|
273
318
|
import WButtonCircle from 'w-component-vue/src/components/WButtonCircle.vue'
|
|
@@ -302,6 +347,7 @@ export default {
|
|
|
302
347
|
mdiPlus,
|
|
303
348
|
mdiPencilOutline,
|
|
304
349
|
mdiContentCopy,
|
|
350
|
+
mdiLock,
|
|
305
351
|
|
|
306
352
|
panelHeight: 100,
|
|
307
353
|
headHeight: 100,
|
|
@@ -360,11 +406,22 @@ export default {
|
|
|
360
406
|
widthUsersDescription: null,
|
|
361
407
|
|
|
362
408
|
items: [],
|
|
409
|
+
baseline: [], //載入基準([[id, 內容指紋]]): 與 items 於 changeParams 同一處由同一份來源列產生(草稿所依據之版本), 存檔時隨 RPC 送伺服端比對是否過期(F-26, R-BASELINE-01); 不得於存檔當下由最新 store 重算
|
|
410
|
+
baseRows: [], //草稿所依據之那份 store 列(與 baseline 同時產生): 存檔前之預覽、外部列守門以它為「舊」, 不用已變新之 store(有未存編輯時 store 可能已被推播更新, 見 draftSync)
|
|
411
|
+
staleNotice: false, //草稿所依據之整表已被他人/外部同步更新(R-PRINCIPLE-00 原則 7): true 時顯示「資料已被更新」橫幅與重新載入鈕
|
|
412
|
+
syncTick: 0, //重新載入觸發器(reloadDraft 遞增): changeParams 讀它而於重算時於 computed 內重建草稿, 使 genOpt 讀取之反應式來源(語系等)的依賴重新收集——保留草稿(keep)時 changeParams 不呼叫 genOpt, 依賴會收縮, 若在 computed 外直接重建則不會恢復(外審 sol 第 4 點)
|
|
363
413
|
itemsCheck: [],
|
|
364
414
|
opt: null,
|
|
365
415
|
|
|
366
416
|
}
|
|
367
417
|
},
|
|
418
|
+
created: function() {
|
|
419
|
+
//syncMark(非響應式; changeParams 讀它而不因它建立響應式依賴, 見 draftSync.planDraftSync): refs/editable 為上次建立草稿時之各表 store 參照與編輯模式, dirty 為未存編輯旗標(isModified)之鏡像(由 watch 維護),
|
|
420
|
+
//baseline 為草稿載入時之基準之副本(供保留草稿時判定是否已過期). 不讓 changeParams 依賴 isModified/baseline: 旗標切換或重新載入不得觸發它重算(其重算=重建草稿, 見 draftSync)
|
|
421
|
+
this.syncMark = { refs: null, editable: undefined, dirty: false, baseline: [], tick: 0 }
|
|
422
|
+
//saveMark(非響應式, 同 syncMark 之理由): saving 為「自己之存檔請求進行中」(送出前設真, 回應〔成功或失敗〕一到歸零)——此期間伺服端寫入所致之推播若先於回應抵達, 不是他人更新(planDraftSync 回 'keepQuiet': 保留草稿但不顯示過期橫幅; run12)
|
|
423
|
+
this.saveMark = { saving: false }
|
|
424
|
+
},
|
|
368
425
|
mounted: function() {
|
|
369
426
|
// console.log('mounted')
|
|
370
427
|
|
|
@@ -398,6 +455,17 @@ export default {
|
|
|
398
455
|
}
|
|
399
456
|
|
|
400
457
|
},
|
|
458
|
+
watch: {
|
|
459
|
+
//未存編輯旗標: 維護 syncMark.dirty 鏡像(changeParams 以之判斷「有未存編輯」而不依賴旗標本身). 旗標歸零(已儲存或已放棄)而 store 於保留草稿期間已變新者, 立即依最新 store 重建
|
|
460
|
+
//(儲存成功之推播若先於儲存回應抵達, 其時有未存編輯而被保留; 回應回來旗標歸零時須補上這次重建, 否則畫面停在儲存前之草稿而基準已過期)
|
|
461
|
+
isModified: function(b) {
|
|
462
|
+
let vo = this
|
|
463
|
+
vo.syncMark.dirty = b === true
|
|
464
|
+
if (b !== true && vo.syncMark.refs !== null && refsChanged(vo.syncMark.refs, vo.curRefs())) {
|
|
465
|
+
vo.reloadDraft()
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
},
|
|
401
469
|
computed: {
|
|
402
470
|
|
|
403
471
|
//isEditableIdentity, 使用者身分(name/email/description/from/isAdmin/isActive/排序)與新增複製刪除是否可編;
|
|
@@ -441,6 +509,22 @@ export default {
|
|
|
441
509
|
return rs
|
|
442
510
|
},
|
|
443
511
|
|
|
512
|
+
lockExternalRows: function() {
|
|
513
|
+
//後台是否不得改外部列(from 為非空字串者; 伺服端 opt.lockExternalRows 經 getWebInfor 帶來, 'y'/'n'; 舊伺服端無此鍵視為未啟用)
|
|
514
|
+
return get(this, 'webInfor.lockExternalRows', '')
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
isLockExternal: function() {
|
|
518
|
+
return this.lockExternalRows === 'y'
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
bannerLines: function() {
|
|
522
|
+
let vo = this
|
|
523
|
+
|
|
524
|
+
//清單上方橫幅各行: 外部列提示(啟用保護且有外部列)+本表之外部同步警示(users 無同名異 id 之偵測, 通常無); 皆無為空陣列, 不渲染任何節點
|
|
525
|
+
return getBannerLines({ keyTable: 'users', kind: 'User', rows: vo.users, lock: vo.isLockExternal, syncWarns: get(vo, 'webInfor.syncWarns', []), $t: (k) => vo.$t(k) })
|
|
526
|
+
},
|
|
527
|
+
|
|
444
528
|
changeParams: function() {
|
|
445
529
|
// console.log('computed changeParams')
|
|
446
530
|
|
|
@@ -448,18 +532,22 @@ export default {
|
|
|
448
532
|
|
|
449
533
|
//trigger
|
|
450
534
|
let isEditable = vo.isEditable
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
535
|
+
let rows = vo.users
|
|
536
|
+
|
|
537
|
+
//草稿同步(R-PRINCIPLE-00 原則 7, draftSync.planDraftSync): store 變新(推播)時, 無未存編輯 → 重建 items 與基準(同修改前); 有未存編輯 → 保留草稿與其載入時之基準, 不重建, 只判定是否提示「資料已被更新」
|
|
538
|
+
let act = planDraftSync({
|
|
539
|
+
first: vo.syncMark.refs === null || vo.syncMark.tick !== vo.syncTick, //首次載入或使用者按了「重新載入」(syncTick 遞增)
|
|
540
|
+
editChanged: vo.syncMark.editable !== isEditable,
|
|
541
|
+
storeChanged: refsChanged(vo.syncMark.refs, [rows]),
|
|
542
|
+
hasDraft: () => vo.syncMark.dirty === true,
|
|
543
|
+
saving: () => vo.saveMark.saving === true, //自己之存檔請求進行中所致之推播 → 'keepQuiet'(不改過期提示)
|
|
544
|
+
})
|
|
545
|
+
if (act === 'rebuild') {
|
|
546
|
+
vo.rebuildDraft(rows, isEditable)
|
|
547
|
+
}
|
|
548
|
+
else if (act === 'keep') {
|
|
549
|
+
vo.staleNotice = isDraftStale({ keys: vo.$ds.users.keys, rows, baseline: vo.syncMark.baseline })
|
|
550
|
+
}
|
|
463
551
|
|
|
464
552
|
return ''
|
|
465
553
|
},
|
|
@@ -492,93 +580,83 @@ export default {
|
|
|
492
580
|
return b
|
|
493
581
|
},
|
|
494
582
|
|
|
495
|
-
|
|
583
|
+
nameErrs: function() {
|
|
496
584
|
let vo = this
|
|
497
585
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
let kpName = Object.create(null)
|
|
504
|
-
each(rows, (v, k) => {
|
|
505
|
-
|
|
506
|
-
//name
|
|
507
|
-
let name = get(v, 'name', '')
|
|
508
|
-
|
|
509
|
-
//check
|
|
510
|
-
if (!isestr(name)) {
|
|
511
|
-
|
|
512
|
-
//kpErr
|
|
513
|
-
kpErr[name] = vo.$t(`userNameEmpty`)
|
|
586
|
+
//只擋「本次造成或觸及」者(R-PRINCIPLE-00 g、h, R-NAME-UNIQ-02): 本次新增或改成空值/撞名之列擋存; 庫內既有的歷史同名/空值(僅外部同步可造成)不擋、改為警告圖示;
|
|
587
|
+
//重複之判準與伺服端同一份(findTouchedDup), 基準為 store 之 users(最近一次由伺服端載入的列). mappingBy 前端不知(伺服端選項), 前端一律採「只擋本次造成或觸及」,
|
|
588
|
+
//伺服端依 mappingBy 另有更嚴之檢查, 被擋時以伺服端回的錯誤鍵經 showCheckYes 顯示
|
|
589
|
+
return getNameErrs(get(vo, 'opt.rows', []), vo.baseRows, 'name', { badScope: 'touched' })
|
|
590
|
+
},
|
|
514
591
|
|
|
515
|
-
|
|
516
|
-
|
|
592
|
+
emailErrs: function() {
|
|
593
|
+
let vo = this
|
|
517
594
|
|
|
518
|
-
|
|
519
|
-
|
|
595
|
+
//同 nameErrs; 另 email 須合格式(isEmail)
|
|
596
|
+
return getNameErrs(get(vo, 'opt.rows', []), vo.baseRows, 'email', { badScope: 'touched', isValid: isEmail })
|
|
597
|
+
},
|
|
520
598
|
|
|
521
|
-
|
|
522
|
-
|
|
599
|
+
errItemsByName: function() {
|
|
600
|
+
let vo = this
|
|
523
601
|
|
|
524
|
-
|
|
602
|
+
//擋存者(本次造成或觸及之空名稱/重複): 既有呈現(橘色名稱+圖示), 決定 isError;
|
|
603
|
+
//重複者之一為外部列(from 非空)時, 提示改為「外部來源 X 已使用此名稱」(dupExternal; 外部列由外部擁有, 須改自己之名稱)
|
|
604
|
+
return mapNameKp(vo.nameErrs.errBlock, (kind, name) => {
|
|
605
|
+
if (kind === 'dup' && name in vo.nameErrs.dupExternal) {
|
|
606
|
+
return fmtVars(vo.$t('nameUsedByExternal'), { name, from: vo.nameErrs.dupExternal[name] })
|
|
525
607
|
}
|
|
526
|
-
|
|
527
|
-
//kpName
|
|
528
|
-
kpName[name] = true
|
|
529
|
-
|
|
608
|
+
return vo.$t(kind === 'empty' ? `userNameEmpty` : `userNameDuplicate`)
|
|
530
609
|
})
|
|
531
|
-
|
|
532
|
-
return kpErr
|
|
533
610
|
},
|
|
534
611
|
|
|
535
612
|
errItemsByEmail: function() {
|
|
536
613
|
let vo = this
|
|
537
614
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
let kpEmail = Object.create(null)
|
|
544
|
-
each(rows, (v, k) => {
|
|
545
|
-
|
|
546
|
-
//email
|
|
547
|
-
let email = get(v, 'email', '')
|
|
548
|
-
|
|
549
|
-
//check
|
|
550
|
-
if (!isestr(email)) {
|
|
551
|
-
|
|
552
|
-
//kpErr
|
|
553
|
-
kpErr[email] = vo.$t(`userEmailEmpty`)
|
|
554
|
-
|
|
555
|
-
return true //跳出換下一個
|
|
556
|
-
}
|
|
615
|
+
//擋存者(本次造成或觸及之空值/格式錯誤/重複): 既有呈現, 決定 isError
|
|
616
|
+
return mapNameKp(vo.emailErrs.errBlock, (kind) => {
|
|
617
|
+
return vo.$t(kind === 'empty' ? `userEmailEmpty` : (kind === 'invalid' ? `userEmailError` : `userEmailDuplicate`))
|
|
618
|
+
})
|
|
619
|
+
},
|
|
557
620
|
|
|
558
|
-
|
|
559
|
-
|
|
621
|
+
warnItemsByName: function() {
|
|
622
|
+
let vo = this
|
|
560
623
|
|
|
561
|
-
|
|
562
|
-
|
|
624
|
+
//歷史同名/歷史空名稱(不擋存, 與 errItemsByName 互斥): 同樣顯示橘色名稱+圖示, tooltip 說明為既有資料
|
|
625
|
+
let kp = mapNameKp(vo.nameErrs.warnHist, (n) => {
|
|
626
|
+
return fmtWarn(vo.$t('warnUserNameHistDup'), { n })
|
|
627
|
+
})
|
|
628
|
+
return Object.assign(kp, mapNameKp(vo.nameErrs.warnHistBad, () => {
|
|
629
|
+
return vo.$t('warnUserNameHistEmpty')
|
|
630
|
+
}))
|
|
631
|
+
},
|
|
563
632
|
|
|
564
|
-
|
|
565
|
-
|
|
633
|
+
warnItemsByEmail: function() {
|
|
634
|
+
let vo = this
|
|
566
635
|
|
|
567
|
-
|
|
568
|
-
|
|
636
|
+
//歷史同 email/歷史空值/歷史格式錯誤(不擋存, 與 errItemsByEmail 互斥)
|
|
637
|
+
let kp = mapNameKp(vo.emailErrs.warnHist, (n) => {
|
|
638
|
+
return fmtWarn(vo.$t('warnUserEmailHistDup'), { n })
|
|
639
|
+
})
|
|
640
|
+
return Object.assign(kp, mapNameKp(vo.emailErrs.warnHistBad, (kind) => {
|
|
641
|
+
return vo.$t(kind === 'empty' ? 'warnUserEmailHistEmpty' : 'warnUserEmailHistInvalid')
|
|
642
|
+
}))
|
|
643
|
+
},
|
|
569
644
|
|
|
570
|
-
|
|
571
|
-
|
|
645
|
+
warnCgrupsById: function() {
|
|
646
|
+
let vo = this
|
|
572
647
|
|
|
573
|
-
|
|
648
|
+
//cgrups 引用清單之衝突提示(R-PRINCIPLE-00 b、c、d、e、f): 列 id → tooltip 文字, 只含有衝突之列(無衝突之列不入表, 畫面維持原樣);
|
|
649
|
+
//引用比對的被引用表為 store 之 grups(群組頁已儲存之列), 判準與伺服端 getUserRules 同一份(findRefRows)
|
|
650
|
+
let kp = Object.create(null)
|
|
651
|
+
let rows = get(vo, 'opt.rows', [])
|
|
652
|
+
each(rows, (r) => {
|
|
653
|
+
let res = getRefWarns(get(r, 'cgrups', ''), vo.grups)
|
|
654
|
+
if (res.hasWarn) {
|
|
655
|
+
kp[get(r, 'id', '')] = getRefWarnText(res, (k) => vo.$t(k), 'warnKindGrup')
|
|
574
656
|
}
|
|
575
|
-
|
|
576
|
-
//kpEmail
|
|
577
|
-
kpEmail[email] = true
|
|
578
|
-
|
|
579
657
|
})
|
|
580
658
|
|
|
581
|
-
return
|
|
659
|
+
return kp
|
|
582
660
|
},
|
|
583
661
|
|
|
584
662
|
isError: function() {
|
|
@@ -654,6 +732,75 @@ export default {
|
|
|
654
732
|
return head
|
|
655
733
|
},
|
|
656
734
|
|
|
735
|
+
isLockedRow: function(row) {
|
|
736
|
+
let vo = this
|
|
737
|
+
|
|
738
|
+
//啟用外部列保護且該列為外部列(from 非空)
|
|
739
|
+
return vo.isLockExternal && isExternalRow(row)
|
|
740
|
+
},
|
|
741
|
+
|
|
742
|
+
lockOfRow: function(row) {
|
|
743
|
+
let vo = this
|
|
744
|
+
|
|
745
|
+
//啟用外部列保護且該列為外部列者回 [{ from }](首欄顯示鎖頭), 否則回空陣列(供 v-for 渲染 0 或 1 個節點: 無外部列時 DOM 同未啟用前, 不留 v-if 之註解節點)
|
|
746
|
+
if (vo.isLockedRow(row)) {
|
|
747
|
+
return [{ from: row.from }]
|
|
748
|
+
}
|
|
749
|
+
return []
|
|
750
|
+
},
|
|
751
|
+
|
|
752
|
+
getLockTitle: function(loc) {
|
|
753
|
+
let vo = this
|
|
754
|
+
|
|
755
|
+
return fmtVars(vo.$t('externalRowLocked'), { from: loc.from })
|
|
756
|
+
},
|
|
757
|
+
|
|
758
|
+
getBannerStyle: function(kind) {
|
|
759
|
+
|
|
760
|
+
//橫幅樣式沿用既有色票與間距: 與功能區同為 border-top:1px solid #ddd; 字級 0.8rem(同副標題/waitingData)、水平內距 15px(同 waitingData)、垂直內距 5px(同功能區 padding:5px);
|
|
761
|
+
//外部列提示為中性灰底(#f2f2f2, 同按鈕 hover 底)、文字 #444(同工具鈕圖示色); 外部同步警示為淡橘底(#FFE0B2, 同編輯模式開關之橫條色)搭既有警告圖示; 長來源名稱以 overflow-wrap:anywhere 折行, 窄螢幕不溢出
|
|
762
|
+
let bg = kind === 'warn' ? '#FFE0B2' : '#f2f2f2'
|
|
763
|
+
return `padding:5px 15px; border-top:1px solid #ddd; background:${bg}; color:#444; font-size:0.8rem; overflow-wrap:anywhere;`
|
|
764
|
+
},
|
|
765
|
+
|
|
766
|
+
rebuildDraft: function(rows, isEditable) {
|
|
767
|
+
let vo = this
|
|
768
|
+
|
|
769
|
+
//items
|
|
770
|
+
let items = cloneDeep(rows)
|
|
771
|
+
|
|
772
|
+
//baseline, 與 items 同一處由同一份來源列(items)計算: 草稿所依據之版本(F-26), 重建 items 時一併更新, 存檔時不重算
|
|
773
|
+
let baseline = makeBaseline(items, vo.$ds.users.keys)
|
|
774
|
+
|
|
775
|
+
//save; 重建即放棄未存編輯(isModified 歸零)與過期提示, syncMark 記下本次所依據之 store 參照與編輯模式
|
|
776
|
+
vo.items = items
|
|
777
|
+
vo.baseline = baseline
|
|
778
|
+
vo.baseRows = rows
|
|
779
|
+
vo.isModified = false
|
|
780
|
+
vo.staleNotice = false
|
|
781
|
+
vo.syncMark = { refs: [rows], editable: isEditable, dirty: false, baseline, tick: vo.syncTick }
|
|
782
|
+
|
|
783
|
+
//genOpt
|
|
784
|
+
vo.genOpt({ isEditable })
|
|
785
|
+
|
|
786
|
+
//firstLoading
|
|
787
|
+
vo.firstLoading = false
|
|
788
|
+
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
curRefs: function() {
|
|
792
|
+
//目前各來源 store 之參照(與 changeParams 之 refsChanged 同一組)
|
|
793
|
+
let vo = this
|
|
794
|
+
return [vo.users]
|
|
795
|
+
},
|
|
796
|
+
|
|
797
|
+
reloadDraft: function() {
|
|
798
|
+
//「重新載入」鈕(資料已被更新橫幅): 使用者明確放棄未存編輯, 以目前 store 重建草稿與基準. 不在此直接呼叫 rebuildDraft: 遞增 syncTick 使 changeParams 重算並於 computed 內重建,
|
|
799
|
+
//genOpt 之反應式依賴(語系等)才會重新收集(保留草稿期間 changeParams 未呼叫 genOpt, 依賴已收縮; 於 computed 外重建不會恢復, 重新載入後再切換語系表頭將停留舊值, 外審 sol 第 4 點)
|
|
800
|
+
let vo = this
|
|
801
|
+
vo.syncTick++
|
|
802
|
+
},
|
|
803
|
+
|
|
657
804
|
genOpt: function() {
|
|
658
805
|
// console.log('methods genOpt')
|
|
659
806
|
|
|
@@ -678,11 +825,14 @@ export default {
|
|
|
678
825
|
let kpRowDrag = {}
|
|
679
826
|
let kpHeadCheckBox = {}
|
|
680
827
|
if (vo.isEditableIdentity) { //身分欄位編輯/拖曳排序/列勾選(供複製刪除)皆屬身分編修, for:grups 時鎖定
|
|
828
|
+
|
|
829
|
+
//外部列(from 非空)之身分/結構欄逐列唯讀(啟用保護時以函數判定: w-aggrid-vue 之 setobj 原樣取 kpCellEditable[key] 指給 ag-grid colDef.editable, 其接受 (params) => boolean);
|
|
830
|
+
//isAdmin/isActive 為 cell-render 之核選方塊(以 :disabled 逐列鎖定); 指派欄(cgrups)走開窗按鈕, 不受影響; from 欄於所有列皆唯讀(外部同步所擁有, 後台新增列 from 恆為空)
|
|
831
|
+
let editable = vo.isLockExternal ? (params) => !isExternalRow(params.data) : true
|
|
681
832
|
kpCellEditable = {
|
|
682
|
-
'name':
|
|
683
|
-
'email':
|
|
684
|
-
'description':
|
|
685
|
-
'from': true,
|
|
833
|
+
'name': editable,
|
|
834
|
+
'email': editable,
|
|
835
|
+
'description': editable,
|
|
686
836
|
}
|
|
687
837
|
kpRowDrag = {
|
|
688
838
|
'name': true,
|
|
@@ -849,6 +999,12 @@ export default {
|
|
|
849
999
|
return
|
|
850
1000
|
}
|
|
851
1001
|
|
|
1002
|
+
//外部列(啟用保護時)之身分欄唯讀: 核選方塊已 :disabled, 此為防呆(不變更、不標修改)
|
|
1003
|
+
if (vo.isLockedRow(r)) {
|
|
1004
|
+
vo.refresh()
|
|
1005
|
+
return
|
|
1006
|
+
}
|
|
1007
|
+
|
|
852
1008
|
//v
|
|
853
1009
|
let _v = get(r, k, 'n')
|
|
854
1010
|
let v = _v === 'y' ? 'n' : 'y'
|
|
@@ -900,6 +1056,12 @@ export default {
|
|
|
900
1056
|
return
|
|
901
1057
|
}
|
|
902
1058
|
|
|
1059
|
+
//外部列(啟用保護時)之身分欄唯讀: 核選方塊已 :disabled, 此為防呆(不變更、不標修改)
|
|
1060
|
+
if (vo.isLockedRow(r)) {
|
|
1061
|
+
vo.refresh()
|
|
1062
|
+
return
|
|
1063
|
+
}
|
|
1064
|
+
|
|
903
1065
|
//v
|
|
904
1066
|
let _v = get(r, k, 'n')
|
|
905
1067
|
let v = _v === 'y' ? 'n' : 'y'
|
|
@@ -998,6 +1160,7 @@ export default {
|
|
|
998
1160
|
let nameOld = r.name
|
|
999
1161
|
let _r = vo.$ds.users.funNew()
|
|
1000
1162
|
r.id = _r.id //使用新建方式預先產生id避免重複
|
|
1163
|
+
r.from = '' //複製出之列為後台新增列, 不繼承來源(來源非空即外部列, 後台新增列 from 須為空, 否則伺服端 saveFromNotAllowed; 不論是否啟用外部列保護)
|
|
1001
1164
|
r.name = vo.$s.getNameNew(rows, 'name', nameOld, vo.$t('userCopyNameNew'))
|
|
1002
1165
|
r.userId = `{${vo.$t('userAddIdNew')}}`
|
|
1003
1166
|
r.timeCreate = `{${vo.$t('userAddIdNew')}}`
|
|
@@ -1034,6 +1197,28 @@ export default {
|
|
|
1034
1197
|
return
|
|
1035
1198
|
}
|
|
1036
1199
|
|
|
1200
|
+
//外部列保護(啟用時): 外部列(from 非空)不得刪除(伺服端 saveExternalRowChanged), 先過濾掉並以 showCheckYes 提示被略過之筆數(警告, 非 success), 使用者按確定後其餘照常;
|
|
1201
|
+
//全為外部列則不刪除任何列
|
|
1202
|
+
let sp = splitCheckedRows(vo.itemsCheck, vo.isLockExternal)
|
|
1203
|
+
if (sp.nBlocked > 0) {
|
|
1204
|
+
vo.$dg.showCheckYes(fmtVars(vo.$t('externalRowDelBlocked'), { n: sp.nBlocked }), { type: 'warning' })
|
|
1205
|
+
.then(() => {
|
|
1206
|
+
if (size(sp.rows) > 0) {
|
|
1207
|
+
vo.deleteItems(sp.rows)
|
|
1208
|
+
}
|
|
1209
|
+
})
|
|
1210
|
+
return
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
vo.deleteItems(sp.rows)
|
|
1214
|
+
|
|
1215
|
+
},
|
|
1216
|
+
|
|
1217
|
+
deleteItems: function(itemsDel) {
|
|
1218
|
+
// console.log('method deleteItems')
|
|
1219
|
+
|
|
1220
|
+
let vo = this
|
|
1221
|
+
|
|
1037
1222
|
//cloneDeep
|
|
1038
1223
|
let rows = get(vo, 'opt.rows', [])
|
|
1039
1224
|
|
|
@@ -1041,7 +1226,7 @@ export default {
|
|
|
1041
1226
|
rows = cloneDeep(rows)
|
|
1042
1227
|
|
|
1043
1228
|
//filter
|
|
1044
|
-
each(
|
|
1229
|
+
each(itemsDel, (v) => {
|
|
1045
1230
|
// console.log('v', v)
|
|
1046
1231
|
let id = get(v, 'data.id', '')
|
|
1047
1232
|
if (!isestr(id)) {
|
|
@@ -1074,7 +1259,7 @@ export default {
|
|
|
1074
1259
|
let kpGrup = {}
|
|
1075
1260
|
try {
|
|
1076
1261
|
// console.log('cgrups', cgrups)
|
|
1077
|
-
kpGrup = JSON5.parse(cgrups)
|
|
1262
|
+
kpGrup = cvKpToObj(JSON5.parse(cgrups), { hasMode: true }) //陣列形式經共用解碼(R-AUTH-LIST-01): 修前把陣列索引當群組名
|
|
1078
1263
|
}
|
|
1079
1264
|
catch (err) {}
|
|
1080
1265
|
|
|
@@ -1101,7 +1286,7 @@ export default {
|
|
|
1101
1286
|
if (n > 0) {
|
|
1102
1287
|
t = t.replace('{n}', n)
|
|
1103
1288
|
if (n === 1) {
|
|
1104
|
-
t = t.replace('{nms}', `(${vs[0]})`)
|
|
1289
|
+
t = t.replace('{nms}', `(${vs[0] === '' ? '""' : vs[0]})`) //空字串名稱(R-PRINCIPLE-00 f 照原樣保留)顯示為 "", 否則括號內看不到而像是消失
|
|
1105
1290
|
}
|
|
1106
1291
|
else {
|
|
1107
1292
|
t = t.replace('{nms}', '')
|
|
@@ -1128,11 +1313,9 @@ export default {
|
|
|
1128
1313
|
|
|
1129
1314
|
//find
|
|
1130
1315
|
let r = null
|
|
1131
|
-
|
|
1132
|
-
each(rows, (v, k) => {
|
|
1316
|
+
each(rows, (v) => {
|
|
1133
1317
|
if (get(v, 'id', '') === id) {
|
|
1134
1318
|
r = v
|
|
1135
|
-
kr = k
|
|
1136
1319
|
return false //跳出
|
|
1137
1320
|
}
|
|
1138
1321
|
})
|
|
@@ -1147,6 +1330,10 @@ export default {
|
|
|
1147
1330
|
// let kur = vo.$s.getUserRules(r, vo.grups, vo.pemis, vo.targets)
|
|
1148
1331
|
// // console.log('getUserRules kur', kur)
|
|
1149
1332
|
|
|
1333
|
+
//baseline0: 開啟編輯器當下之載入基準(整表, F-26、原則 7 表級比對, 外審 sol R1 與 run11 第 1 點): 編輯器開著時若他人/外部同步改了任何一列或增刪列, 而父頁當時無未存編輯, 推播會重建 items 與基準,
|
|
1334
|
+
//編輯器回傳之字串仍是舊版內容, 回填後存檔會以「新基準」證明過期內容新鮮(還原已被撤銷之授權, 或繞過同表別列已被改動之整表比對); 故回填前比對「草稿現所依據之那份表」與開框時之基準, 有異即放棄回填並提示重新載入.
|
|
1335
|
+
//父頁已有未存編輯時推播只保留草稿與其基準(baseRows 不變), 此比對不誤擋, 存檔時由伺服端表級比對拒收
|
|
1336
|
+
let baseline0 = cloneDeep(vo.baseline)
|
|
1150
1337
|
//showVeCgrups
|
|
1151
1338
|
vo.$dg.showVeCgrups({
|
|
1152
1339
|
isEditable: vo.isEditable,
|
|
@@ -1155,9 +1342,28 @@ export default {
|
|
|
1155
1342
|
.then((cgrups) => {
|
|
1156
1343
|
// console.log('cgrups', cgrups)
|
|
1157
1344
|
|
|
1345
|
+
//以 id 於「現在」之 opt.rows 重新找列(R-REL-PAIR-01): 對話框開啟期間 store 更新會重建 opt.rows(他人增刪、改 order), 開框時之列索引可能已指向另一列; 該列已不存在則提示並放棄回填
|
|
1346
|
+
let krNow = -1
|
|
1347
|
+
each(get(vo, 'opt.rows', []), (v, k) => {
|
|
1348
|
+
if (get(v, 'id', '') === id) {
|
|
1349
|
+
krNow = k
|
|
1350
|
+
return false //跳出
|
|
1351
|
+
}
|
|
1352
|
+
})
|
|
1353
|
+
if (krNow < 0) {
|
|
1354
|
+
vo.$alert(`${vo.$t('userEditCgrupsNoUserData')}`, { type: 'error' })
|
|
1355
|
+
return
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
//草稿所依據之表於編輯器開啟後已被改動(整表指紋與開框時之基準不同; 僅 order/伺服器管理欄不同不算): 放棄回填並提示重新載入(同伺服端 saveStale 之語意)
|
|
1359
|
+
if (isDraftStale({ keys: vo.$ds.users.keys, rows: vo.baseRows, baseline: baseline0 })) {
|
|
1360
|
+
vo.$alert(`${vo.$t('saveStale')}`, { type: 'warning' })
|
|
1361
|
+
return
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1158
1364
|
//set
|
|
1159
|
-
set(vo, `opt.rows[${
|
|
1160
|
-
// console.log('vo.opt.rows[
|
|
1365
|
+
set(vo, `opt.rows[${krNow}].cgrups`, cgrups)
|
|
1366
|
+
// console.log('vo.opt.rows[krNow]', cloneDeep(vo.opt.rows[krNow]))
|
|
1161
1367
|
|
|
1162
1368
|
//refresh
|
|
1163
1369
|
vo.refresh()
|
|
@@ -1192,8 +1398,9 @@ export default {
|
|
|
1192
1398
|
return
|
|
1193
1399
|
}
|
|
1194
1400
|
|
|
1195
|
-
//rows
|
|
1401
|
+
//rows, baseline: 與 rows 同一刻(同步)擷取——兩者於 changeParams 同一處同時產生, 之後若推播重建清單, 仍帶「草稿所依據之那一版」之基準(F-26), 不由最新 store 重算
|
|
1196
1402
|
let rows = get(vo, 'opt.rows', [])
|
|
1403
|
+
let baseline = cloneDeep(vo.baseline)
|
|
1197
1404
|
|
|
1198
1405
|
//check
|
|
1199
1406
|
if (size(rows) === 0) {
|
|
@@ -1202,6 +1409,14 @@ export default {
|
|
|
1202
1409
|
return
|
|
1203
1410
|
}
|
|
1204
1411
|
|
|
1412
|
+
//外部列保護(啟用時): 外部列之身分/結構欄以逐列唯讀與刪除過濾擋下, 但貼上(Ctrl+V)等路徑可繞過逐列唯讀, 送出前再比對一次(基準為 store 之 users);
|
|
1413
|
+
//有違規即不送出並提示(伺服端同樣整次拒收 saveExternalRowChanged); 於開 loading 之前, 訊息框前釋放按鈕鎖
|
|
1414
|
+
if (vo.isLockExternal && getExternalEdits({ keyTable: 'users', keys: vo.$ds.users.keys, rowsNew: rows, rowsOld: vo.baseRows }) !== null) {
|
|
1415
|
+
unlockBtn()
|
|
1416
|
+
await vo.$dg.showCheckYes(vo.$t('saveExternalRowChanged'), { type: 'warning' })
|
|
1417
|
+
return
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1205
1420
|
//新增列之 transient 欄位 _isNew(addItem / copyItem 標記)隨列送後端: 後端據以拒絕「標為新增但 id 已存在」之列
|
|
1206
1421
|
//(同一包重送時不把剛建立之列當修改覆寫, 回 saveNewRowExists), 檢查後由 ltdtmapping 依 schema 取欄時剝除不入庫 (ADR-025);
|
|
1207
1422
|
//modeEditUsers='for:grups' 時後端不採納新增列(mergePickKeysOnly), 亦不檢查
|
|
@@ -1209,11 +1424,17 @@ export default {
|
|
|
1209
1424
|
//2) 確定打 API 才開 loading
|
|
1210
1425
|
vo.$ui.updateLoading(true)
|
|
1211
1426
|
|
|
1212
|
-
//3) updateUsers, 各自 catch +
|
|
1427
|
+
//3) updateUsers, 各自 catch + 旗標短路; 第 2 參 { baseline } 為載入基準(伺服端於表鎖內比對本次將寫入之列是否仍與草稿所依據之版本相同, 否則整次拒收 saveStale)
|
|
1213
1428
|
let okSave = false
|
|
1214
|
-
|
|
1215
|
-
|
|
1429
|
+
vo.saveMark.saving = true //自己之存檔請求進行中: 此期間之推播不是他人更新(changeParams 的 planDraftSync 回 'keepQuiet'); 回應(成功或失敗)一到即歸零
|
|
1430
|
+
await vo.$fapi.updateUsers(rows, { baseline })
|
|
1431
|
+
.then(() => {
|
|
1432
|
+
okSave = true
|
|
1433
|
+
vo.saveMark.saving = false
|
|
1434
|
+
})
|
|
1216
1435
|
.catch(async (err) => {
|
|
1436
|
+
vo.saveMark.saving = false
|
|
1437
|
+
vo.staleNotice = isDraftStale({ keys: vo.$ds.users.keys, rows: vo.users, baseline: vo.syncMark.baseline }) //失敗(含 saveStale 被拒): 請求進行中被安靜保留之推播, 此時依目前 store 重算過期提示(同 changeParams 之保留分支)
|
|
1217
1438
|
vo.$ui.updateLoading(false) //showCheckYes 前關 loading(modal 阻斷期間避免 loading 疊在底下)
|
|
1218
1439
|
await vo.$dg.showCheckYes(`${vo.$t('userSaveUsersFail')}: ${vo.$tErr(err)}`)
|
|
1219
1440
|
})
|
|
@@ -1237,6 +1458,9 @@ export default {
|
|
|
1237
1458
|
})
|
|
1238
1459
|
.finally(() => {
|
|
1239
1460
|
|
|
1461
|
+
//非預期例外時亦歸零, 避免之後之推播永遠被當成自己存檔所致
|
|
1462
|
+
vo.saveMark.saving = false
|
|
1463
|
+
|
|
1240
1464
|
//hide loading
|
|
1241
1465
|
vo.$ui.updateLoading(false)
|
|
1242
1466
|
|
|
@@ -1250,4 +1474,18 @@ export default {
|
|
|
1250
1474
|
</script>
|
|
1251
1475
|
|
|
1252
1476
|
<style scoped>
|
|
1477
|
+
/* 「資料已被更新」橫幅之重新載入鈕: 底色 #fff(同工具鈕底)、hover #f2f2f2(同工具鈕 hover)、邊線 #ddd(同橫幅邊線)、文字 #444(同橫幅文字), 與橫幅同字級 */
|
|
1478
|
+
.staleReloadBtn {
|
|
1479
|
+
margin-left: 8px;
|
|
1480
|
+
padding: 1px 10px;
|
|
1481
|
+
font-size: 0.8rem;
|
|
1482
|
+
color: #444;
|
|
1483
|
+
background: #fff;
|
|
1484
|
+
border: 1px solid #ddd;
|
|
1485
|
+
border-radius: 4px;
|
|
1486
|
+
cursor: pointer;
|
|
1487
|
+
}
|
|
1488
|
+
.staleReloadBtn:hover {
|
|
1489
|
+
background: #f2f2f2;
|
|
1490
|
+
}
|
|
1253
1491
|
</style>
|