專案

一般

配置概況

工作單 #200 » P9702001-商店交易,設備統計(台幣).json

marlboro chu, 2025-06-30 01:59

 
{
"result" : {
"id" : 613,
"orgId" : 1,
"folderId" : 143,
"folderUid" : "bempo37hjqtq8d",
"uid" : "aemqkkrcvicqoa",
"name" : "P9702001-商店交易,設備統計(台幣)",
"kind" : 1,
"type" : "marcusolsson-dynamictext-panel",
"description" : "",
"model" : {
"datasource" : {
"type" : "yesoreyeram-infinity-datasource",
"uid" : "aegaeyjq187b4e"
},
"description" : "",
"fieldConfig" : {
"defaults" : {
"decimals" : 0,
"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" : 11
},
"id" : 9702001,
"options" : {
"afterRender" : "let currentSortField = 'merchantName'; // 預設排序欄位\r\nlet currentSortOrder = 'desc'; // 預設排序方向\r\n\r\n// 中文表頭設定\r\nconst headers = [\r\n { text: '風險等級', field: 'merchantName' },\r\n { text: '交易數量', field: 'txCount' },\r\n //{ text: '交易筆數(有設備資訊)', field: 'txCountDevice' },\r\n { text: '交易金額(台幣)', field: 'txRealAmount' },\r\n { text: '授權成功數量', field: 'authResultY' },\r\n { text: '設備數量', field: 'txCountUniDevice' },\r\n //{ text: '設備資訊收集率%', field: 'deviceCollection' },\r\n\r\n];\r\n\r\n// 要加總的欄位順序(數值)\r\n//const sumFields = ['txCount', 'txCountDevice', 'txAmount', 'txCountUniDevice', 'deviceCollection', 'authResultY'];\r\nconst sumFields = ['txCount', 'txRealAmount', 'authResultY', 'txCountUniDevice'];\r\n\r\nfunction 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.txRealAmount,\r\n item.authResultY,\r\n item.txCountUniDevice\r\n //item.deviceCollection,\r\n\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\nfunction setupMerchantTableHeader(data) {\r\n const tableHeader = document.getElementById('merchant-table-header');\r\n tableHeader.innerHTML = ''; // 清空舊表頭\r\n\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\nfunction 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\nfunction 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 = '總計';\r\n totalLabelCell.style.textAlign = 'right';\r\n footerRow.appendChild(totalLabelCell);\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\nif (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. 商店交易,設備統計(台幣)",
"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" : {
"txCountDevice" : false
},
"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" : 19,
"meta" : {
"folderName" : "R97-Library",
"folderUid" : "bempo37hjqtq8d",
"connectedDashboards" : 2,
"created" : "2025-05-23T16:55:21+08:00",
"updated" : "2025-05-29T09:38:44+08:00",
"createdBy" : {
"avatarUrl" : "/avatar/ce6412d58e966caaa26cac12eb99734b",
"id" : 1,
"name" : "admin"
},
"updatedBy" : {
"avatarUrl" : "/avatar/ce6412d58e966caaa26cac12eb99734b",
"id" : 1,
"name" : "admin"
}
}
}
}
(17-17/26)