@epxing/business 5.3.0-beta.2 → 5.3.0-beta.3
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/API.md +8 -4
- package/README.md +44 -44
- package/dist/src/base/AiSkillEditor.js +1 -1
- package/dist/src/base/AiSkillEditor.vue.js +2 -0
- package/dist/src/base/AppConfigFormItem.js +1 -3
- package/dist/src/base/AppConfigFormItem.vue.js +210 -85
- package/dist/src/base/CommonFieldList.js +1 -1
- package/dist/src/base/CommonFieldList.vue.js +2 -1
- package/dist/src/base/ConfigModuleIndex.js +38 -1
- package/dist/src/base/ManageAppNav.js +1 -1
- package/dist/src/base/ManageMenu.js +1 -1
- package/dist/src/base/RoleBindTrace.js +253 -0
- package/dist/src/base/RoleGroupManage.js +1037 -0
- package/dist/src/base/ServiceMenu.js +7 -6
- package/dist/src/components/AiList.js +1278 -3
- package/dist/src/components/EntityCheckboxFind.vue.js +14 -10
- package/dist/src/components/EntityRightFormItem.js +1 -5
- package/dist/src/components/EntityRightFormItem.vue.js +3 -1
- package/dist/src/components/EntitySystemEr.js +492 -0
- package/dist/src/components/HallNav.vue.js +7 -36
- package/dist/src/components/IconFontLibrary.js +22 -1
- package/dist/src/components/IconFontList.js +21 -1
- package/dist/src/components/PrintFormList.js +2 -1
- package/dist/src/components/PrintFormList.vue.js +3 -1
- package/dist/src/components/WikiWorkbench.js +153 -129
- package/dist/src/components/authorization/AuthorizationInterface.js +3 -1
- package/dist/src/components/custom-bpmn/PropertiesView.js +1991 -1942
- package/dist/src/components/icon-gallery/IconGallery.js +193 -0
- package/dist/src/components/icon-gallery/mixin.js +150 -0
- package/dist/src/components/wiki-workbench/WikiIntake.js +86 -93
- package/dist/src/components/wiki-workbench/WikiIntake.vue.js +153 -73
- package/dist/src/index.js +26 -20
- package/dist/src/public-components.js +16 -14
- package/dist/src/route-components.js +5 -5
- package/dist/src/system.component.index.js +1 -1
- package/dist/src/system.component.js +8 -3
- package/dist/src/system.component.lazy.js +1 -1
- package/dist/src/views/Add.js +1 -1
- package/dist/src/views/Add.vue.js +8 -3
- package/dist/src/views/AiToolManager.js +792 -317
- package/dist/src/views/App.js +3 -45
- package/dist/src/views/AppRoleSelect.js +3 -230
- package/dist/src/views/Error.js +3 -207
- package/dist/src/views/ExcelDesign.js +326 -316
- package/dist/src/views/JvmMemoryMonitor.js +463 -0
- package/dist/src/views/Module.js +3 -170
- package/dist/src/views/WikiBinding.js +88 -25
- package/dist/style.css +2867 -1487
- package/package.json +12 -6
- package/src/base/AiSkillEditor.vue +2 -0
- package/src/base/AppConfigFormItem.vue +211 -145
- package/src/base/CommonFieldList.vue +9 -6
- package/src/base/ConfigModuleIndex.vue +37 -1
- package/src/base/ManageAppNav.vue +1188 -1188
- package/src/base/ManageMenu.vue +933 -933
- package/src/base/RoleBindTrace.vue +364 -0
- package/src/base/RoleGroupManage.vue +1160 -0
- package/src/base/ServiceMenu.vue +6 -5
- package/src/components/AiList.vue +439 -113
- package/src/components/EntityCheckboxFind.vue +12 -8
- package/src/components/EntityRightFormItem.vue +9 -10
- package/src/components/EntitySystemEr.vue +517 -0
- package/src/components/HallNav.vue +6 -33
- package/src/components/IconFontLibrary.vue +27 -60
- package/src/components/IconFontList.vue +13 -64
- package/src/components/PrintFormList.vue +33 -27
- package/src/components/WikiWorkbench.vue +90 -60
- package/src/components/authorization/AuthorizationInterface.vue +2 -0
- package/src/components/custom-bpmn/CustomPalette.js +1 -1
- package/src/components/custom-bpmn/CustomRenderer.js +1 -1
- package/src/components/custom-bpmn/PropertiesView.vue +35 -4
- package/src/components/custom-bpmn/index.js +1 -1
- package/src/components/icon-gallery/IconGallery.vue +130 -0
- package/src/components/icon-gallery/mixin.jsx +109 -0
- package/src/components/wiki-workbench/WikiIntake.vue +178 -104
- package/src/index.js +24 -20
- package/src/public-components.js +1 -0
- package/src/route-components.js +4 -4
- package/src/system.component.index.js +1 -1
- package/src/views/Add.vue +8 -5
- package/src/views/AiToolManager.vue +326 -4
- package/src/views/App.vue +4 -53
- package/src/views/AppRoleSelect.vue +4 -334
- package/src/views/Error.vue +4 -63
- package/src/views/ExcelDesign.vue +24 -7
- package/src/views/JvmMemoryMonitor.vue +284 -0
- package/src/views/Module.vue +4 -210
- package/src/views/WikiBinding.vue +42 -0
- package/types/app.d.ts +1 -0
- package/types/dept-find.d.ts +1 -1
- package/types/index.d.ts +15 -14
- package/types/popup.d.ts +2 -2
- package/types/route-components.d.ts +1 -1
- package/types/user-find.d.ts +1 -1
- package/dist/assets/role_select-DTqRlFNz.webp +0 -0
- package/dist/src/components/AiList.vue.js +0 -93
- package/dist/src/components/IconFontLibrary.vue.js +0 -67
- package/dist/src/components/IconFontList.vue.js +0 -79
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="jvm-memory">
|
|
3
|
+
<div class="jvm-memory__toolbar">
|
|
4
|
+
<div><h2>内存监控</h2><p>当前 JVM 的内存使用、GC 与请求保护状态</p></div>
|
|
5
|
+
<div class="jvm-memory__actions">
|
|
6
|
+
<el-switch v-model="autoRefresh" active-text="自动刷新" @change="changeRefresh" />
|
|
7
|
+
<el-button size="small" icon="el-icon-refresh" :loading="loading" @click="refresh">刷新</el-button>
|
|
8
|
+
<el-button size="small" icon="el-icon-document" :loading="jstackLoading" @click="captureJstack">获取 jstack</el-button>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<el-alert v-if="error" :title="error" type="error" :closable="false" show-icon />
|
|
12
|
+
<el-alert v-else-if="stale" title="采样数据已过期,请检查 JVM 监控线程或网络。下方保留最后一次采样。" type="warning" :closable="false" show-icon />
|
|
13
|
+
<template v-if="snapshot">
|
|
14
|
+
<div class="jvm-memory__status">
|
|
15
|
+
<el-tag :type="statusType">{{ statusText }}</el-tag>
|
|
16
|
+
<span>采样时间:{{ time(snapshot.sampledAt) }}</span>
|
|
17
|
+
<span>采样间隔:{{ snapshot.sampleIntervalMs / 1000 }} 秒</span>
|
|
18
|
+
<span>保护策略:{{ snapshot.rejectRequests ? '高压时拒绝新请求' : '仅监控' }}</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="jvm-memory__summary-row">
|
|
21
|
+
<div class="jvm-memory__panel jvm-memory__policy">
|
|
22
|
+
<h3>监控与保护规则</h3>
|
|
23
|
+
<p>预警 {{ ratio(snapshot.warningThreshold) }};连续 {{ snapshot.highSamples }} 次达到 {{ ratio(snapshot.rejectThreshold) }},或单次达到 {{ ratio(snapshot.emergencyThreshold) }} 时进入保护。</p>
|
|
24
|
+
<p>连续 {{ snapshot.recoverySamples }} 次降至 {{ ratio(snapshot.recoveryThreshold) }} 以下后恢复。保护期间新业务请求返回 503,已接收的请求继续执行。</p>
|
|
25
|
+
<p>压力取堆内存、有明确上限的非堆内存池及已配置预算的直接内存使用率的最大值。监控不主动触发 GC,也不采集对象明细。</p>
|
|
26
|
+
<small>实例 {{ snapshot.instanceId }} · JVM 启动于 {{ time(snapshot.startedAt) }}。只展示本次请求命中的 JVM,切换实例时清空趋势。</small>
|
|
27
|
+
</div>
|
|
28
|
+
<div v-if="snapshot.risk" class="jvm-memory__panel jvm-memory__risk">
|
|
29
|
+
<div class="jvm-memory__chart-title"><h3>溢出与死锁风险预测</h3><span>{{ snapshot.risk.reason }}</span></div>
|
|
30
|
+
<p>内存风险:<el-tag size="small" :type="riskType(snapshot.risk.memoryRisk)">{{ snapshot.risk.memoryRisk }}</el-tag>
|
|
31
|
+
<span class="jvm-memory__risk-gap">死锁风险:</span><el-tag size="small" :type="riskType(snapshot.risk.deadlockRisk)">{{ snapshot.risk.deadlockRisk }}</el-tag></p>
|
|
32
|
+
<p v-if="snapshot.risk.memoryGrowthBytesPerSecond > 0">最近窗口堆增长 {{ bytesPerSecond(snapshot.risk.memoryGrowthBytesPerSecond) }};预计达到拒绝线 {{ seconds(snapshot.risk.predictedSecondsToReject) }},达到紧急线 {{ seconds(snapshot.risk.predictedSecondsToEmergency) }}。</p>
|
|
33
|
+
<p v-else>最近窗口未观察到持续堆增长,预测仅供预警参考。</p>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div v-if="snapshot.heap" class="jvm-memory__cards">
|
|
37
|
+
<div class="jvm-memory__card">
|
|
38
|
+
<span>堆内存</span><strong>{{ bytes(snapshot.heap.used) }}</strong>
|
|
39
|
+
<el-progress :percentage="percent(snapshot.heap.used, snapshot.heap.max)" :color="pressureColor" />
|
|
40
|
+
<small>上限 {{ bytes(snapshot.heap.max) }} · 已提交 {{ bytes(snapshot.heap.committed) }}</small>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="jvm-memory__card">
|
|
43
|
+
<span>非堆内存</span><strong>{{ bytes(snapshot.nonHeap.used) }}</strong>
|
|
44
|
+
<small>上限 {{ bytes(snapshot.nonHeap.max) }} · 已提交 {{ bytes(snapshot.nonHeap.committed) }}</small>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="jvm-memory__card">
|
|
47
|
+
<span>直接 / 映射缓冲区</span><strong>{{ bytes(snapshot.directBytes) }}</strong>
|
|
48
|
+
<small>映射 {{ bytes(snapshot.mappedBytes) }}</small>
|
|
49
|
+
<small>直接内存预算 {{ snapshot.directMemoryLimitBytes ? bytes(snapshot.directMemoryLimitBytes) : '未配置,仅监控' }}</small>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="jvm-memory__card">
|
|
52
|
+
<span>GC 累计次数 / 耗时</span><strong>{{ snapshot.gcCount }} 次</strong>
|
|
53
|
+
<small>{{ (snapshot.gcTimeMillis / 1000).toFixed(2) }} 秒 · JVM 启动以来累计</small>
|
|
54
|
+
<small>已拒绝请求 {{ snapshot.rejectedRequests }} 次</small>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="jvm-memory__panel">
|
|
58
|
+
<div class="jvm-memory__chart-title"><h3>内存压力趋势</h3><span>当前 {{ (snapshot.pressureRatio * 100).toFixed(1) }}% · 当前页面最近 60 个采样点</span></div>
|
|
59
|
+
<svg v-if="history.length > 1" class="jvm-memory__chart" viewBox="0 0 600 120" preserveAspectRatio="none" role="img" aria-label="内存压力趋势,橙色虚线为保护阈值">
|
|
60
|
+
<line x1="0" :y1="120 * (1 - snapshot.rejectThreshold)" x2="600" :y2="120 * (1 - snapshot.rejectThreshold)" stroke="#e6a23c" stroke-dasharray="5 5" />
|
|
61
|
+
<polyline :points="trendPoints" fill="none" stroke="#3074ef" stroke-width="2" vector-effect="non-scaling-stroke" />
|
|
62
|
+
</svg>
|
|
63
|
+
<p v-else class="jvm-memory__empty">{{ snapshot.enabled ? '等待下一次采样形成趋势' : '监控已关闭' }}</p>
|
|
64
|
+
<div class="jvm-memory__chart-title"><small>{{ history.length ? time(history[0].sampledAt) : '—' }}</small><small>{{ time(snapshot.sampledAt) }}</small></div>
|
|
65
|
+
</div>
|
|
66
|
+
<div v-if="snapshot.threads" class="jvm-memory__panel jvm-memory__threads">
|
|
67
|
+
<div class="jvm-memory__chart-title"><h3>线程监控</h3><span>采样 {{ time(snapshot.threads.sampledAt) }}</span></div>
|
|
68
|
+
<div class="jvm-memory__thread-grid">
|
|
69
|
+
<span>活动线程 <strong>{{ snapshot.threads.live }}</strong></span>
|
|
70
|
+
<span>峰值线程 <strong>{{ snapshot.threads.peak }}</strong></span>
|
|
71
|
+
<span>RUNNABLE <strong>{{ snapshot.threads.runnable }}</strong></span>
|
|
72
|
+
<span>BLOCKED <strong>{{ snapshot.threads.blocked }}</strong></span>
|
|
73
|
+
<span>WAITING <strong>{{ snapshot.threads.waiting + snapshot.threads.timedWaiting }}</strong></span>
|
|
74
|
+
<span>死锁线程 <strong :class="{ 'is-danger': snapshot.threads.deadlocked > 0 }">{{ snapshot.threads.deadlocked }}</strong></span>
|
|
75
|
+
<span>线程栈估算 <strong>{{ snapshot.threads.stackBytesEstimated ? bytes(snapshot.threads.stackBytesEstimate) : '未知' }}</strong></span>
|
|
76
|
+
</div>
|
|
77
|
+
<p>线程栈是按活动线程数和 Xss 参数估算的保留空间,不等于实际 RSS;死锁扫描按 30 秒周期执行。</p>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
<p v-else-if="!loading && !error" class="jvm-memory__empty">暂无采样数据</p>
|
|
81
|
+
<el-dialog v-model="jstackVisible" width="80%" append-to-body>
|
|
82
|
+
<template #title>
|
|
83
|
+
<div class="jvm-memory__dialog-title">
|
|
84
|
+
<span>JVM jstack(有界快照)</span>
|
|
85
|
+
<span class="jvm-memory__dialog-actions">
|
|
86
|
+
<el-button size="small" icon="el-icon-document-copy" :disabled="!jstackSnapshot" @click="copyJstack">复制</el-button>
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
<div v-if="jstackSnapshot" class="jvm-memory__jstack-meta">线程 {{ jstackSnapshot.threadCount }},输出 {{ jstackSnapshot.includedThreads }} 条,栈空间估算 {{ jstackSnapshot.stackBytesEstimated ? bytes(jstackSnapshot.stackBytesEstimate) : '未知' }}</div>
|
|
91
|
+
<pre v-if="jstackSnapshot" class="jvm-memory__jstack">{{ jstackSnapshot.text }}</pre>
|
|
92
|
+
</el-dialog>
|
|
93
|
+
</section>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<script>
|
|
97
|
+
import axios from 'axios'
|
|
98
|
+
import { markRaw } from 'vue'
|
|
99
|
+
|
|
100
|
+
export default {
|
|
101
|
+
name: 'epx-jvm-memory-monitor',
|
|
102
|
+
data () {
|
|
103
|
+
return {
|
|
104
|
+
snapshot: null,
|
|
105
|
+
history: [],
|
|
106
|
+
autoRefresh: true,
|
|
107
|
+
loading: false,
|
|
108
|
+
error: '',
|
|
109
|
+
stale: false,
|
|
110
|
+
active: false,
|
|
111
|
+
timer: null,
|
|
112
|
+
// 取消并作废离开页面前的请求,避免迟到响应重启轮询。
|
|
113
|
+
pendingRequest: null,
|
|
114
|
+
jstackLoading: false,
|
|
115
|
+
jstackVisible: false,
|
|
116
|
+
jstackSnapshot: null
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
computed: {
|
|
120
|
+
statusText () {
|
|
121
|
+
if (this.error || this.stale) return '状态未知'
|
|
122
|
+
const states = { NORMAL: '正常', WARNING: '内存预警', PROTECTED: this.snapshot.rejecting ? '请求保护中' : '高内存压力(仅监控)', STARTING: '等待采样', DISABLED: '监控已关闭' }
|
|
123
|
+
return states[this.snapshot.state] || '状态未知'
|
|
124
|
+
},
|
|
125
|
+
statusType () {
|
|
126
|
+
if (this.error || this.stale || !this.snapshot.enabled) return 'info'
|
|
127
|
+
return this.snapshot.state === 'PROTECTED' ? 'danger' : this.snapshot.state === 'WARNING' ? 'warning' : 'success'
|
|
128
|
+
},
|
|
129
|
+
pressureColor () {
|
|
130
|
+
return this.snapshot.pressureRatio >= this.snapshot.rejectThreshold ? '#f56c6c' : '#3074ef'
|
|
131
|
+
},
|
|
132
|
+
trendPoints () {
|
|
133
|
+
return this.history.map((point, index) => `${index * 600 / Math.max(1, this.history.length - 1)},${120 * (1 - Math.min(1, Math.max(0, point.pressureRatio)))}`).join(' ')
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
mounted () {
|
|
137
|
+
document.addEventListener('visibilitychange', this.onVisibilityChange)
|
|
138
|
+
this.activate()
|
|
139
|
+
},
|
|
140
|
+
activated () { this.activate() },
|
|
141
|
+
deactivated () { this.active = false; this.stop() },
|
|
142
|
+
beforeUnmount () {
|
|
143
|
+
this.active = false
|
|
144
|
+
this.stop()
|
|
145
|
+
document.removeEventListener('visibilitychange', this.onVisibilityChange)
|
|
146
|
+
},
|
|
147
|
+
methods: {
|
|
148
|
+
activate () {
|
|
149
|
+
if (this.active) return
|
|
150
|
+
this.active = true
|
|
151
|
+
if (!document.hidden) this.refresh()
|
|
152
|
+
},
|
|
153
|
+
stop () {
|
|
154
|
+
clearTimeout(this.timer)
|
|
155
|
+
this.timer = null
|
|
156
|
+
if (this.pendingRequest) this.pendingRequest.cancel()
|
|
157
|
+
this.pendingRequest = null
|
|
158
|
+
this.loading = false
|
|
159
|
+
},
|
|
160
|
+
onVisibilityChange () {
|
|
161
|
+
if (document.hidden) this.stop()
|
|
162
|
+
else if (this.active && this.autoRefresh) this.refresh()
|
|
163
|
+
},
|
|
164
|
+
changeRefresh () {
|
|
165
|
+
this.stop()
|
|
166
|
+
if (this.autoRefresh) this.refresh()
|
|
167
|
+
},
|
|
168
|
+
async refresh () {
|
|
169
|
+
if (!this.active || document.hidden || this.loading) return
|
|
170
|
+
clearTimeout(this.timer)
|
|
171
|
+
this.loading = true
|
|
172
|
+
const request = markRaw(axios.CancelToken.source())
|
|
173
|
+
this.pendingRequest = request
|
|
174
|
+
try {
|
|
175
|
+
// FM_SYSTEM 返回共享的最新 JVM 快照;接口不查询业务数据、不即时采样。
|
|
176
|
+
const data = await this.$http.get({ entityId: 'FM_SYSTEM', action: 'getJvmMemoryStatus' }, { timeout: 8000, cancelToken: request.token })
|
|
177
|
+
if (this.pendingRequest !== request) return
|
|
178
|
+
if (!data || !data.instanceId || !data.state) throw new Error('监控数据格式异常')
|
|
179
|
+
if (this.snapshot && this.snapshot.instanceId !== data.instanceId) this.history = []
|
|
180
|
+
this.snapshot = data
|
|
181
|
+
this.error = ''
|
|
182
|
+
this.stale = data.enabled && data.sampledAt > 0 && Date.now() - data.sampledAt > Math.max(30000, data.sampleIntervalMs * 3)
|
|
183
|
+
const last = this.history[this.history.length - 1]
|
|
184
|
+
if (data.sampledAt > 0 && (!last || last.sampledAt !== data.sampledAt)) {
|
|
185
|
+
this.history.push({ sampledAt: data.sampledAt, pressureRatio: data.pressureRatio })
|
|
186
|
+
if (this.history.length > 60) this.history.shift()
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (this.pendingRequest === request && !axios.isCancel(error)) this.error = (error && (error.msg || error.message)) || '内存监控获取失败'
|
|
190
|
+
} finally {
|
|
191
|
+
if (this.pendingRequest === request) {
|
|
192
|
+
this.pendingRequest = null
|
|
193
|
+
this.loading = false
|
|
194
|
+
if (this.active && this.autoRefresh && !document.hidden) {
|
|
195
|
+
this.timer = setTimeout(this.refresh, Math.max(10000, (this.snapshot && this.snapshot.sampleIntervalMs) || 0))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
async captureJstack () {
|
|
201
|
+
if (this.jstackLoading) return
|
|
202
|
+
this.jstackLoading = true
|
|
203
|
+
try {
|
|
204
|
+
this.jstackSnapshot = await this.$http.get({ entityId: 'FM_SYSTEM', action: 'getJvmJstack' }, { timeout: 15000 })
|
|
205
|
+
this.jstackVisible = true
|
|
206
|
+
} catch (error) {
|
|
207
|
+
this.$message.error((error && (error.msg || error.message)) || 'jstack 获取失败')
|
|
208
|
+
} finally {
|
|
209
|
+
this.jstackLoading = false
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
async copyJstack () {
|
|
213
|
+
if (!this.jstackSnapshot || !this.jstackSnapshot.text) return
|
|
214
|
+
const text = this.jstackSnapshot.text
|
|
215
|
+
try {
|
|
216
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
217
|
+
await navigator.clipboard.writeText(text)
|
|
218
|
+
} else {
|
|
219
|
+
const input = document.createElement('textarea')
|
|
220
|
+
input.value = text
|
|
221
|
+
input.setAttribute('readonly', '')
|
|
222
|
+
input.style.position = 'fixed'
|
|
223
|
+
input.style.opacity = '0'
|
|
224
|
+
document.body.appendChild(input)
|
|
225
|
+
input.select()
|
|
226
|
+
document.execCommand('copy')
|
|
227
|
+
document.body.removeChild(input)
|
|
228
|
+
}
|
|
229
|
+
this.$message.success('jstack 已复制')
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.$message.error('复制失败,请手动选择文本复制')
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
bytes (value) {
|
|
235
|
+
if (value == null || value < 0) return '未限定'
|
|
236
|
+
return value >= 1073741824 ? `${(value / 1073741824).toFixed(2)} GB` : `${(value / 1048576).toFixed(1)} MB`
|
|
237
|
+
},
|
|
238
|
+
percent (used, max) { return max > 0 ? Math.min(100, Math.max(0, Math.round(used / max * 1000) / 10)) : 0 },
|
|
239
|
+
ratio (value) { return `${Math.round(value * 100)}%` },
|
|
240
|
+
riskType (value) { return value === 'HIGH' || value === 'DETECTED' ? 'danger' : value === 'WATCH' ? 'warning' : 'success' },
|
|
241
|
+
seconds (value) { return value == null || value < 0 ? '暂不可预测' : `${value} 秒` },
|
|
242
|
+
bytesPerSecond (value) { return `${this.bytes(value)}/秒` },
|
|
243
|
+
time (value) { return value > 0 ? new Date(value).toLocaleString() : '—' }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
</script>
|
|
247
|
+
|
|
248
|
+
<style scoped>
|
|
249
|
+
.jvm-memory { padding: 24px; background: var(--epx-bg-card, #f3f5fa); color: var(--epx-text, #303133); min-height: 100%; box-sizing: border-box; }
|
|
250
|
+
.jvm-memory h2, .jvm-memory h3, .jvm-memory p { margin: 0; }
|
|
251
|
+
.jvm-memory h2 { font-size: 21px; margin-bottom: 8px; }
|
|
252
|
+
.jvm-memory h3 { font-size: 15px; }
|
|
253
|
+
.jvm-memory small, .jvm-memory__toolbar p { color: var(--epx-text-secondary, #727987); font-size: 13px; }
|
|
254
|
+
.jvm-memory__toolbar, .jvm-memory__chart-title { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
|
|
255
|
+
.jvm-memory__toolbar { margin-bottom: 20px; }
|
|
256
|
+
.jvm-memory__actions, .jvm-memory__status { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
|
|
257
|
+
.jvm-memory__status { margin: 18px 0; font-size: 13px; color: var(--epx-text, #606266); }
|
|
258
|
+
.jvm-memory__cards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
|
|
259
|
+
.jvm-memory__summary-row + .jvm-memory__cards { margin-top: 18px; }
|
|
260
|
+
.jvm-memory__card, .jvm-memory__panel { background: var(--epx-bg-card, #fff); border: 1px solid var(--epx-border, #ebeef5); border-radius: 6px; padding: 20px; }
|
|
261
|
+
.jvm-memory__card { display: flex; flex-direction: column; gap: 12px; }
|
|
262
|
+
.jvm-memory__card strong { font-size: 27px; font-weight: 600; }
|
|
263
|
+
.jvm-memory__panel { margin-top: 18px; }
|
|
264
|
+
.jvm-memory__summary-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
|
|
265
|
+
.jvm-memory__summary-row .jvm-memory__panel { margin-top: 18px; }
|
|
266
|
+
.jvm-memory__chart-title span { color: var(--epx-text-secondary, #727987); font-size: 13px; }
|
|
267
|
+
.jvm-memory__chart { width: 100%; height: 160px; margin: 18px 0 10px; }
|
|
268
|
+
.jvm-memory__empty { padding: 40px 0; text-align: center; color: var(--epx-text-secondary, #909399); }
|
|
269
|
+
.jvm-memory__policy { line-height: 1.9; font-size: 13px; }
|
|
270
|
+
.jvm-memory__policy h3 { margin-bottom: 8px; }
|
|
271
|
+
.jvm-memory__policy small { overflow-wrap: anywhere; }
|
|
272
|
+
.jvm-memory__threads p, .jvm-memory__risk p { margin-top: 10px; color: var(--epx-text-secondary, #727987); font-size: 13px; }
|
|
273
|
+
.jvm-memory__thread-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-top: 16px; }
|
|
274
|
+
.jvm-memory__thread-grid span { padding: 12px; background: var(--epx-bg-card, #f7f8fa); border-radius: 4px; color: var(--epx-text, #606266); }
|
|
275
|
+
.jvm-memory__thread-grid strong { display: block; margin-top: 6px; color: var(--epx-text, #303133); font-size: 20px; }
|
|
276
|
+
.jvm-memory__thread-grid .is-danger { color: var(--el-color-danger, #f56c6c); }
|
|
277
|
+
.jvm-memory__jstack-meta { color: var(--epx-text, #606266); margin-bottom: 12px; }
|
|
278
|
+
.jvm-memory__dialog-title { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding-right: 42px; }
|
|
279
|
+
.jvm-memory__dialog-actions { display: inline-flex; gap: 6px; }
|
|
280
|
+
.jvm-memory__jstack { max-height: 60vh; overflow: auto; padding: 14px; background: #1f2430; color: #d7dae0; white-space: pre-wrap; word-break: break-word; }
|
|
281
|
+
@media (max-width: 1100px) { .jvm-memory__cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
282
|
+
@media (max-width: 900px) { .jvm-memory__summary-row { grid-template-columns: 1fr; } }
|
|
283
|
+
@media (max-width: 650px) { .jvm-memory { padding: 14px; } .jvm-memory__cards { grid-template-columns: 1fr; } .jvm-memory__thread-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .jvm-memory__toolbar, .jvm-memory__chart-title { align-items: flex-start; flex-direction: column; } }
|
|
284
|
+
</style>
|
package/src/views/Module.vue
CHANGED
|
@@ -1,210 +1,4 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<!-- 创建容器 -->
|
|
6
|
-
<epx-page
|
|
7
|
-
@changeRouter="changeRouter"
|
|
8
|
-
@back="back"
|
|
9
|
-
v-if="showPage"
|
|
10
|
-
:pageId="moduleRouter.pageId"
|
|
11
|
-
:flag="moduleRouter.flag"
|
|
12
|
-
:metaId="moduleRouter.metaId"
|
|
13
|
-
:component="moduleRouter.component"
|
|
14
|
-
:router="moduleRouter"
|
|
15
|
-
:current-query="moduleRouter"
|
|
16
|
-
/>
|
|
17
|
-
</div>
|
|
18
|
-
</template>
|
|
19
|
-
|
|
20
|
-
<script>
|
|
21
|
-
// 容器页面
|
|
22
|
-
import { DELETE_MODULE_ROUTER, SET_ROUTER } from '@epxing/core/src/store/actions'
|
|
23
|
-
import { mapState } from 'vuex'
|
|
24
|
-
export default {
|
|
25
|
-
name: 'epx-module',
|
|
26
|
-
props: {
|
|
27
|
-
router: {
|
|
28
|
-
type: Object,
|
|
29
|
-
default () {
|
|
30
|
-
return {}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
data () {
|
|
35
|
-
return {
|
|
36
|
-
showPage: false,
|
|
37
|
-
moduleIndexPage: '',
|
|
38
|
-
moduleRouters: [],
|
|
39
|
-
initRoute: null
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
computed: {
|
|
43
|
-
...mapState({
|
|
44
|
-
// routers: state => state.router.moduleRouters,
|
|
45
|
-
// route: state => state.router.moduleRoute,
|
|
46
|
-
user: state => state.user.user
|
|
47
|
-
}),
|
|
48
|
-
pageId () {
|
|
49
|
-
return this.route.pageId || this.moduleIndexPage
|
|
50
|
-
},
|
|
51
|
-
// moduleRouters () {
|
|
52
|
-
// const { children = [] } = this.router
|
|
53
|
-
// return children
|
|
54
|
-
// },
|
|
55
|
-
moduleRouter () {
|
|
56
|
-
return this.moduleRouters[this.moduleRouters.length - 1] || {}
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
watch: {
|
|
60
|
-
$routers: {
|
|
61
|
-
handler (routers) {
|
|
62
|
-
routers.forEach(item => {
|
|
63
|
-
if (item.key === this.router.key) {
|
|
64
|
-
const { children = [] } = item
|
|
65
|
-
this.moduleRouters = [...children]
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
},
|
|
69
|
-
deep: true
|
|
70
|
-
},
|
|
71
|
-
moduleRouter: {
|
|
72
|
-
handler (v, oldV) {
|
|
73
|
-
// 页面更改、刷新页面
|
|
74
|
-
if (!(v.pageId === oldV.pageId && v.flag === oldV.flag && v.metaId === oldV.metaId && v.component === oldV.component)) {
|
|
75
|
-
this.reloadPageDebounce()
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
deep: true
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
provide () {
|
|
82
|
-
return {
|
|
83
|
-
$changeRouter: this.changeRouter,
|
|
84
|
-
$reloadPage: this.reloadPage,
|
|
85
|
-
$backPage: this.$popModuleRoute
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
methods: {
|
|
89
|
-
reloadPage () {
|
|
90
|
-
this.showPage = false
|
|
91
|
-
this.$nextTick(() => {
|
|
92
|
-
if (this.moduleRouter.pageId || this.moduleRouter.flag || this.moduleIndexPage || this.moduleRouter.component) {
|
|
93
|
-
const timer = setTimeout(() => {
|
|
94
|
-
this.showPage = true
|
|
95
|
-
clearTimeout(timer)
|
|
96
|
-
}, 0)
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
},
|
|
100
|
-
getModule () {
|
|
101
|
-
let { moduleId } = this.$route.query
|
|
102
|
-
if (this.router.query) {
|
|
103
|
-
moduleId = this.router.query.moduleId
|
|
104
|
-
}
|
|
105
|
-
if (!moduleId) {
|
|
106
|
-
if (this.user.currentRoleEntity) {
|
|
107
|
-
const { MENU: USER_MENU } = this.user.currentRoleEntity.data
|
|
108
|
-
moduleId = USER_MENU
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// 获取当前模块信息
|
|
112
|
-
// 如果当前模块没绑定页面,获取其子模块页面
|
|
113
|
-
this.$http.get({
|
|
114
|
-
entityId: 'FM_MODULE',
|
|
115
|
-
id: moduleId
|
|
116
|
-
})
|
|
117
|
-
.then(data => {
|
|
118
|
-
if (data) {
|
|
119
|
-
const module = data.data || {}
|
|
120
|
-
const date = new Date().getTime()
|
|
121
|
-
// 初始化模块首页
|
|
122
|
-
// this.$pushModuleRoute({
|
|
123
|
-
// pageId: module.MAIN_PAGE
|
|
124
|
-
// })
|
|
125
|
-
if (!this.router.query) {
|
|
126
|
-
this.$createPage({
|
|
127
|
-
key: this.$routers.length === 0 ? 'nav-' + date : date,
|
|
128
|
-
moduleId: module.ID,
|
|
129
|
-
title: module.NAME,
|
|
130
|
-
openType: '1'
|
|
131
|
-
})
|
|
132
|
-
this.initRoute = {
|
|
133
|
-
key: this.$routers.length === 0 ? 'nav-' + date : date,
|
|
134
|
-
moduleId: module.ID,
|
|
135
|
-
title: module.NAME
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
if (!this.router.children || this.router.children.length === 0) {
|
|
139
|
-
const router = {
|
|
140
|
-
...this.router,
|
|
141
|
-
children: [
|
|
142
|
-
{ key: date, pageId: module.MAIN_PAGE, moduleId: moduleId }
|
|
143
|
-
]
|
|
144
|
-
}
|
|
145
|
-
this.initRoute = { key: date, pageId: module.MAIN_PAGE, moduleId: moduleId }
|
|
146
|
-
this.$store.commit(SET_ROUTER, { key: this.router.key, router })
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// this.$createPage({
|
|
150
|
-
// pageId: module.MAIN_PAGE
|
|
151
|
-
// })
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
},
|
|
155
|
-
addListenerRouter () {
|
|
156
|
-
// ⚠️ T17 修复轮 2(定向复审 N4):这里原来还有两行
|
|
157
|
-
// this.$bus.on('push', this.pushModuleRoute) / this.$bus.on('pop', this.popModuleRoute)
|
|
158
|
-
// 两个 handler **在本组件里恒为 `undefined`**:它们全仓只定义在 `@epxing/core/src/Container.vue`
|
|
159
|
-
// (:204-213),而本组件**没有 mixins/extends**,globalProperties 里也没有。
|
|
160
|
-
// 往总线塞 `undefined` 只会让 `emit('push')` 抛 `h is not a function`,从来没做过事 ⇒ 删掉。
|
|
161
|
-
// (两个不带 handler 的 off 是挂载侧既有行为,本次不动。)
|
|
162
|
-
this.$bus.off('push')
|
|
163
|
-
this.$bus.off('pop')
|
|
164
|
-
},
|
|
165
|
-
removeListenerRouter () {
|
|
166
|
-
// ⚠️ T17 修复轮 2(定向复审 N4,顶回②的安全性论断被推翻):修复轮 1 认定这里「本来就传了
|
|
167
|
-
// handler,是安全的」。计数没错(该按原修法改的确实是 5 个文件不是 6 个),**但安全性论断错了**:
|
|
168
|
-
// `this.pushModuleRoute` / `this.popModuleRoute` 都是 `undefined`,而 `vue.bus.js` 的 off
|
|
169
|
-
// 判的是 `if (!callback)` ⇒ `off(event, undefined)` 走「删光该事件全部监听」那条分支,
|
|
170
|
-
// 与裸 `off('push')` 完全等价、一样连坐删掉别人的监听。
|
|
171
|
-
// 本组件从来没有成功注册过任何 handler,没有东西可摘 ⇒ 两行 off 一并删掉,只留 commit。
|
|
172
|
-
// 把二级路由清空
|
|
173
|
-
this.$store.commit(DELETE_MODULE_ROUTER)
|
|
174
|
-
},
|
|
175
|
-
changeRouter (v) {
|
|
176
|
-
// const { mode, router } = v
|
|
177
|
-
// if (mode === 'replace') {
|
|
178
|
-
// this.$popModuleRoute()
|
|
179
|
-
// }
|
|
180
|
-
// this.$pushModuleRoute(router)
|
|
181
|
-
},
|
|
182
|
-
back () {
|
|
183
|
-
this.$popModuleRoute()
|
|
184
|
-
}
|
|
185
|
-
},
|
|
186
|
-
created () {
|
|
187
|
-
this.getModule()
|
|
188
|
-
this.addListenerRouter()
|
|
189
|
-
this.reloadPageDebounce = this.$_.debounce(this.reloadPage, 20)
|
|
190
|
-
},
|
|
191
|
-
beforeUnmount () {
|
|
192
|
-
this.removeListenerRouter()
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
</script>
|
|
196
|
-
|
|
197
|
-
<style scoped>
|
|
198
|
-
.module {
|
|
199
|
-
width: 100%;
|
|
200
|
-
height: 100%;
|
|
201
|
-
padding: 16px;
|
|
202
|
-
box-sizing: border-box;
|
|
203
|
-
position: relative;
|
|
204
|
-
}
|
|
205
|
-
@media only screen and (max-width: 992px) {
|
|
206
|
-
.module{
|
|
207
|
-
padding: 0;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
</style>
|
|
1
|
+
<script>
|
|
2
|
+
// 保留业务包旧路径,基础路由页统一由 core 提供。
|
|
3
|
+
export { default } from '@epxing/core/src/views/Module.vue'
|
|
4
|
+
</script>
|
|
@@ -30,6 +30,14 @@
|
|
|
30
30
|
<el-button size="small" @click="view = 'intake'">⬆ 上传原始文件</el-button>
|
|
31
31
|
<el-button size="small" @click="view = 'config'">⚙ 配置</el-button>
|
|
32
32
|
</template>
|
|
33
|
+
<template #doc-ops>
|
|
34
|
+
<span class="wiki-binding__doc-sep"></span>
|
|
35
|
+
<el-button v-if="space.SOURCE_TYPE && space.SOURCE_TYPE !== 'db'" type="success" plain size="small" icon="el-icon-refresh" :loading="syncing" @click="doSync">同步远端</el-button>
|
|
36
|
+
<el-button type="primary" size="small" icon="el-icon-s-tools" :loading="inspecting" @click="doInspect">
|
|
37
|
+
{{ inspecting ? '检查中…' : '体检与索引维护' }}
|
|
38
|
+
</el-button>
|
|
39
|
+
<el-button type="primary" plain size="small" icon="el-icon-share" @click="shareWiki">分享</el-button>
|
|
40
|
+
</template>
|
|
33
41
|
</epx-wiki-site>
|
|
34
42
|
</div>
|
|
35
43
|
|
|
@@ -205,6 +213,8 @@ export default {
|
|
|
205
213
|
// 「这个空间里到底有什么」。配置收进右上角按钮
|
|
206
214
|
view: 'content',
|
|
207
215
|
contentHeight: 0,
|
|
216
|
+
syncing: false,
|
|
217
|
+
inspecting: false,
|
|
208
218
|
form: { scope: '', retrieval: 'exact' }
|
|
209
219
|
}
|
|
210
220
|
},
|
|
@@ -393,6 +403,31 @@ export default {
|
|
|
393
403
|
})
|
|
394
404
|
},
|
|
395
405
|
|
|
406
|
+
shareWiki () {
|
|
407
|
+
const site = this.$refs.wikiSite
|
|
408
|
+
if (site) site.share()
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
doSync () {
|
|
412
|
+
this.syncing = true
|
|
413
|
+
this.$http.update({ action: 'sync', entityId: WIKI_META, wikiId: this.space.ID })
|
|
414
|
+
.then(d => {
|
|
415
|
+
this.$message.success((d && d.message) || '同步完成')
|
|
416
|
+
this.refreshWiki()
|
|
417
|
+
this.load()
|
|
418
|
+
})
|
|
419
|
+
.catch(e => this.$message.error((e && e.message) || '同步失败'))
|
|
420
|
+
.finally(() => { this.syncing = false })
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
doInspect () {
|
|
424
|
+
this.inspecting = true
|
|
425
|
+
this.$http.update({ action: 'inspect', entityId: WIKI_META, wikiId: this.space.ID, maintain: 'true' })
|
|
426
|
+
.then(d => this.$message.success((d && d.message) || '体检与索引维护完成'))
|
|
427
|
+
.catch(e => this.$message.error((e && e.message) || '体检与索引维护失败'))
|
|
428
|
+
.finally(() => { this.inspecting = false })
|
|
429
|
+
},
|
|
430
|
+
|
|
396
431
|
/** 从入库记录点击生成页时,回到内容视图并打开同一页。 */
|
|
397
432
|
openGeneratedPage (path) {
|
|
398
433
|
this.view = 'content'
|
|
@@ -410,6 +445,13 @@ export default {
|
|
|
410
445
|
.wiki-binding {
|
|
411
446
|
padding: var(--padding-large) 0;
|
|
412
447
|
|
|
448
|
+
&__doc-sep {
|
|
449
|
+
flex: none;
|
|
450
|
+
width: 1px;
|
|
451
|
+
height: 16px;
|
|
452
|
+
background: var(--epx-bg, #e3e3e3);
|
|
453
|
+
}
|
|
454
|
+
|
|
413
455
|
// 未绑定态:虚线框 + 居中,与设计稿 .bindbox 一致。
|
|
414
456
|
// 虚线是有意的——它表示「这里还空着,等你填」,实线框看着像一块已经配好的内容
|
|
415
457
|
&__bindbox {
|
package/types/app.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { App as default } from './index'
|
package/types/dept-find.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { DeptFind as default } from './index'
|
|
1
|
+
export { DeptFind as default } from './index'
|
package/types/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import type { App, DefineComponent, Plugin } from 'vue'
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
9
|
-
export declare const
|
|
10
|
-
export declare
|
|
11
|
-
declare
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
export declare const
|
|
1
|
+
import type { App as VueApp, DefineComponent, Plugin } from 'vue'
|
|
2
|
+
export declare const App: DefineComponent<any, any, any>
|
|
3
|
+
export declare const UserFind: DefineComponent<any, any, any>
|
|
4
|
+
export declare const DeptFind: DefineComponent<any, any, any>
|
|
5
|
+
export declare const HeaderNav: DefineComponent<any, any, any>
|
|
6
|
+
export declare const EntityTreeManage: DefineComponent<any, any, any>
|
|
7
|
+
export declare const PageCatalog: DefineComponent<any, any, any>
|
|
8
|
+
export declare const ReportFormList: DefineComponent<any, any, any>
|
|
9
|
+
export declare const ReportFormListPage: DefineComponent<any, any, any>
|
|
10
|
+
export declare const ExcelPreviewPage: DefineComponent<any, any, any>
|
|
11
|
+
export declare function install(app: VueApp): void
|
|
12
|
+
declare const plugin: Plugin
|
|
13
|
+
export default plugin
|
|
14
|
+
export declare const systemComponents: Record<string, import('vue').Component>
|
|
15
|
+
export declare const routeComponents: Record<string, import('vue').Component>
|
package/types/popup.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const popup: import('vue').Plugin
|
|
2
|
-
export default popup
|
|
1
|
+
declare const popup: import('vue').Plugin
|
|
2
|
+
export default popup
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { routeComponents as default } from './index'
|
|
1
|
+
export { routeComponents as default } from './index'
|
package/types/user-find.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { UserFind as default } from './index'
|
|
1
|
+
export { UserFind as default } from './index'
|
|
Binary file
|