⚲
專案
一般
配置概況
登入
註冊
網站首頁
專案清單
說明
搜尋
:
Grafana報表系統
全部的專案
Grafana報表系統
概觀
活動
議題清單
新聞
下載 (14.2 KB)
工作單 #200
» P9702001-商店交易,設備統計(USD).json
marlboro chu, 2025-06-30 01:59
{
"result"
:
{
"id"
:
613
,
"orgId"
:
1
,
"folderId"
:
143
,
"folderUid"
:
"bempo37hjqtq8d"
,
"uid"
:
"aemqkkrcvicqoa"
,
"name"
:
"P9702001-商店交易,設備統計(USD)"
,
"kind"
:
1
,
"type"
:
"marcusolsson-dynamictext-panel"
,
"description"
:
""
,
"model"
:
{
"datasource"
:
{
"type"
:
"yesoreyeram-infinity-datasource"
,
"uid"
:
"aegaeyjq187b4e"
},
"description"
:
""
,
"fieldConfig"
:
{
"defaults"
:
{
"decimals"
:
2
,
"thresholds"
:
{
"mode"
:
"absolute"
,
"steps"
:
[
{
"color"
:
"green"
,
"value"
:
null
}
]
},
"unit"
:
"locale"
},
"overrides"
:
[
{
"matcher"
:
{
"id"
:
"byName"
,
"options"
:
"deviceCollection"
},
"properties"
:
[
{
"id"
:
"unit"
,
"value"
:
"percentunit"
}
]
}
]
},
"gridPos"
:
{
"h"
:
8
,
"w"
:
24
,
"x"
:
0
,
"y"
:
13
},
"id"
:
9702001
,
"options"
:
{
"afterRender"
:
"let currentSortField = 'merchantName'; // 預設排序欄位
\r\n
let currentSortOrder = 'desc'; // 預設排序方向
\r\n\r\n
function generateMerchantItems(data, sortField = 'merchantName', sortOrder = 'asc') {
\r\n\r\n
const tableBody = document.getElementById('merchant-table-body');
\r\n
tableBody.innerHTML = ''; // 清空舊資料
\r\n\r\n
// 排序
\r\n
data.sort((a, b) => {
\r\n
const rawA = a[sortField];
\r\n
const rawB = b[sortField];
\r\n\r\n
// 嘗試把逗號移除,轉成數字
\r\n
const numA = typeof rawA === 'string' ? Number(rawA.replace(/,/g, '')) : rawA;
\r\n
const numB = typeof rawB === 'string' ? Number(rawB.replace(/,/g, '')) : rawB;
\r\n\r\n
// 自訂非數值風險等級排序
\r\n
const RISK_ORDER_ASC = ['high', 'mid', 'low', 'nan'];
\r\n
const RISK_ORDER_DESC = ['low', 'mid', 'high', 'nan'];
\r\n\r\n
const isMerchantNameField = sortField === 'merchantName';
\r\n
if (isMerchantNameField) {
\r\n
// 將數字轉換為風險等級
\r\n
const mapName = val => {
\r\n
if (val === '1' || val === 1) return 'low';
\r\n
if (val === '2' || val === 2) return 'mid';
\r\n
if (val === '3' || val === 3) return 'high';
\r\n
if (!val) return 'nan';
\r\n
return String(val).toLowerCase();
\r\n
};
\r\n
const valA = mapName(rawA);
\r\n
const valB = mapName(rawB);
\r\n\r\n
const order = sortOrder === 'asc' ? RISK_ORDER_ASC : RISK_ORDER_DESC;
\r\n
const indexA = order.indexOf(valA);
\r\n
const indexB = order.indexOf(valB);
\r\n
return indexA - indexB;
\r\n
}
\r\n\r\n\r\n
if (!isNaN(numA) && !isNaN(numB)) {
\r\n
// 都是有效數字,就用數字比較
\r\n
return sortOrder === 'asc' ? numA - numB : numB - numA;
\r\n
} else {
\r\n
// 有不是數字,就當字串比
\r\n
return sortOrder === 'asc'
\r\n
? String(rawA).localeCompare(String(rawB))
\r\n
: String(rawB).localeCompare(String(rawA));
\r\n
}
\r\n
});
\r\n\r\n
// 產生每一列
\r\n
data.forEach(item => {
\r\n
const row = document.createElement('tr');
\r\n\r\n
// 商店名稱
\r\n
if (item.merchantName == '1') item.merchantName = 'low';
\r\n
else if (item.merchantName == '2') item.merchantName = 'mid';
\r\n
else if (item.merchantName == '3') item.merchantName = 'high';
\r\n\r\n\r\n
const merchantNameCell = document.createElement('td');
\r\n
if (item.merchantName === 'high') {
\r\n
merchantNameCell.className = 'kpi-merchant-highname';
\r\n
row.classList.add('kpi-merchant-highrow');
\r\n
} else
\r\n
merchantNameCell.className = 'kpi-merchant-name';
\r\n
merchantNameCell.textContent = item.merchantName;
\r\n
row.appendChild(merchantNameCell);
\r\n\r\n
// 其他欄位
\r\n
const kpiFields = [
\r\n
item.txCount,
\r\n
item.txCountDevice,
\r\n
item.txAmount,
\r\n
item.txCountUniDevice,
\r\n
item.deviceCollection,
\r\n
item.authResultY
\r\n
];
\r\n\r\n
kpiFields.forEach(value => {
\r\n
const cell = document.createElement('td');
\r\n
cell.className = 'kpi-value';
\r\n
cell.textContent = value;
\r\n
row.appendChild(cell);
\r\n
});
\r\n\r\n
tableBody.appendChild(row);
\r\n
});
\r\n
}
\r\n\r\n
function setupMerchantTableHeader(data) {
\r\n
const tableHeader = document.getElementById('merchant-table-header');
\r\n
tableHeader.innerHTML = ''; // 清空舊表頭
\r\n\r\n
// 中文表頭設定
\r\n
const headers = [
\r\n
{ text: '風險等級', field: 'merchantName' },
\r\n
{ text: '交易筆數', field: 'txCount' },
\r\n
{ text: '交易筆數(有設備資訊)', field: 'txCountDevice' },
\r\n
{ text: '交易金額(USD)', field: 'txAmount' },
\r\n
{ text: '設備數量', field: 'txCountUniDevice' },
\r\n
{ text: '設備資訊收集率%', field: 'deviceCollection' },
\r\n
{ text: '授權結果(Y)', field: 'authResultY' },
\r\n
];
\r\n\r\n
const headerRow = document.createElement('tr');
\r\n\r\n
headers.forEach(header => {
\r\n
const th = document.createElement('th');
\r\n
th.textContent = header.text;
\r\n
th.style.cursor = 'pointer'; // 鼠標提示可以點
\r\n
th.addEventListener('click', () => {
\r\n
if (currentSortField === header.field) {
\r\n
currentSortOrder = currentSortOrder === 'asc' ? 'desc' : 'asc';
\r\n
} else {
\r\n
currentSortField = header.field;
\r\n
currentSortOrder = 'asc';
\r\n
}
\r\n
generateMerchantItems(data, currentSortField, currentSortOrder);
\r\n
updateTableHeaderSortIndicator(headers);
\r\n
});
\r\n
th.dataset.field = header.field; // 記錄欄位屬性 (方便加箭頭)
\r\n
headerRow.appendChild(th);
\r\n
});
\r\n\r\n
tableHeader.appendChild(headerRow);
\r\n
}
\r\n\r\n
function updateTableHeaderSortIndicator(headers) {
\r\n
const thElements = document.querySelectorAll('#merchant-table-header th');
\r\n
thElements.forEach(th => {
\r\n
const field = th.dataset.field;
\r\n
const header = headers.find(h => h.field === field);
\r\n
if (!header) return;
\r\n\r\n
if (field === currentSortField) {
\r\n
th.textContent = `${header.text} ${currentSortOrder === 'asc' ? '▲' : '▼'}`;
\r\n
} else {
\r\n
th.textContent = header.text;
\r\n
}
\r\n
});
\r\n
}
\r\n\r\n
function generateMerchantFooter(data) {
\r\n
const tableFooter = document.getElementById('merchant-table-footer');
\r\n
tableFooter.innerHTML = ''; // 清空舊表尾
\r\n\r\n
const footerRow = document.createElement('tr');
\r\n\r\n
// 第一欄:標題「加總」
\r\n
const totalLabelCell = document.createElement('td');
\r\n
totalLabelCell.textContent = 'Total';
\r\n
totalLabelCell.style.textAlign = 'right';
\r\n
footerRow.appendChild(totalLabelCell);
\r\n\r\n
// 要加總的欄位順序(數值)
\r\n
const sumFields = ['txCount', 'txCountDevice', 'txAmount', 'txCountUniDevice', 'deviceCollection', 'authResultY'];
\r\n\r\n
sumFields.forEach(field => {
\r\n
const sum = data.reduce((acc, cur) => {
\r\n
const raw = cur[field];
\r\n
const num = typeof raw === 'string' ? Number(raw.replace(/,/g, '')) : raw;
\r\n
return acc + (isNaN(num) ? 0 : num);
\r\n
}, 0);
\r\n\r\n
const td = document.createElement('td');
\r\n
if (sum === 0)
\r\n
td.textContent = '';
\r\n
else
\r\n
td.textContent = sum.toLocaleString();
\r\n
footerRow.appendChild(td);
\r\n
});
\r\n\r\n
tableFooter.appendChild(footerRow);
\r\n
}
\r\n\r\n
// 初始化
\r\n
if (Array.isArray(context.data) && context.data.length > 0) {
\r\n
const tableData = context.data[0];
\r\n
setupMerchantTableHeader(tableData);
\r\n
generateMerchantItems(tableData);
\r\n
generateMerchantFooter(tableData);
\r\n
}
\r\n
"
,
"content"
:
"<table id=
\"
merchant-table
\"
class=
\"
merchant-table
\"
>
\n
<thead id=
\"
merchant-table-header
\"
>
\n
<!-- JS 會產生表頭 -->
\n
</thead>
\n
<tbody id=
\"
merchant-table-body
\"
>
\n
<!-- Data will be populated by JavaScript -->
\n
</tbody>
\n
<tfoot id=
\"
merchant-table-footer
\"
></tfoot>
\n
</table>"
,
"contentPartials"
:
[
],
"defaultContent"
:
"The query didn't return any results."
,
"editor"
:
{
"format"
:
"auto"
,
"language"
:
"markdown"
},
"editors"
:
[
"afterRender"
,
"styles"
],
"externalStyles"
:
[
],
"helpers"
:
""
,
"renderMode"
:
"data"
,
"styles"
:
"<style>
\r\n
/* Grid 外框(非表格) */
\r\n
.kpi-grid {
\r\n
display: grid;
\r\n
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
\r\n
gap: 24px;
\r\n
padding: 20px;
\r\n
font-family: 'Segoe UI', 'Roboto Mono', 'Noto Sans TC', monospace;
\r\n
}
\r\n\r\n
/* 每個 KPI 區塊 */
\r\n
.kpi-item {
\r\n
border: 1px solid rgba(100, 181, 246, 0.4);
\r\n
border-radius: 12px;
\r\n
padding: 16px 20px;
\r\n
background: linear-gradient(160deg, rgba(30, 30, 30, 0.9), rgba(40, 40, 40, 0.6));
\r\n
box-shadow: 0 0 12px rgba(66, 165, 245, 0.2);
\r\n
transition: transform 0.2s ease;
\r\n
}
\r\n
.kpi-item:hover {
\r\n
transform: scale(1.02);
\r\n
box-shadow: 0 0 20px rgba(66, 165, 245, 0.4);
\r\n
}
\r\n\r\n
/* 表格基本樣式 */
\r\n
.merchant-table {
\r\n
width: 98%;
\r\n
margin: auto;
\r\n
border-collapse: collapse;
\r\n
table-layout: fixed;
\r\n
border: 1px solid rgba(100, 181, 246, 0.2);
\r\n
font-family: 'Segoe UI', 'Roboto Mono', 'Noto Sans TC', monospace;
\r\n
}
\r\n\r\n
/* 表頭、表尾 sticky */
\r\n
.merchant-table thead th,
\r\n
.merchant-table tfoot td {
\r\n
position: sticky;
\r\n
//background: rgba(30, 30, 30, 0.95);
\r\n
z-index: 1;
\r\n
}
\r\n\r\n
.merchant-table thead th {
\r\n
top: 0;
\r\n
font-size: 14px;
\r\n
//color: #fff;
\r\n
text-align: center;
\r\n
//border-bottom: 1px solid rgba(100, 181, 246, 0.4);
\r\n
}
\r\n\r\n
.merchant-table tfoot td {
\r\n
bottom: 0;
\r\n
font-weight: bold;
\r\n
//color: #fff;
\r\n
//background: rgba(50, 50, 50, 0.95);
\r\n
border-top: 1px solid rgba(100, 181, 246, 0.4);
\r\n
}
\r\n\r\n
/* 表格內容 */
\r\n
.merchant-table th,
\r\n
.merchant-table td {
\r\n
padding: 8px 14px;
\r\n
min-width: 120px;
\r\n
text-align: right;
\r\n
}
\r\n\r\n
.merchant-table td.kpi-merchant-name {
\r\n
font-size: 16px;
\r\n
letter-spacing: 1px;
\r\n
text-align: center;
\r\n
//color: #fff;
\r\n
}
\r\n\r\n
.merchant-table td.kpi-merchant-highname {
\r\n
font-size: 14px;
\r\n
letter-spacing: 1px;
\r\n
text-align: center;
\r\n
color: red;
\r\n
}
\r\n\r\n
.merchant-table td.kpi-value {
\r\n
font-size: 16px;
\r\n
//color: rgb(144, 202, 249);
\r\n
font-variant-numeric: tabular-nums;
\r\n
}
\r\n\r\n
.merchant-table td.kpi-label {
\r\n
font-size: 16px;
\r\n
letter-spacing: 1px;
\r\n
text-align: left;
\r\n
}
\r\n\r\n
.merchant-table tbody tr:hover {
\r\n
transform: scale(1.02);
\r\n
box-shadow: 0 0 20px rgba(66, 165, 245, 0.4);
\r\n
transition: all 0.2s ease;
\r\n
}
\r\n
.merchant-table tr.kpi-merchant-highrow {
\r\n
border: 1px solid red;
\r\n
border-radius: 6px;
\r\n
box-shadow: 0 0 8px rgba(255, 0, 0, 0.6);
\r\n
}
\r\n
</style>
\r\n
"
,
"wrap"
:
true
},
"pluginVersion"
:
"5.7.0"
,
"targets"
:
[
{
"columns"
:
[
],
"datasource"
:
{
"type"
:
"yesoreyeram-infinity-datasource"
,
"uid"
:
"aegaeyjq187b4e"
},
"filters"
:
[
],
"format"
:
"table"
,
"global_query_id"
:
""
,
"parser"
:
"backend"
,
"refId"
:
"A"
,
"root_selector"
:
""
,
"source"
:
"url"
,
"type"
:
"json"
,
"url"
:
"/smartfds-adm-web/report/api/decision-aggs"
,
"url_options"
:
{
"data"
:
""
,
"method"
:
"GET"
,
"params"
:
[
{
"key"
:
"operator_id"
,
"value"
:
"${operator_id}"
},
{
"key"
:
"institute_id"
,
"value"
:
"${institute_id}"
},
{
"key"
:
"connector_id"
,
"value"
:
"${connector_id}"
},
{
"key"
:
"from_time"
,
"value"
:
"${__from}"
},
{
"key"
:
"to_time"
,
"value"
:
"${__to}"
},
{
"key"
:
"auth_token"
,
"value"
:
"${auth_token}"
},
{
"key"
:
"merchant_id"
,
"value"
:
"${merchant_id}"
}
]
}
}
],
"title"
:
"B-01. 商店交易,設備統計(USD)"
,
"transformations"
:
[
{
"id"
:
"calculateField"
,
"options"
:
{
"alias"
:
"deviceCollection"
,
"binary"
:
{
"left"
:
{
"matcher"
:
{
"id"
:
"byName"
,
"options"
:
"txCountDevice"
}
},
"operator"
:
"/"
,
"right"
:
{
"matcher"
:
{
"id"
:
"byName"
,
"options"
:
"txCount"
}
}
},
"mode"
:
"binary"
,
"reduce"
:
{
"reducer"
:
"sum"
}
}
},
{
"disabled"
:
true
,
"id"
:
"organize"
,
"options"
:
{
"excludeByName"
:
{
},
"includeByName"
:
{
},
"indexByName"
:
{
"Transaction Volume (with Device Info)"
:
5
,
"decisionHight"
:
6
,
"decisionMid"
:
7
,
"merchantName"
:
0
,
"txAmount"
:
3
,
"txCount"
:
1
,
"txCountDevice"
:
2
,
"txCountUniDevice"
:
4
},
"renameByName"
:
{
"Transaction Volume (with Device Info)"
:
"設備資訊收集率%"
,
"decisionHight"
:
"High-Risk"
,
"decisionMid"
:
"Mid-Risk"
,
"merchantName"
:
"風險等級"
,
"txAmount"
:
"交易金額 (USD)"
,
"txCount"
:
"交易次數"
,
"txCountDevice"
:
"交易次數 (有設備資訊)"
,
"txCountUniDevice"
:
"設備數量"
}
}
}
],
"type"
:
"marcusolsson-dynamictext-panel"
},
"version"
:
14
,
"meta"
:
{
"folderName"
:
"R97-Library"
,
"folderUid"
:
"bempo37hjqtq8d"
,
"connectedDashboards"
:
2
,
"created"
:
"2025-05-23T16:55:21+08:00"
,
"updated"
:
"2025-05-27T10:57:16+08:00"
,
"createdBy"
:
{
"avatarUrl"
:
"/avatar/ce6412d58e966caaa26cac12eb99734b"
,
"id"
:
1
,
"name"
:
"admin"
},
"updatedBy"
:
{
"avatarUrl"
:
"/avatar/ce6412d58e966caaa26cac12eb99734b"
,
"id"
:
1
,
"name"
:
"admin"
}
}
}
}
« 上一頁
1
…
13
14
15
16
17
…
26
下一頁 »
(15-15/26)
載入中...