w-web-perm 1.1.5 → 1.1.7
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/dist/css/{chunk-vendors.80aa3ae8.css → chunk-vendors.3c580e72.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.6f82d8bd.js +1 -0
- package/dist/js/app.6f82d8bd.js +1 -0
- package/dist/js/{chunk-vendors.fbf771bd.js → chunk-vendors-legacy.00f159e8.js} +5 -5
- package/dist/js/{chunk-vendors-legacy.fbf771bd.js → chunk-vendors.00f159e8.js} +5 -5
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +3 -3
- package/docs/global.html +474 -2
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +12 -3
- package/docs/server_lockSave.mjs.html +115 -0
- package/docs/server_maskLog.mjs.html +2 -2
- package/docs/server_procCore.mjs.html +31 -5
- package/docs/server_wrapInjected.mjs.html +2 -2
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_js_legendFit.mjs.html +2 -2
- package/docs/src_plugins_reuseDeletedIds.mjs.html +136 -0
- package/docs/src_plugins_submitGuard.mjs.html +163 -0
- package/package.json +13 -13
- package/server/WWebPerm.mjs +10 -1
- package/server/lockSave.mjs +43 -0
- package/server/procCore.mjs +29 -3
- package/server/procLang.mjs +8 -0
- package/src/components/Common/CheckYes.vue +6 -0
- package/src/components/Common/CheckYesNo.vue +6 -0
- package/src/components/LayoutContentGrups.vue +30 -21
- package/src/components/LayoutContentPemis.vue +30 -21
- package/src/components/LayoutContentTargets.vue +36 -22
- package/src/components/LayoutContentUsers.vue +31 -21
- package/src/components/VeCgrups.vue +8 -1
- package/src/components/VeCpemis.vue +8 -1
- package/src/components/VeCrules.vue +8 -1
- package/src/components/VeGrupBlngUsers.vue +37 -31
- package/src/components/VePemiBlngGrups.vue +37 -31
- package/src/plugins/mUI.mjs +20 -0
- package/src/plugins/reuseDeletedIds.mjs +64 -0
- package/src/plugins/submitGuard.mjs +91 -0
- package/dist/js/app-legacy.833f9566.js +0 -1
- package/dist/js/app.833f9566.js +0 -1
- package/tableTags-web-perm.json +0 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>server/lockSave.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">server/lockSave.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
|
|
49
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 有副作用之寫入路徑(如清單儲存 updateUsersList / updateTokensList / updateIpsList)之雙擊防護(後端, ADR-025)
|
|
54
|
+
*
|
|
55
|
+
* 以「操作:操作者 id」於 wsemi cacheSt 原子占位執行 fn, 同一操作者之同一操作處理中再送出即 reject 錯誤 key
|
|
56
|
+
* (預設 'saveInProgress', 可以 opt.errKey 指定; 皆為 i18n key, 見 procLang); 不同操作者、不同操作互不影響;
|
|
57
|
+
* fn 之 reject 原樣 bubble; fn 結束(含失敗)即釋放.
|
|
58
|
+
* 第 2 次不排隊執行: 其內容與第 1 次相同, 排隊後再寫一次會把第 1 次剛建立之列當修改覆寫
|
|
59
|
+
* (依序重送另由各專案之資料層檢查擋, 如 SSO procCore.updateTabItems 之新增列檢查回 'saveNewRowExists').
|
|
60
|
+
* operatorId 可為操作者 id 或「操作者 id + 列識別」(只擋同一列之連發, 同批其他列不受影響).
|
|
61
|
+
*
|
|
62
|
+
* @param {Object} cst 輸入 wsemi cacheSt 實例
|
|
63
|
+
* @returns {Function} 回傳 async (op, operatorId, fn, opt = {}) => fn 之結果, opt.errKey 為占位衝突時 reject 之 key
|
|
64
|
+
*/
|
|
65
|
+
function createLockSave(cst) {
|
|
66
|
+
|
|
67
|
+
let lockSave = async (op, operatorId, fn, opt = {}) => {
|
|
68
|
+
|
|
69
|
+
//errKey
|
|
70
|
+
let errKey = get(opt, 'errKey', '')
|
|
71
|
+
if (!isestr(errKey)) {
|
|
72
|
+
errKey = 'saveInProgress'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let key = `${op}:${operatorId}`
|
|
76
|
+
return await cst.setWithFree([key], fn)
|
|
77
|
+
.catch((err) => {
|
|
78
|
+
//remap setWithFree 之占位衝突 → i18n key; fn 內之 reject 原樣 bubble
|
|
79
|
+
if (err === `setWithFree: key in use: ${key}`) {
|
|
80
|
+
return Promise.reject(errKey)
|
|
81
|
+
}
|
|
82
|
+
return Promise.reject(err)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return lockSave
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
export default createLockSave
|
|
91
|
+
</code></pre>
|
|
92
|
+
</article>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<br class="clear">
|
|
103
|
+
|
|
104
|
+
<footer>
|
|
105
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
106
|
+
</footer>
|
|
107
|
+
|
|
108
|
+
<script>prettyPrint();</script>
|
|
109
|
+
<script src="scripts/polyfill.js"></script>
|
|
110
|
+
<script src="scripts/linenumber.js"></script>
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -163,7 +163,7 @@ export {
|
|
|
163
163
|
<br class="clear">
|
|
164
164
|
|
|
165
165
|
<footer>
|
|
166
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun
|
|
166
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
167
167
|
</footer>
|
|
168
168
|
|
|
169
169
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -96,7 +96,7 @@ function mergePickKeysOnly(ltdtDb, rowsIn, keyDetect, pickKeys) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
function proc(woItems, procOrm, { srLog, kmx }) {
|
|
99
|
+
function proc(woItems, procOrm, { srLog, kmx, lockSave }) {
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
//updateTabItems
|
|
@@ -106,8 +106,21 @@ function proc(woItems, procOrm, { srLog, kmx }) {
|
|
|
106
106
|
//pickKeysOnly, 僅採納既有列之指定欄位變更(見 mergePickKeysOnly), 未給則整表 diff 寫入
|
|
107
107
|
let pickKeysOnly = get(opt, 'pickKeysOnly', null)
|
|
108
108
|
|
|
109
|
+
//idsNew: 前端標為新增(transient 欄位 _isNew === true, 清單頁 addItem / copyItem 標記)之列 id, 供下方比對既有列 (ADR-025);
|
|
110
|
+
//_isNew 不在 schema keys 內, 下方 ltdtmapping 取欄時即剝除不入庫. pickKeysOnly 模式不採納新增列(見 mergePickKeysOnly), 故不收集
|
|
111
|
+
let idsNew = []
|
|
112
|
+
if (!isearr(pickKeysOnly)) {
|
|
113
|
+
each(rows, (r) => {
|
|
114
|
+
if (get(r, '_isNew') === true) {
|
|
115
|
+
idsNew.push(get(r, 'id', ''))
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//雙擊防護(後端): 同一操作者之同表寫入處理中再送出即拒絕 'saveInProgress'(不排隊, 見 lockSave.mjs, ADR-025);
|
|
121
|
+
//占位包在 kmx 之外層, 不同操作者之並行寫入仍由 kmx 序列化、各自成功(ADR-012)
|
|
109
122
|
//序列化同表之並行整表批次寫入: 防 lost update (同 keyTable 序列化, 不同表並行)
|
|
110
|
-
return await kmx('updateTabItems:' + keyTable, async () => {
|
|
123
|
+
return await lockSave(`updateTabItems:${keyTable}`, userId, () => kmx('updateTabItems:' + keyTable, async () => {
|
|
111
124
|
|
|
112
125
|
//pickKeysOnly, 須於 mutex 內讀取資料庫為基底, 避免與並行寫入 race
|
|
113
126
|
if (isearr(pickKeysOnly)) {
|
|
@@ -184,6 +197,19 @@ function proc(woItems, procOrm, { srLog, kmx }) {
|
|
|
184
197
|
|
|
185
198
|
//ltdtDiffByKey
|
|
186
199
|
let ltdtOld = await woItems[keyTable].select()
|
|
200
|
+
|
|
201
|
+
//新增列已存在即拒絕 (ADR-025): 同一包重送且第 2 次於第 1 次完成後才處理時(占位已釋放), 第 1 次已建立之列會被當「修改」(diff),
|
|
202
|
+
//以前端之佔位字覆寫伺服器於新增時填入之建立者 / 建立時間(procOrm save 只覆寫 userIdUpdate / timeUpdate); 故整包拒絕、不寫入
|
|
203
|
+
if (size(idsNew) > 0) {
|
|
204
|
+
let kpIdOld = {}
|
|
205
|
+
each(ltdtOld, (v) => {
|
|
206
|
+
kpIdOld[get(v, 'id', '')] = true
|
|
207
|
+
})
|
|
208
|
+
if (idsNew.some((id) => kpIdOld[id] === true)) {
|
|
209
|
+
return Promise.reject('saveNewRowExists')
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
187
213
|
let ltdtNew = rows
|
|
188
214
|
let r = ltdtDiffByKey(ltdtOld, ltdtNew, keyDetect)
|
|
189
215
|
// console.log('ltdtDiffByKey r', r)
|
|
@@ -214,7 +240,7 @@ function proc(woItems, procOrm, { srLog, kmx }) {
|
|
|
214
240
|
|
|
215
241
|
return ltdtNew
|
|
216
242
|
|
|
217
|
-
})
|
|
243
|
+
}))
|
|
218
244
|
}
|
|
219
245
|
|
|
220
246
|
|
|
@@ -298,7 +324,7 @@ export default proc
|
|
|
298
324
|
<br class="clear">
|
|
299
325
|
|
|
300
326
|
<footer>
|
|
301
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun
|
|
327
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
302
328
|
</footer>
|
|
303
329
|
|
|
304
330
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -150,7 +150,7 @@ export default wrapInjected
|
|
|
150
150
|
<br class="clear">
|
|
151
151
|
|
|
152
152
|
<footer>
|
|
153
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun
|
|
153
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
154
154
|
</footer>
|
|
155
155
|
|
|
156
156
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -126,7 +126,7 @@ export default fetchJson
|
|
|
126
126
|
<br class="clear">
|
|
127
127
|
|
|
128
128
|
<footer>
|
|
129
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun
|
|
129
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
130
130
|
</footer>
|
|
131
131
|
|
|
132
132
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -212,7 +212,7 @@ export {
|
|
|
212
212
|
<br class="clear">
|
|
213
213
|
|
|
214
214
|
<footer>
|
|
215
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun
|
|
215
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
216
216
|
</footer>
|
|
217
217
|
|
|
218
218
|
<script>prettyPrint();</script>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>src/plugins/reuseDeletedIds.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">src/plugins/reuseDeletedIds.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
|
|
49
|
+
import map from 'lodash-es/map.js'
|
|
50
|
+
import each from 'lodash-es/each.js'
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 新增列沿用資料庫既有 id 時之正規化 (標的清單專用, 其 id 為可就地改寫之標的路徑; ADR-025)
|
|
55
|
+
*
|
|
56
|
+
* 同一次儲存中刪除(或把 id 改掉)某既有列, 又新增一列並把 id 改成該既有列之 id, 淨效果為「修改該列」:
|
|
57
|
+
* 該新增列改為一般列送出(_isNew 設 false), 建立者 / 建立時間沿用資料庫之值(修改者 / 修改時間由後端寫入).
|
|
58
|
+
* 否則後端之「新增列已存在即拒絕」(saveNewRowExists, 擋同一包重送) 會把它當重送擋下, 且重新整理後重做仍被擋;
|
|
59
|
+
* 亦避免以前端佔位字({待自動給予})覆寫建立者 / 建立時間.
|
|
60
|
+
* 只處理 id 於 rows 內唯一者; 重複 id 不處理, 由後端 ckKey 回 saveRowFieldDuplicate.
|
|
61
|
+
* 使用者 / 群組 / 權限清單之 id 為系統產生且不可編輯, 新增列不會與既有列同 id, 不需此處理.
|
|
62
|
+
*
|
|
63
|
+
* @param {Array} rows 輸入欲送出之列陣列(清單之 opt.rows), 不修改
|
|
64
|
+
* @param {Array} rowsDb 輸入資料庫之列陣列(前端 store 之同表資料)
|
|
65
|
+
* @returns {Array} 回傳正規化後之新陣列
|
|
66
|
+
*/
|
|
67
|
+
function reuseDeletedIds(rows, rowsDb) {
|
|
68
|
+
|
|
69
|
+
//kpDb, kpCount: 以 Map 存放, 不以 lodash get 取值(標的 id 為路徑, 可能含「.」而被當成巢狀路徑)
|
|
70
|
+
let kpDb = new Map()
|
|
71
|
+
each(rowsDb, (r) => {
|
|
72
|
+
kpDb.set(get(r, 'id', ''), r)
|
|
73
|
+
})
|
|
74
|
+
let kpCount = new Map()
|
|
75
|
+
each(rows, (r) => {
|
|
76
|
+
let id = get(r, 'id', '')
|
|
77
|
+
kpCount.set(id, (kpCount.get(id) || 0) + 1)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
let rs = map(rows, (r) => {
|
|
81
|
+
let id = get(r, 'id', '')
|
|
82
|
+
|
|
83
|
+
//非新增列
|
|
84
|
+
if (get(r, '_isNew') !== true) {
|
|
85
|
+
return r
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//資料庫無此 id: 真正之新增
|
|
89
|
+
if (!kpDb.has(id)) {
|
|
90
|
+
return r
|
|
91
|
+
}
|
|
92
|
+
let rDb = kpDb.get(id)
|
|
93
|
+
|
|
94
|
+
//rows 內重複 id: 不處理
|
|
95
|
+
if (kpCount.get(id) !== 1) {
|
|
96
|
+
return r
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
...r,
|
|
101
|
+
_isNew: false,
|
|
102
|
+
userId: get(rDb, 'userId', ''),
|
|
103
|
+
timeCreate: get(rDb, 'timeCreate', ''),
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return rs
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
export default reuseDeletedIds
|
|
112
|
+
</code></pre>
|
|
113
|
+
</article>
|
|
114
|
+
</section>
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<br class="clear">
|
|
124
|
+
|
|
125
|
+
<footer>
|
|
126
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
127
|
+
</footer>
|
|
128
|
+
|
|
129
|
+
<script>prettyPrint();</script>
|
|
130
|
+
<script src="scripts/polyfill.js"></script>
|
|
131
|
+
<script src="scripts/linenumber.js"></script>
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
</body>
|
|
136
|
+
</html>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>src/plugins/submitGuard.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">src/plugins/submitGuard.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
|
|
49
|
+
import each from 'lodash-es/each.js'
|
|
50
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
51
|
+
import haskey from 'wsemi/src/haskey.mjs'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 送出類操作之前端雙擊防護核心 (與 Vue / store 無關, 供單元測試; 由 mUI 之 runSubmit 與 updateLoading 使用, ADR-025)
|
|
56
|
+
*
|
|
57
|
+
* - run(key, fn, opt): 同 key 之送出流程進行中(自觸發起至流程結束, 含結果訊息框開啟期間)再觸發即略過.
|
|
58
|
+
* 按鈕之滑鼠與鍵盤 Enter、輸入框之 Enter 等入口共用同一狀態.
|
|
59
|
+
* opt.pm 為 WButtonChip / WButtonCircle 之 promiseUnlock 鎖(click 事件之 msg.pm), 於 releaseBtnLocks()(全頁 loading 關閉, 即請求結束)
|
|
60
|
+
* 或流程結束時釋放; 被略過之觸發亦立即釋放其 pm, 否則該按鈕永久鎖住.
|
|
61
|
+
* fn 收到 unlock 函數: 流程需先開確認框者於開框前呼叫, 按鈕鎖只涵蓋請求期間(確認框背後之按鈕不顯示載入圖示), 重入仍由流程狀態擋.
|
|
62
|
+
* opt.hold 為函數, 輸入流程結果, 回傳 true 時保持占位與按鈕鎖(如登入成功轉址, 頁面即將離開).
|
|
63
|
+
* - releaseBtnLocks(): 釋放進行中流程所登記之按鈕鎖, 流程狀態不變(結果訊息框開啟期間之重入仍擋).
|
|
64
|
+
* - isRunning(key): 查詢 key 是否進行中.
|
|
65
|
+
*
|
|
66
|
+
* 為何需要: 全頁 loading(WDialog)不搶焦點, 只擋滑鼠, 鍵盤事件仍送達背後之按鈕與輸入框;
|
|
67
|
+
* 按鈕鎖若於 handler 第一行即解除, 鍵盤連按照樣送出(2026-09-29 實測).
|
|
68
|
+
*
|
|
69
|
+
* @returns {Object} 回傳 { run, releaseBtnLocks, isRunning }
|
|
70
|
+
*/
|
|
71
|
+
function submitGuard() {
|
|
72
|
+
|
|
73
|
+
//kpRunning, 進行中流程: key → { unlock }
|
|
74
|
+
let kpRunning = {}
|
|
75
|
+
|
|
76
|
+
let run = (key, fn, opt = {}) => {
|
|
77
|
+
|
|
78
|
+
//unlock, 釋放本次觸發之按鈕鎖; pm.resolve 重複呼叫無作用
|
|
79
|
+
let pm = get(opt, 'pm', null)
|
|
80
|
+
let unlock = () => {
|
|
81
|
+
if (pm && isfun(pm.resolve)) {
|
|
82
|
+
pm.resolve()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//同 key 進行中: 略過, 並立即釋放本次觸發之按鈕鎖
|
|
87
|
+
if (haskey(kpRunning, key)) {
|
|
88
|
+
unlock()
|
|
89
|
+
return Promise.resolve(null)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//占位
|
|
93
|
+
kpRunning[key] = { unlock }
|
|
94
|
+
|
|
95
|
+
//hold
|
|
96
|
+
let hold = get(opt, 'hold', null)
|
|
97
|
+
|
|
98
|
+
//end
|
|
99
|
+
let end = () => {
|
|
100
|
+
unlock()
|
|
101
|
+
delete kpRunning[key]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Promise.resolve()
|
|
105
|
+
.then(() => {
|
|
106
|
+
return fn(unlock)
|
|
107
|
+
})
|
|
108
|
+
.then((r) => {
|
|
109
|
+
if (isfun(hold) && hold(r) === true) {
|
|
110
|
+
return r //保持占位與按鈕鎖
|
|
111
|
+
}
|
|
112
|
+
end()
|
|
113
|
+
return r
|
|
114
|
+
}, (err) => {
|
|
115
|
+
end()
|
|
116
|
+
return Promise.reject(err)
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let releaseBtnLocks = () => {
|
|
121
|
+
each(kpRunning, (v) => {
|
|
122
|
+
v.unlock()
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let isRunning = (key) => {
|
|
127
|
+
return haskey(kpRunning, key)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
run,
|
|
132
|
+
releaseBtnLocks,
|
|
133
|
+
isRunning,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
export default submitGuard
|
|
139
|
+
</code></pre>
|
|
140
|
+
</article>
|
|
141
|
+
</section>
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<br class="clear">
|
|
151
|
+
|
|
152
|
+
<footer>
|
|
153
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Oct 11 2026 01:45:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
154
|
+
</footer>
|
|
155
|
+
|
|
156
|
+
<script>prettyPrint();</script>
|
|
157
|
+
<script src="scripts/polyfill.js"></script>
|
|
158
|
+
<script src="scripts/linenumber.js"></script>
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
</body>
|
|
163
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-web-perm",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"main": "dist/w-web-perm.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"json5": "^2.2.3",
|
|
7
|
-
"w-aggrid-vue": "^2.1.
|
|
8
|
-
"w-component-vue": "^2.5.
|
|
9
|
-
"w-data-collector": "^1.0.
|
|
10
|
-
"w-echarts-vue": "^1.0.
|
|
11
|
-
"w-serv-hapi": "^1.1.
|
|
12
|
-
"w-serv-orm": "^1.1.
|
|
13
|
-
"w-syslog": "^1.0.
|
|
14
|
-
"w-ui-loginout": "^1.0.
|
|
15
|
-
"wsemi": "^1.9.
|
|
7
|
+
"w-aggrid-vue": "^2.1.7",
|
|
8
|
+
"w-component-vue": "^2.5.27",
|
|
9
|
+
"w-data-collector": "^1.0.42",
|
|
10
|
+
"w-echarts-vue": "^1.0.17",
|
|
11
|
+
"w-serv-hapi": "^1.1.4",
|
|
12
|
+
"w-serv-orm": "^1.1.5",
|
|
13
|
+
"w-syslog": "^1.0.31",
|
|
14
|
+
"w-ui-loginout": "^1.0.30",
|
|
15
|
+
"wsemi": "^1.9.11"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@vue/cli-plugin-babel": "^5.0.8",
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
"eslint-plugin-vue": "^9.10.0",
|
|
28
28
|
"vue": "^2.7.16",
|
|
29
29
|
"vuex": "^3.6.2",
|
|
30
|
-
"w-orm-lmdb": "^1.0.
|
|
30
|
+
"w-orm-lmdb": "^1.0.36",
|
|
31
31
|
"w-package-tools": "^1.1.16",
|
|
32
|
-
"w-package-tools-e2e": "^1.0.
|
|
33
|
-
"w-screenctl": "^1.0.
|
|
32
|
+
"w-package-tools-e2e": "^1.0.6",
|
|
33
|
+
"w-screenctl": "^1.0.24"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"serve": "vue-cli-service serve",
|