获取指标说明的能力

This commit is contained in:
2025-05-21 15:51:44 +08:00
parent 06c351a956
commit 1f329e3b2a
131 changed files with 4461 additions and 3126 deletions

View File

@@ -1,4 +1,5 @@
spring:
cache.type: simple
datasource:
postgre:
jdbc-url: jdbc:postgresql://localhost:5432/verich

View File

@@ -383,7 +383,17 @@ blockquote.layui-elem-quote {
box-shadow: 1px 1px 10px rgba(0, 0, 0, .1);
border-radius: 0;
}
.layui-layer>.layui-layer-content {
.layui-layer-indexDetail>.layui-layer-content>*:not(:last-child) {
margin-bottom: 1em;
}
.layui-layer-indexDetail>.layui-layer-content>* {
max-width: 100%;
}
.layui-layer-indexDetail>.layui-layer-content>img {
margin: auto;
display: block
}
.layui-layer-adminRight>.layui-layer-content {
overflow: visible !important;
}
.layui-anim-rl {
@@ -442,4 +452,15 @@ fieldset>legend:before {
font-weight: bold;
margin-right: .25em;
color: rgb(22, 183, 119)
}
.ipInfo {
color: #000
}
.ipInfo>dl {
max-width: 100px
}
.ipInfo .ipv6>a {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -1,14 +1,75 @@
if (!window.Helper) {window.Helper = {}}
if (!window.Helper) { window.Helper = {} }
window.Helper = {
emoneyPeriodToName: function(x) {
if (x < 10000) return `${x} 分钟`;
if (x == 10000) return '日线';
if (x == 20000) return '周线';
if (x == 30000) return '月线';
if (x == 40000) return '季线';
if (x == 50000) return '半年线';
if (x == 60000) return '年线';
},
allEmoneyPeriods: [1, 5, 15, 30, 60, 120, 10000, 20000, 30000, 40000, 50000, 60000]
emoneyPeriodToName: function(x) {
if (x < 10000) return `${x} 分钟`;
if (x == 10000) return '日线';
if (x == 20000) return '周线';
if (x == 30000) return '月线';
if (x == 40000) return '季线';
if (x == 50000) return '半年线';
if (x == 60000) return '年线';
},
allEmoneyPeriods: [1, 5, 15, 30, 60, 120, 10000, 20000, 30000, 40000, 50000, 60000],
showIndexDetailLayer: async function(obj, forceRefresh) {
// obj: {indexCode: _, indexName: _}
var layer = layui.layer;
var load = layer.load(2);
var url = !!forceRefresh ?
'/admin/v1/manage/indexInfo/forceRefreshAndGetIndexDetail?indexCode=' :
'/admin/v1/manage/indexInfo/getIndexDetail?indexCode=';
url += obj.indexCode;
var res = await (await fetch(url)).json();
if (res.ok) {
// build content
var html = [];
for (var i = 0; i < res.data.details.length; i++) {
var detail = res.data.details[i];
if (detail.type == 'TITLE') {
html.push('<h3>');
html.push(Helper.trimChars(detail.content, ':'));
html.push('</h3>');
}
else if (detail.type == 'IMAGE') {
if (detail.content.indexOf('data:image/') == 0 &&
detail.content.indexOf('base64') != -1) {
html.push('<img src="');
html.push(detail.content);
html.push('">');
}
}
else {
html.push('<p>');
html.push(detail.content.replaceAll(/\n+/g, '<br>'));
html.push('</p>');
}
}
console.log(res.data);
layer.open({
title: obj.indexName + '指标说明',
content: html.join(''),
skin: 'layui-layer-indexDetail',
area: ['520px', '320px'],
btn: ['刷新', '确定'],
btn1: function(index, layero, that) {
layer.close(index);
Helper.showIndexDetailLayer(obj, !0);
},
success: function(layero, index) {
var btns = layero.find('.layui-layer-btn>*');
btns[0].setAttribute('class', 'layui-layer-btn1');
btns[1].setAttribute('class', 'layui-layer-btn0');
}
})
}
else {
Dog.error({ msg: res && res.message || '服务器错误' });
}
layer.close(load);
},
trimChars: function (str, chars) {
const escaped = chars.split('').map(c => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('');
const pattern = new RegExp(`^[${escaped}]+|[${escaped}]+$`, 'g');
return str.replace(pattern, '');
}
}

View File

@@ -224,29 +224,34 @@
el.value = !!el.checked;
});
})
form.on('submit(submit)', function(data){
var field = data.field;
document.querySelectorAll('input[lay-skin="switch"]').forEach(checkbox => {
field[checkbox.getAttribute('name')] = checkbox.getAttribute('value') == 'true'
});
form.on('submit(submit)', async function(data){
$.ajax({
url: location.href,
data: JSON.stringify(field),
contentType: 'application/json',
method: 'POST',
success: function (result) {
Dog.success({
onClose: () => location.reload()
})
},
error: function (res) {
var r = res.responseJSON;
Dog.error({
msg: r && r.data,
})
}
layui.layer.confirm('若设备信息修改,会导致清空鉴权信息,确定吗?', async function() {
var load = layui.layer.load(2);
var field = data.field;
document.querySelectorAll('input[lay-skin="switch"]').forEach(checkbox => {
field[checkbox.getAttribute('name')] = checkbox.getAttribute('value') == 'true'
});
try {
var res = await (await fetch(location.href, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(field)
})).json();
if (res && res.ok) {
Dog.success({onClose: () => location.reload()});
}
else {
Dog.error({msg: res && res.data || '服务器错误'})
}
}
catch (e) {
Dog.error({msg: e})
}
layui.layer.close(load);
})
});
form.render();

View File

@@ -63,6 +63,20 @@
<dd><a th:href="@{/admin/v1/logout}">退出登录</a></dd>
</dl>
</li>
<li class="layui-nav-item ipInfo" style="float:right;margin-right: 1px" lay-unselect="">
<a id="ipThroughProxy" href="javascript:manualRefreshIp()" title="立即刷新">
IP 属地:
<span th:if="${@proxyConfig.ipInfo == null}" class="layui-badge layui-bg-cyan">加载中...</span>
<span th:if="${@proxyConfig.ipInfo != null}" class="layui-badge layui-bg-cyan">[[${@proxyConfig.ipInfo.geoString}]]</span>
</a>
<th:block th:if="${@proxyConfig.ipInfo != null}">
<dl class="layui-nav-child">
<dd class="ip"><a title="点击复制">[[${@proxyConfig.ipInfo.ip}]]</a></dd>
<dd class="ipv6" th:if="${@proxyConfig.ipInfo.ipv6 != null}"><a title="点击复制">[[${@proxyConfig.ipInfo.ipv6}]]</a></dd>
</dl>
</th:block>
</li>
</ul>
<script type="text/html" id="editUser">
<div class="layui-form" style="margin:10px 15px" id="editUserForm" lay-filter="editUserForm">
@@ -111,13 +125,47 @@
<script>
let refreshTimer = null;
document.querySelectorAll('dd.ip,dd.ipv6').forEach(el => {
el.addEventListener('click', () => {
let text = el.querySelector('a').textContent;
navigator.clipboard.writeText(text).then(() => {
Dog.success({msg: '复制成功'})
}).catch(err => {
Dog.error({msg: '复制失败'})
});
})
});
async function refreshIpThroughProxy() {
try {
let geoEl =
document.querySelector('#ipThroughProxy>span');
geoEl.textContent = '加载中...';
let res = await (await fetch('/admin/v1/config/proxy/refreshIpThroughProxy')).json();
if (res.ok) {
let ip = res.data.string || '获取代理 IP 失败';
document.getElementById('ipThroughProxy').textContent = ip;
geoEl.textContent = res.data.geoString || '获取失败';
let ipMenu = document.querySelector('.ipInfo>dl');
let genIpEL = (clazz, title) => {
let el = ipMenu.querySelector('.' + selector);
if (!el) {
el = document.createElement('dd');
let a = document.createElement('a');
a.setAttribute('title', title || '点击复制');
el.classList.add(clazz);
el.appendChild(a);
ipMenu.appendChild(el);
}
return el;
};
let ipEl = genIpEL('ip');
let ipv6El = ipMenu.querySelector('.ipv6');
ipEl.querySelector('a').textContent = res.data.ip;
if (res.data.ipv6) {
ipv6El = ipv6El || genIpEL('ipv6');
ipv6El.querySelector('a').textContent = res.data.ipv6;
}
else if (ipv6El) {
ipv6El.remove();
}
}
} catch (e) {
console.error('刷新失败:', e);
@@ -221,10 +269,6 @@
</script>
</th:block>
<div th:fragment="feet" class="layui-trans layadmin-user-login-footer">
<a id="ipThroughProxy" href="javascript:manualRefreshIp()" title="立即刷新">
[[${@proxyConfig.ipInfo == null ? '当前 IP 信息:加载中...' :
@proxyConfig.ipInfo.string}]]
</a><br>
Driven by Latte<br />
&copy;2025-[[${#dates.format(new java.util.Date().getTime(),'yyyy')}]]
<a href="#">Latte</a>

View File

@@ -67,8 +67,8 @@
cols: [ [
{field: 'indexCode', title: '指标代码'},
{field: 'indexName', title: '指标名称'},
{field: 'isCalc', title: '是否传统算法', templet: (d) => {
return d.isCalc ?
{field: 'isCalc', title: '是否在线指标', templet: (d) => {
return !d.isCalc ?
'<i class="op-green fa-solid fa-circle-check"></i>' :
'<i class="op-red fa-solid fa-circle-exclamation"></i>';
}},
@@ -102,13 +102,13 @@
}
}
})
table.on('tool(indexInfos)', (obj) => {
table.on('tool(indexInfos)', async function (obj) {
if (obj.event == 'detail') {
Helper.showIndexDetailLayer(obj.data);
}
})
})
});
</script>
<th:block th:replace="~{admin/v1/manage/indexInfo/updateForm::indexInfoUpdateForm}"></th:block>
</body>

View File

@@ -114,7 +114,32 @@
}
}
});
enableCodeMirrorResize(editor, document.getElementById("editor-wrapper"))
enableCodeMirrorResize(editor, document.getElementById("editor-wrapper"));
let submitBtn = document.querySelector('[lay-filter="submitIndexConfigFile"]');
submitBtn.addEventListener('click', async function(e) {
e.preventDefault();
let res = await (await fetch('/admin/v1/config/indexInfo', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
configIndOnlineUrl: document.querySelector('[name="configIndOnlineUrl"]').value,
configIndOnline: JSON.parse(editor.getValue())
})
})).json();
if (res.ok) {
Dog.success({
onClose: () => location.reload()
})
}
else {
Dog.error({
msg: res && res.data,
})
}
})
}
})
}
@@ -160,5 +185,6 @@
document.body.style.cursor = "default";
});
}
</script>
</th:block>

View File

@@ -25,6 +25,9 @@
<option value="">选择更新指标</option>
</select>
</div>
<div class="layui-inline">
<a style="display:none" href="#" id="showIndexDetailLayer" onclick="javascript:showIndexDetailLayer(this, event)"><i class="fa fa-solid fa-circle-question"></i></a>
</div>
</div>
</div>
<div id="params">
@@ -112,6 +115,13 @@
}
})
})
function showIndexDetailLayer(el, event) {
event.preventDefault();
Helper.showIndexDetailLayer({
indexCode: el.dataset.indexCode,
indexName: el.dataset.indexName
});
}
function openEditForm(r) {
if (r && r.ok) {
window.editLayer = layui.layer.open({
@@ -149,11 +159,21 @@
cssPath: '/admin/v1/static/css/cron.css'
});
layui.form.on('select(indexCodeFilter)', async function (obj) {
const paramsEl = document.getElementById('params');
const periodsEl = document.getElementById('periods');
paramsEl.textContent = periodsEl.textContent = '';
const paramsEl = document.getElementById('params'); // 参数选择控件
const periodsEl = document.getElementById('periods'); // 时间粒度选择控件
paramsEl.textContent = periodsEl.textContent = ''; // 清除参数和时间粒度选择控件
const dataset = obj.elem.querySelector(`[value="${obj.value}"]`).dataset;
const detailTriggerEl = document.getElementById('showIndexDetailLayer');
if (!dataset || !dataset.indInfo) {
// 未存在 dataset/.indInfo, 则去除可能存在的指标详情按钮
detailTriggerEl.style.display = 'none';
return;
}
detailTriggerEl.style.display = '';
detailTriggerEl.dataset.indexCode = obj.value;
detailTriggerEl.dataset.indexName = dataset.indName;
const indInfo =
JSON.parse(obj.elem.querySelector(`[value="${obj.value}"]`).dataset.indInfo);
JSON.parse(dataset.indInfo);
const paramTemplet = document.getElementById('paramTemplet');
const periodsTemplet = document.getElementById('periodsTemplet');
if (indInfo.indParam) {
@@ -211,6 +231,7 @@
option.textContent = `${key} ${jo.indIdNameConfig[key]}`;
option.selected = r.data.indexCode == key;
option.dataset.indInfo = JSON.stringify(indInfo);
option.dataset.indName = jo.indIdNameConfig[key];
optionsFragment.appendChild(option);
})