vue2-client 1.13.30 → 1.14.2
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/package.json +1 -1
- package/src/base-client/components/common/XFormTable/XFormTable.vue +12 -2
- package/src/base-client/components/common/XFormTable/demo.vue +1 -1
- package/src/base-client/components/common/XReportGrid/XReportTrGroup.vue +5 -0
- package/src/base-client/components/common/XTable/XTable.vue +12 -2
- package/src/base-client/components/common/XTable/XTableWrapper.vue +4 -0
- package/src/base-client/components/his/XHDescriptions/XHDescriptions.vue +3 -2
- package/src/bootstrap.js +10 -6
- package/src/router/async/router.map.js +4 -1
- package/src/utils/indexedDB.js +339 -37
package/package.json
CHANGED
|
@@ -81,7 +81,8 @@
|
|
|
81
81
|
@gotoUserDetail="gotoUserDetail"
|
|
82
82
|
@editButtonStateDataClick="editButtonStateDataClick"
|
|
83
83
|
@importExcelOk="importExcelOk"
|
|
84
|
-
@rowClick="handleRowClick"
|
|
84
|
+
@rowClick="handleRowClick"
|
|
85
|
+
@expand="onExpand">
|
|
85
86
|
<template slot="leftButton" slot-scope="{selectedRowKeys, selectedRows}">
|
|
86
87
|
<slot name="leftButton" :selectedRowKeys="selectedRowKeys" :selectedRows="selectedRows"></slot>
|
|
87
88
|
</template>
|
|
@@ -217,6 +218,7 @@ export default {
|
|
|
217
218
|
table_selectedRows: [],
|
|
218
219
|
// 数据只有一页时是否展示分页,true:展示,auto:隐藏
|
|
219
220
|
showPagination: true,
|
|
221
|
+
isMounted: false
|
|
220
222
|
}
|
|
221
223
|
},
|
|
222
224
|
computed: {
|
|
@@ -821,6 +823,9 @@ export default {
|
|
|
821
823
|
// 添加处理 rowClick 的方法
|
|
822
824
|
handleRowClick (record) {
|
|
823
825
|
this.$emit('rowClick', record)
|
|
826
|
+
},
|
|
827
|
+
onExpand (expanded, record) {
|
|
828
|
+
this.$emit('expand', expanded, record)
|
|
824
829
|
}
|
|
825
830
|
},
|
|
826
831
|
action: {
|
|
@@ -840,7 +845,12 @@ export default {
|
|
|
840
845
|
}
|
|
841
846
|
},
|
|
842
847
|
mounted () {
|
|
843
|
-
this.
|
|
848
|
+
if (!this.isMounted) {
|
|
849
|
+
// 防止多次调用
|
|
850
|
+
// 不知道为啥他会执行两次 mounted 暂时处理方式
|
|
851
|
+
this.initConfig()
|
|
852
|
+
this.isMounted = true
|
|
853
|
+
}
|
|
844
854
|
if (this.getSelectedData && typeof this.getSelectedData === 'function') {
|
|
845
855
|
const selectedId = this.getSelectedData()
|
|
846
856
|
if (!selectedId) {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
v-on="getEventHandlers(cell)"
|
|
36
36
|
@hook:mounted="(h)=>onComponentMounted(h,cell,cellIndex)"
|
|
37
37
|
@rowClick="handleRowClick"
|
|
38
|
+
@onExpand="onExpand"
|
|
38
39
|
:queryParamsName="cell.slotConfig"
|
|
39
40
|
:configName="cell.slotConfig"
|
|
40
41
|
:countVisible="false"
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
v-on="getEventHandlers(cell)"
|
|
72
73
|
@hook:mounted="(h)=>onComponentMounted(h,cell,cellIndex)"
|
|
73
74
|
@rowClick="handleRowClick"
|
|
75
|
+
@onExpand="onExpand"
|
|
74
76
|
:queryParamsName="cell.slotConfig"
|
|
75
77
|
:configName="cell.slotConfig"
|
|
76
78
|
:countVisible="false"
|
|
@@ -218,6 +220,9 @@ export default {
|
|
|
218
220
|
handleRowClick (record) {
|
|
219
221
|
this.$emit('rowClick', record)
|
|
220
222
|
},
|
|
223
|
+
onExpand (expanded, record) {
|
|
224
|
+
this.$emit('expand', expanded, record)
|
|
225
|
+
},
|
|
221
226
|
listClick (data) {
|
|
222
227
|
this.$emit('listClick', data)
|
|
223
228
|
},
|
|
@@ -149,7 +149,10 @@
|
|
|
149
149
|
</a-row>
|
|
150
150
|
<!-- 如果当前是表格模式 -->
|
|
151
151
|
<template v-if="isTableMode">
|
|
152
|
-
<x-table-wrapper
|
|
152
|
+
<x-table-wrapper
|
|
153
|
+
ref="table"
|
|
154
|
+
@rowClick="handleRowClick"
|
|
155
|
+
@expand="onExpand">
|
|
153
156
|
<template slot="expandedRowRender">
|
|
154
157
|
<!-- 列扩展栅格 -->
|
|
155
158
|
<x-report
|
|
@@ -250,7 +253,11 @@
|
|
|
250
253
|
ref="exportExcel"
|
|
251
254
|
/>
|
|
252
255
|
<a-modal @cancel="$refs.table.refresh()" v-model="selectedRowModalVisible" width="80vw" title="已选中数据" :footer="null">
|
|
253
|
-
<x-table-wrapper
|
|
256
|
+
<x-table-wrapper
|
|
257
|
+
ref="selectedDataTable"
|
|
258
|
+
:load-selected-data="true"
|
|
259
|
+
@rowClick="handleRowClick">
|
|
260
|
+
</x-table-wrapper>
|
|
254
261
|
</a-modal>
|
|
255
262
|
</div>
|
|
256
263
|
</template>
|
|
@@ -1323,6 +1330,9 @@ export default {
|
|
|
1323
1330
|
},
|
|
1324
1331
|
handleRowClick (record) {
|
|
1325
1332
|
this.$emit('rowClick', record)
|
|
1333
|
+
},
|
|
1334
|
+
onExpand (expanded, record) {
|
|
1335
|
+
this.$emit('expand', expanded, record)
|
|
1326
1336
|
}
|
|
1327
1337
|
}
|
|
1328
1338
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
:setScrollYHeight="tableContext.setScrollYHeight"
|
|
19
19
|
:selectRowMode="tableContext.selectRowMode"
|
|
20
20
|
:size="tableContext.tableSize"
|
|
21
|
+
@expand="onExpand"
|
|
21
22
|
@rowClick="handleRowClick"
|
|
22
23
|
>
|
|
23
24
|
<template
|
|
@@ -296,6 +297,9 @@ export default {
|
|
|
296
297
|
handleRowClick (record) {
|
|
297
298
|
this.$emit('rowClick', record)
|
|
298
299
|
},
|
|
300
|
+
onExpand (expanded, record) {
|
|
301
|
+
this.$emit('expand', expanded, record)
|
|
302
|
+
},
|
|
299
303
|
setLocalDataSource (data) {
|
|
300
304
|
this.activeTable?.setLocalDataSource(data)
|
|
301
305
|
},
|
|
@@ -325,7 +325,7 @@ export default {
|
|
|
325
325
|
white-space: nowrap !important;
|
|
326
326
|
min-width: v-bind('config?.style?.labelWidth || "80px"');
|
|
327
327
|
justify-content: flex-start;
|
|
328
|
-
padding-right:
|
|
328
|
+
padding-right: 2px !important;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
:deep(.ant-descriptions-item-content) {
|
|
@@ -334,7 +334,7 @@ export default {
|
|
|
334
334
|
align-items: center !important;
|
|
335
335
|
padding: 0 !important;
|
|
336
336
|
margin: 0 !important;
|
|
337
|
-
margin-left:
|
|
337
|
+
margin-left: 0px !important;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
:deep(.ant-descriptions-item-colon) {
|
|
@@ -343,6 +343,7 @@ export default {
|
|
|
343
343
|
align-items: center !important;
|
|
344
344
|
margin: 0 !important;
|
|
345
345
|
padding: 0 !important;
|
|
346
|
+
margin-right: 2px !important;
|
|
346
347
|
}
|
|
347
348
|
|
|
348
349
|
:deep(.ant-descriptions-item-container:hover) {
|
package/src/bootstrap.js
CHANGED
|
@@ -5,6 +5,7 @@ import guards from '@vue2-client/router/guards'
|
|
|
5
5
|
import interceptors from '@vue2-client/utils/axios-interceptors'
|
|
6
6
|
import { getConfigByName } from '@vue2-client/services/api/common'
|
|
7
7
|
import { hiPrintPlugin } from '@vue2-client/plugins/HiPrintPlugin'
|
|
8
|
+
import { isMicroAppEnv } from '@vue2-client/utils/microAppUtils'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 启动引导方法
|
|
@@ -18,12 +19,15 @@ async function bootstrap ({ router, store, i18n, message }) {
|
|
|
18
19
|
Vue.$store = store
|
|
19
20
|
// 获取系统配置
|
|
20
21
|
if (store) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// microApp 环境下作为子应用不获取webConfig
|
|
23
|
+
if (!isMicroAppEnv) {
|
|
24
|
+
await getConfigByName('webConfig', undefined, res => {
|
|
25
|
+
localStorage.setItem(process.env.VUE_APP_WEB_CONFIG_KEY, JSON.stringify(res))
|
|
26
|
+
if (res?.setting) {
|
|
27
|
+
Vue.$store.commit('setting/setSetting', res?.setting)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
27
31
|
await getConfigByName('componentStyles', undefined, res => {
|
|
28
32
|
localStorage.setItem(process.env.VUE_APP_WEB_STYLES_KEY, JSON.stringify(res))
|
|
29
33
|
})
|
|
@@ -18,7 +18,10 @@ routerResource.blank = view.blank
|
|
|
18
18
|
routerResource.singlePage = view.blank
|
|
19
19
|
// 栅格配置视图
|
|
20
20
|
routerResource.gridView = view.gridView
|
|
21
|
-
|
|
21
|
+
// 测试
|
|
22
|
+
routerResource.businessQuery = view.blank
|
|
23
|
+
// 业务查询 - 收费查询
|
|
24
|
+
routerResource.chargeQuery = () => import('@vue2-client/base-client/components/common/XFormTable/demo.vue')
|
|
22
25
|
// --------------------------------------仪表盘--------------------------------------
|
|
23
26
|
routerResource.dashboard = view.blank
|
|
24
27
|
// 工作台
|
package/src/utils/indexedDB.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { post } from '@vue2-client/services/api'
|
|
2
2
|
|
|
3
|
+
// 内存缓存作为IndexedDB的备用方案,防止数据库损坏时应用崩溃
|
|
4
|
+
const MEMORY_CACHE = new Map()
|
|
5
|
+
const USE_MEMORY_CACHE = true // 是否启用内存缓存
|
|
6
|
+
|
|
3
7
|
const DB_CONFIG = {
|
|
4
8
|
NAME: window.__MICRO_APP_NAME__ ? `view_${window.__MICRO_APP_NAME__}` : 'view',
|
|
5
9
|
STORE_NAME: 'metaCache',
|
|
@@ -7,12 +11,16 @@ const DB_CONFIG = {
|
|
|
7
11
|
CURRENT_VERSION: 1
|
|
8
12
|
}
|
|
9
13
|
|
|
14
|
+
// 数据库健康状态标记,用于在数据库异常时切换到备用方案
|
|
15
|
+
let IS_DB_HEALTHY = true
|
|
16
|
+
|
|
10
17
|
const LIULI_WEB_DB_NAME = 'view_liuli_web'
|
|
11
18
|
|
|
12
19
|
export class IndexedDBManager {
|
|
13
20
|
constructor () {
|
|
14
21
|
this.db = undefined
|
|
15
|
-
this.locks =
|
|
22
|
+
this.locks = new Map()
|
|
23
|
+
this.requestTimeout = 10000
|
|
16
24
|
this.isInMicroApp = !!window.__MICRO_APP_NAME__
|
|
17
25
|
this.microAppName = window.__MICRO_APP_NAME__ || ''
|
|
18
26
|
this.indexedDB = window?.rawWindow?.indexedDB || window.indexedDB || window.webkitindexedDB
|
|
@@ -22,9 +30,16 @@ export class IndexedDBManager {
|
|
|
22
30
|
async openDatabase () {
|
|
23
31
|
try {
|
|
24
32
|
return await new Promise((resolve, reject) => {
|
|
33
|
+
// 添加超时机制,防止请求永久挂起
|
|
34
|
+
const timeout = setTimeout(() => {
|
|
35
|
+
console.error('[openDatabase] 请求超时,将尝试重建数据库')
|
|
36
|
+
reject(new Error('IndexedDB open timeout'))
|
|
37
|
+
}, 5000) // 5秒超时
|
|
38
|
+
|
|
25
39
|
const checkRequest = this.indexedDB.open(DB_CONFIG.NAME)
|
|
26
40
|
|
|
27
41
|
checkRequest.onsuccess = (e) => {
|
|
42
|
+
clearTimeout(timeout) // 清除超时
|
|
28
43
|
const db = e.target.result
|
|
29
44
|
const currentVersion = db.version
|
|
30
45
|
db.close()
|
|
@@ -32,9 +47,19 @@ export class IndexedDBManager {
|
|
|
32
47
|
DB_CONFIG.CURRENT_VERSION = Math.max(currentVersion, DB_CONFIG.VERSION)
|
|
33
48
|
const request = this.indexedDB.open(DB_CONFIG.NAME, DB_CONFIG.CURRENT_VERSION)
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
// 为第二次打开也添加超时
|
|
51
|
+
const openTimeout = setTimeout(() => {
|
|
52
|
+
console.error('[openDatabase] 二次打开请求超时')
|
|
53
|
+
reject(new Error('IndexedDB second open timeout'))
|
|
54
|
+
}, 5000)
|
|
55
|
+
|
|
56
|
+
request.onerror = (e) => {
|
|
57
|
+
clearTimeout(openTimeout)
|
|
58
|
+
reject(e.currentTarget.error)
|
|
59
|
+
}
|
|
36
60
|
|
|
37
61
|
request.onsuccess = (e) => {
|
|
62
|
+
clearTimeout(openTimeout)
|
|
38
63
|
const db = e.target.result
|
|
39
64
|
if (!db.objectStoreNames.contains(DB_CONFIG.STORE_NAME)) {
|
|
40
65
|
db.close()
|
|
@@ -46,6 +71,7 @@ export class IndexedDBManager {
|
|
|
46
71
|
}
|
|
47
72
|
|
|
48
73
|
request.onupgradeneeded = (e) => {
|
|
74
|
+
clearTimeout(openTimeout)
|
|
49
75
|
const db = e.target.result
|
|
50
76
|
if (!db.objectStoreNames.contains(DB_CONFIG.STORE_NAME)) {
|
|
51
77
|
db.createObjectStore(DB_CONFIG.STORE_NAME, { keyPath: 'key' })
|
|
@@ -53,15 +79,26 @@ export class IndexedDBManager {
|
|
|
53
79
|
}
|
|
54
80
|
}
|
|
55
81
|
|
|
56
|
-
checkRequest.onerror = (e) =>
|
|
82
|
+
checkRequest.onerror = (e) => {
|
|
83
|
+
clearTimeout(timeout)
|
|
84
|
+
reject(e.currentTarget.error)
|
|
85
|
+
}
|
|
57
86
|
})
|
|
58
87
|
} catch (error) {
|
|
59
|
-
console.error('打开数据库失败:', error)
|
|
88
|
+
console.error('[openDatabase] 打开数据库失败:', error)
|
|
89
|
+
|
|
90
|
+
// 如果是超时错误或版本错误,尝试自动重建数据库
|
|
91
|
+
if (error.message?.includes('timeout') || error.message?.includes('version')) {
|
|
92
|
+
await this.forceRecreateDatabase()
|
|
93
|
+
return this.openDatabase() // 递归调用,重新尝试打开
|
|
94
|
+
}
|
|
95
|
+
|
|
60
96
|
throw error
|
|
61
97
|
}
|
|
62
98
|
}
|
|
63
99
|
|
|
64
100
|
async upgradeDatabase (resolve, reject) {
|
|
101
|
+
console.warn('[upgradeDatabase] 升级数据库到版本:', DB_CONFIG.CURRENT_VERSION)
|
|
65
102
|
const request = this.indexedDB.open(DB_CONFIG.NAME, DB_CONFIG.CURRENT_VERSION)
|
|
66
103
|
|
|
67
104
|
request.onupgradeneeded = (e) => {
|
|
@@ -71,11 +108,28 @@ export class IndexedDBManager {
|
|
|
71
108
|
}
|
|
72
109
|
}
|
|
73
110
|
|
|
74
|
-
request.onsuccess = (e) =>
|
|
75
|
-
|
|
111
|
+
request.onsuccess = (e) => {
|
|
112
|
+
resolve(e.target.result)
|
|
113
|
+
}
|
|
114
|
+
request.onerror = (e) => {
|
|
115
|
+
reject(e.currentTarget.error)
|
|
116
|
+
}
|
|
76
117
|
}
|
|
77
118
|
|
|
78
119
|
async openDB (callback) {
|
|
120
|
+
// 如果数据库不健康,直接调用回调并返回
|
|
121
|
+
if (!IS_DB_HEALTHY) {
|
|
122
|
+
console.warn('[openDB] 数据库不健康,跳过数据库操作')
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
try {
|
|
125
|
+
callback(null)
|
|
126
|
+
} catch (e) {
|
|
127
|
+
console.error('[openDB] 回调执行错误:', e)
|
|
128
|
+
}
|
|
129
|
+
}, 0)
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
79
133
|
try {
|
|
80
134
|
if (this.db) {
|
|
81
135
|
const isAlive = await this.checkConnection()
|
|
@@ -85,13 +139,51 @@ export class IndexedDBManager {
|
|
|
85
139
|
}
|
|
86
140
|
}
|
|
87
141
|
|
|
88
|
-
|
|
89
|
-
|
|
142
|
+
// 添加超时机制,防止卡死
|
|
143
|
+
const openDbPromise = this.openDatabase()
|
|
144
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
|
145
|
+
setTimeout(() => reject(new Error('openDB timeout')), 5000)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
this.db = await Promise.race([openDbPromise, timeoutPromise])
|
|
150
|
+
callback(this.db)
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('[openDB] 打开数据库连接超时或失败', error)
|
|
153
|
+
// 标记数据库为不健康状态,后续操作使用备用方案
|
|
154
|
+
IS_DB_HEALTHY = false
|
|
155
|
+
try {
|
|
156
|
+
callback(null)
|
|
157
|
+
} catch (callbackError) {
|
|
158
|
+
console.error('[openDB] 回调执行错误:', callbackError)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
90
161
|
} catch (error) {
|
|
91
|
-
console.error('数据库操作失败:', error)
|
|
162
|
+
console.error('[openDB] 数据库操作失败:', error)
|
|
163
|
+
|
|
164
|
+
// 版本错误时尝试重建数据库
|
|
92
165
|
if (error.message?.includes('version')) {
|
|
93
|
-
|
|
94
|
-
|
|
166
|
+
try {
|
|
167
|
+
await this.recreateDatabase()
|
|
168
|
+
callback(this.db)
|
|
169
|
+
} catch (rebuildError) {
|
|
170
|
+
console.error('[openDB] 重建失败,标记数据库为不健康状态', rebuildError)
|
|
171
|
+
IS_DB_HEALTHY = false
|
|
172
|
+
try {
|
|
173
|
+
callback(null)
|
|
174
|
+
} catch (callbackError) {
|
|
175
|
+
console.error('[openDB] 回调执行错误:', callbackError)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
// 其他错误,标记为不健康并使用备用方案
|
|
180
|
+
IS_DB_HEALTHY = false
|
|
181
|
+
console.error('[openDB] 不可恢复的错误,标记数据库为不健康状态')
|
|
182
|
+
try {
|
|
183
|
+
callback(null)
|
|
184
|
+
} catch (callbackError) {
|
|
185
|
+
console.error('[openDB] 回调执行错误:', callbackError)
|
|
186
|
+
}
|
|
95
187
|
}
|
|
96
188
|
}
|
|
97
189
|
}
|
|
@@ -102,41 +194,93 @@ export class IndexedDBManager {
|
|
|
102
194
|
const store = transaction.objectStore(DB_CONFIG.STORE_NAME)
|
|
103
195
|
await this.promisifyRequest(store.add({ key: 'alive', data: true }))
|
|
104
196
|
return true
|
|
105
|
-
} catch {
|
|
197
|
+
} catch (error) {
|
|
106
198
|
return false
|
|
107
199
|
}
|
|
108
200
|
}
|
|
109
201
|
|
|
110
202
|
promisifyRequest (request) {
|
|
111
203
|
return new Promise((resolve, reject) => {
|
|
112
|
-
request.onsuccess = () =>
|
|
113
|
-
|
|
204
|
+
request.onsuccess = () => {
|
|
205
|
+
resolve(request.result)
|
|
206
|
+
}
|
|
207
|
+
request.onerror = () => {
|
|
208
|
+
reject(request.error)
|
|
209
|
+
}
|
|
114
210
|
})
|
|
115
211
|
}
|
|
116
212
|
|
|
117
213
|
async getByWeb (key, url, params, callback, processFun) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
214
|
+
// 数据库不健康时使用备用方案
|
|
215
|
+
if (!IS_DB_HEALTHY) {
|
|
216
|
+
console.warn(`[getByWeb] 数据库不健康,使用内存缓存或直接API调用`)
|
|
217
|
+
return this.getByWebWithoutDB(key, url, params, callback, processFun)
|
|
121
218
|
}
|
|
122
219
|
|
|
123
|
-
|
|
220
|
+
// 锁机制:防止同一键的并发请求
|
|
221
|
+
if (this.locks.has(key)) {
|
|
222
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
|
223
|
+
setTimeout(() => {
|
|
224
|
+
reject(new Error(`请求超时: ${key}`))
|
|
225
|
+
}, this.requestTimeout)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
await Promise.race([this.locks.get(key), timeoutPromise])
|
|
230
|
+
return this.getByWeb(key, url, params, callback, processFun)
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(`[getByWeb] 等待锁期间出错或超时: ${error.message}`)
|
|
233
|
+
this.locks.delete(key)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const lockPromise = (async () => {
|
|
124
238
|
try {
|
|
239
|
+
// 首先检查内存缓存
|
|
240
|
+
if (USE_MEMORY_CACHE && MEMORY_CACHE.has(key)) {
|
|
241
|
+
const cachedData = MEMORY_CACHE.get(key)
|
|
242
|
+
callback(cachedData)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 再检查IndexedDB缓存
|
|
125
247
|
const data = DB_CONFIG.NAME === LIULI_WEB_DB_NAME ? undefined : await this.getData(key)
|
|
248
|
+
|
|
126
249
|
if (!data && url) {
|
|
250
|
+
// 缓存未命中,请求网络数据
|
|
127
251
|
const res = await post(url, params)
|
|
128
252
|
const processedData = processFun ? processFun(res) : res
|
|
129
253
|
|
|
254
|
+
// 同时保存到内存缓存
|
|
255
|
+
if (USE_MEMORY_CACHE) {
|
|
256
|
+
MEMORY_CACHE.set(key, processedData)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 生产环境或非移动配置时保存到IndexedDB
|
|
130
260
|
if (process.env.NODE_ENV === 'production' || key !== 'webMobileConfig') {
|
|
131
261
|
await this.add(key, processedData)
|
|
132
262
|
}
|
|
133
263
|
|
|
134
264
|
callback(processedData)
|
|
135
265
|
} else {
|
|
266
|
+
// 使用缓存数据
|
|
267
|
+
if (USE_MEMORY_CACHE && data) {
|
|
268
|
+
MEMORY_CACHE.set(key, data)
|
|
269
|
+
}
|
|
136
270
|
callback(data)
|
|
137
271
|
}
|
|
138
272
|
} catch (error) {
|
|
139
|
-
console.error(
|
|
273
|
+
console.error(`[getByWeb] 获取数据失败: ${error.message}`)
|
|
274
|
+
|
|
275
|
+
// IndexedDB操作失败时切换到备用方案
|
|
276
|
+
if (error.name === 'InvalidStateError' || error.message?.includes('database') || error.message?.includes('transaction')) {
|
|
277
|
+
console.error(`[getByWeb] 数据库操作失败,标记为不健康`)
|
|
278
|
+
IS_DB_HEALTHY = false
|
|
279
|
+
|
|
280
|
+
// 使用备用方案获取数据
|
|
281
|
+
return this.getByWebWithoutDB(key, url, params, callback, processFun)
|
|
282
|
+
}
|
|
283
|
+
|
|
140
284
|
if (process.env.NODE_ENV === 'production' && key !== 'webMobileConfig') {
|
|
141
285
|
await this.add(key, null)
|
|
142
286
|
}
|
|
@@ -144,65 +288,130 @@ export class IndexedDBManager {
|
|
|
144
288
|
}
|
|
145
289
|
})()
|
|
146
290
|
|
|
291
|
+
this.locks.set(key, lockPromise)
|
|
292
|
+
|
|
147
293
|
try {
|
|
148
|
-
|
|
294
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
reject(new Error(`操作超时: ${key}`))
|
|
297
|
+
}, this.requestTimeout)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
await Promise.race([this.locks.get(key), timeoutPromise])
|
|
301
|
+
} catch (error) {
|
|
302
|
+
console.error(`[getByWeb] 执行期间出错或超时: ${error.message}`)
|
|
149
303
|
} finally {
|
|
150
|
-
|
|
304
|
+
this.locks.delete(key)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 备用方法:不使用IndexedDB,直接使用内存缓存和API
|
|
309
|
+
async getByWebWithoutDB (key, url, params, callback, processFun) {
|
|
310
|
+
// 首先检查内存缓存
|
|
311
|
+
if (USE_MEMORY_CACHE && MEMORY_CACHE.has(key)) {
|
|
312
|
+
const cachedData = MEMORY_CACHE.get(key)
|
|
313
|
+
callback(cachedData)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 无URL则无法获取数据
|
|
318
|
+
if (!url) {
|
|
319
|
+
callback(null)
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 直接通过API获取数据
|
|
324
|
+
try {
|
|
325
|
+
const res = await post(url, params)
|
|
326
|
+
const processedData = processFun ? processFun(res) : res
|
|
327
|
+
|
|
328
|
+
// 保存到内存缓存
|
|
329
|
+
if (USE_MEMORY_CACHE) {
|
|
330
|
+
MEMORY_CACHE.set(key, processedData)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
callback(processedData)
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error(`[getByWebWithoutDB] 获取数据失败: ${error.message}`)
|
|
336
|
+
callback(null)
|
|
151
337
|
}
|
|
152
338
|
}
|
|
153
339
|
|
|
154
340
|
async getData (key) {
|
|
155
341
|
return new Promise((resolve) => {
|
|
156
342
|
this.openDB((db) => {
|
|
343
|
+
if (!db) {
|
|
344
|
+
resolve(null)
|
|
345
|
+
return
|
|
346
|
+
}
|
|
157
347
|
const store = db.transaction(DB_CONFIG.STORE_NAME, 'readwrite').objectStore(DB_CONFIG.STORE_NAME)
|
|
158
348
|
const request = store.get(key)
|
|
159
|
-
|
|
160
|
-
request.
|
|
349
|
+
|
|
350
|
+
request.onsuccess = (e) => {
|
|
351
|
+
resolve(e.target.result?.data)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
request.onerror = () => {
|
|
355
|
+
resolve(null)
|
|
356
|
+
}
|
|
161
357
|
})
|
|
162
358
|
})
|
|
163
359
|
}
|
|
164
360
|
|
|
165
361
|
async add (key, data) {
|
|
166
362
|
this.openDB((db) => {
|
|
363
|
+
if (!db) return
|
|
364
|
+
|
|
167
365
|
const store = db.transaction(DB_CONFIG.STORE_NAME, 'readwrite').objectStore(DB_CONFIG.STORE_NAME)
|
|
168
366
|
const request = store.add({ key, data })
|
|
169
|
-
|
|
367
|
+
|
|
368
|
+
request.onerror = () => {
|
|
369
|
+
// 添加失败时尝试更新
|
|
370
|
+
this.update(key, data)
|
|
371
|
+
}
|
|
170
372
|
})
|
|
171
373
|
}
|
|
172
374
|
|
|
173
375
|
async update (key, data) {
|
|
174
376
|
this.openDB((db) => {
|
|
377
|
+
if (!db) return
|
|
378
|
+
|
|
175
379
|
const store = db.transaction(DB_CONFIG.STORE_NAME, 'readwrite').objectStore(DB_CONFIG.STORE_NAME)
|
|
176
|
-
|
|
177
|
-
request.onerror = () => console.error('数据更新失败')
|
|
380
|
+
store.put({ key, data })
|
|
178
381
|
})
|
|
179
382
|
}
|
|
180
383
|
|
|
181
384
|
async delete (key) {
|
|
182
385
|
this.openDB((db) => {
|
|
386
|
+
if (!db) return
|
|
387
|
+
|
|
183
388
|
const store = db.transaction(DB_CONFIG.STORE_NAME, 'readwrite').objectStore(DB_CONFIG.STORE_NAME)
|
|
184
|
-
|
|
185
|
-
request.onerror = () => console.error('数据删除失败')
|
|
389
|
+
store.delete(key)
|
|
186
390
|
})
|
|
187
391
|
}
|
|
188
392
|
|
|
189
393
|
async clear (callback) {
|
|
190
394
|
this.openDB((db) => {
|
|
395
|
+
if (!db) {
|
|
396
|
+
if (typeof callback === 'function') callback()
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
191
400
|
const store = db.transaction(DB_CONFIG.STORE_NAME, 'readwrite').objectStore(DB_CONFIG.STORE_NAME)
|
|
192
401
|
const request = store.clear()
|
|
402
|
+
|
|
193
403
|
request.onerror = () => {
|
|
194
|
-
|
|
195
|
-
callback()
|
|
404
|
+
if (typeof callback === 'function') callback()
|
|
196
405
|
}
|
|
406
|
+
|
|
197
407
|
request.onsuccess = () => {
|
|
198
|
-
if (typeof callback === 'function')
|
|
199
|
-
callback()
|
|
200
|
-
}
|
|
408
|
+
if (typeof callback === 'function') callback()
|
|
201
409
|
}
|
|
202
410
|
})
|
|
203
411
|
}
|
|
204
412
|
|
|
205
413
|
clearCache () {
|
|
414
|
+
// 清除缓存并刷新页面
|
|
206
415
|
if (this.indexedDB) {
|
|
207
416
|
this.clear(() => {
|
|
208
417
|
location.reload()
|
|
@@ -215,22 +424,115 @@ export class IndexedDBManager {
|
|
|
215
424
|
async getAll () {
|
|
216
425
|
return new Promise((resolve, reject) => {
|
|
217
426
|
this.openDB((db) => {
|
|
427
|
+
if (!db) {
|
|
428
|
+
resolve([])
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
|
|
218
432
|
const store = db.transaction(DB_CONFIG.STORE_NAME, 'readonly').objectStore(DB_CONFIG.STORE_NAME)
|
|
219
433
|
const request = store.getAll()
|
|
220
|
-
|
|
221
|
-
request.
|
|
434
|
+
|
|
435
|
+
request.onsuccess = (e) => {
|
|
436
|
+
resolve(e.target.result)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
request.onerror = (e) => {
|
|
440
|
+
reject(e.target.error)
|
|
441
|
+
}
|
|
222
442
|
})
|
|
223
443
|
})
|
|
224
444
|
}
|
|
225
445
|
|
|
226
446
|
getAllLegacy (callback) {
|
|
227
447
|
this.openDB((res) => {
|
|
448
|
+
if (!res) {
|
|
449
|
+
callback(null, new Error('打开数据库失败'))
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
228
453
|
const store = res.transaction(DB_CONFIG.STORE_NAME, 'readonly').objectStore(DB_CONFIG.STORE_NAME)
|
|
229
454
|
const request = store.getAll()
|
|
230
|
-
|
|
231
|
-
request.
|
|
455
|
+
|
|
456
|
+
request.onerror = (e) => {
|
|
457
|
+
callback(null, e.target.error)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
request.onsuccess = (e) => {
|
|
461
|
+
callback(e.target.result)
|
|
462
|
+
}
|
|
232
463
|
})
|
|
233
464
|
}
|
|
465
|
+
|
|
466
|
+
async recreateDatabase () {
|
|
467
|
+
try {
|
|
468
|
+
if (this.db) {
|
|
469
|
+
this.db.close()
|
|
470
|
+
this.db = undefined
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
await new Promise((resolve, reject) => {
|
|
474
|
+
const deleteRequest = this.indexedDB.deleteDatabase(DB_CONFIG.NAME)
|
|
475
|
+
|
|
476
|
+
deleteRequest.onsuccess = () => {
|
|
477
|
+
resolve()
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
deleteRequest.onerror = (e) => {
|
|
481
|
+
reject(e.target.error)
|
|
482
|
+
}
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
DB_CONFIG.CURRENT_VERSION = DB_CONFIG.VERSION
|
|
486
|
+
this.db = await this.openDatabase()
|
|
487
|
+
return this.db
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error('重新创建数据库失败:', error)
|
|
490
|
+
throw error
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// 强制重建数据库的方法,带有超时保护
|
|
495
|
+
async forceRecreateDatabase () {
|
|
496
|
+
console.warn('[forceRecreateDatabase] 开始强制重建数据库')
|
|
497
|
+
try {
|
|
498
|
+
// 确保关闭所有连接
|
|
499
|
+
if (this.db) {
|
|
500
|
+
try {
|
|
501
|
+
this.db.close()
|
|
502
|
+
} catch (e) {
|
|
503
|
+
console.warn('[forceRecreateDatabase] 关闭现有连接出错')
|
|
504
|
+
}
|
|
505
|
+
this.db = undefined
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 清空锁防止死锁
|
|
509
|
+
this.locks.clear()
|
|
510
|
+
|
|
511
|
+
// 强制删除数据库,带超时保护
|
|
512
|
+
await new Promise((resolve) => {
|
|
513
|
+
const deleteRequest = this.indexedDB.deleteDatabase(DB_CONFIG.NAME)
|
|
514
|
+
|
|
515
|
+
const timeout = setTimeout(() => {
|
|
516
|
+
resolve() // 即使超时也继续执行
|
|
517
|
+
}, 3000)
|
|
518
|
+
|
|
519
|
+
deleteRequest.onsuccess = () => {
|
|
520
|
+
clearTimeout(timeout)
|
|
521
|
+
resolve()
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
deleteRequest.onerror = () => {
|
|
525
|
+
clearTimeout(timeout)
|
|
526
|
+
resolve() // 即使出错也继续执行
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
// 重置版本
|
|
531
|
+
DB_CONFIG.CURRENT_VERSION = DB_CONFIG.VERSION
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.error('[forceRecreateDatabase] 强制重建过程中出错:', error)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
234
536
|
}
|
|
235
537
|
|
|
236
538
|
export const indexedDB = new IndexedDBManager()
|