A:完成提交
This commit is contained in:
1 parent
7daac20ddb
commit
b93bda3113
4 files changed
+5420
No files matched your search
@@ -0,0 +1,1305 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PLC数据格式转换与调试工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #2c3e50, #3498db);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.input-section, .control-section {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
||||
border: 1px solid #e0e6ed;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.4em;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #3498db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(52, 152, 219, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #95a5a6, #7f8c8d);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #27ae60, #229954);
|
||||
}
|
||||
|
||||
.results-section {
|
||||
grid-column: 1 / -1;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
||||
border: 1px solid #e0e6ed;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.results-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 10px 20px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: #7f8c8d;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: #3498db;
|
||||
border-bottom-color: #3498db;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 筛选控件样式 */
|
||||
.filter-controls {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #6c757d;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.filter-group select {
|
||||
padding: 8px;
|
||||
font-size: 13px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.filter-stats {
|
||||
background: #e3f2fd;
|
||||
border: 1px solid #bbdefb;
|
||||
border-radius: 6px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.clear-filters-btn {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.clear-filters-btn:hover {
|
||||
background: #d32f2f;
|
||||
}
|
||||
|
||||
.result-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.result-item:hover {
|
||||
background: #e3f2fd;
|
||||
border-color: #3498db;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.result-item.filtered-out {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result-header {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
background: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee2e6;
|
||||
word-break: break-all;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #138496;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #e74c3c;
|
||||
background: #fadbd8;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #27ae60;
|
||||
background: #d5f4e6;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.detail-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
max-width: 800px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #7f8c8d;
|
||||
}
|
||||
|
||||
.process-step {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #3498db;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.brand-info {
|
||||
background: #e8f4fd;
|
||||
border: 1px solid #3498db;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.brand-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.brand-item {
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #bdc3c7;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.result-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔧 PLC数据格式转换与调试工具</h1>
|
||||
<p>专业的工业数据格式转换、字节序分析与调试平台</p>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 数据输入区 -->
|
||||
<div class="input-section">
|
||||
<div class="section-title">
|
||||
📥 数据输入区
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>输入格式选择:</label>
|
||||
<select id="inputFormat">
|
||||
<option value="byte">byte[] (0-255)</option>
|
||||
<option value="ushort" selected>ushort[] (0-65535)</option>
|
||||
<option value="short">short[] (-32768 to 32767)</option>
|
||||
<option value="int">int[] (-2147483648 to 2147483647)</option>
|
||||
<option value="uint">uint[] (0 to 4294967295)</option>
|
||||
<option value="hex">Hex 字符串</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>原始数据输入:</label>
|
||||
<textarea id="rawData" placeholder="示例:[13312,0,16128,0,13312,0,8704,0,5888,0] 或者:0x3400,0x0000,0x3F00,0x0000 支持逗号、空格、换行分隔"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn" onclick="parseInput()">📊 解析数据</button>
|
||||
<button class="btn btn-secondary" onclick="clearInput()">🗑️ 清空</button>
|
||||
</div>
|
||||
|
||||
<div id="inputValidation"></div>
|
||||
</div>
|
||||
|
||||
<!-- 控制选项区 -->
|
||||
<div class="control-section">
|
||||
<div class="section-title">
|
||||
⚙️ 转换控制
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>目标数据类型:</label>
|
||||
<select id="targetType">
|
||||
<option value="all">🔄 显示所有类型</option>
|
||||
<option value="bool">bool (布尔)</option>
|
||||
<option value="byte">byte (无符号8位)</option>
|
||||
<option value="sbyte">sbyte (有符号8位)</option>
|
||||
<option value="short">short (有符号16位)</option>
|
||||
<option value="ushort">ushort (无符号16位)</option>
|
||||
<option value="int">int (有符号32位)</option>
|
||||
<option value="uint">uint (无符号32位)</option>
|
||||
<option value="long">long (有符号64位)</option>
|
||||
<option value="ulong">ulong (无符号64位)</option>
|
||||
<option value="float">float (32位浮点)</option>
|
||||
<option value="double">double (64位浮点)</option>
|
||||
<option value="ascii">ASCII 字符串</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>字节序选择:</label>
|
||||
<select id="byteOrder">
|
||||
<option value="all">🔄 显示所有字节序</option>
|
||||
<option value="ABCD">ABCD (大端序)</option>
|
||||
<option value="DCBA">DCBA (小端序)</option>
|
||||
<option value="CDAB">CDAB (西门子常见)</option>
|
||||
<option value="BADC">BADC (欧姆龙常见)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>编码格式:</label>
|
||||
<select id="encoding">
|
||||
<option value="all">🔄 显示所有编码</option>
|
||||
<option value="DEC">DEC (十进制编码)</option>
|
||||
<option value="BCD">BCD (二进制编码十进制)</option>
|
||||
<option value="HEX">HEX (十六进制编码)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-success" onclick="convertData()">🚀 开始转换</button>
|
||||
<button class="btn" onclick="showBrandInfo()">📚 设备参考</button>
|
||||
</div>
|
||||
|
||||
<!-- 设备品牌参考信息 -->
|
||||
<div class="brand-info" style="display: none;" id="brandInfo">
|
||||
<h4>🏭 常见PLC品牌字节序参考</h4>
|
||||
<div class="brand-list">
|
||||
<div class="brand-item">
|
||||
<strong>西门子 (Siemens)</strong><br>
|
||||
常用: CDAB
|
||||
</div>
|
||||
<div class="brand-item">
|
||||
<strong>欧姆龙 (Omron)</strong><br>
|
||||
常用: BADC
|
||||
</div>
|
||||
<div class="brand-item">
|
||||
<strong>三菱 (Mitsubishi)</strong><br>
|
||||
常用: ABCD
|
||||
</div>
|
||||
<div class="brand-item">
|
||||
<strong>施耐德 (Schneider)</strong><br>
|
||||
常用: DCBA
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果展示区 -->
|
||||
<div class="results-section" id="resultsSection" style="display: none;">
|
||||
<div class="section-title">
|
||||
📊 转换结果
|
||||
</div>
|
||||
|
||||
<div class="results-tabs">
|
||||
<button class="tab-btn active" onclick="switchTab('all-results')">全部结果</button>
|
||||
<button class="tab-btn" onclick="switchTab('filtered-results')">筛选结果</button>
|
||||
<button class="tab-btn" onclick="switchTab('raw-data')">原始数据</button>
|
||||
</div>
|
||||
|
||||
<div id="all-results" class="tab-content active">
|
||||
<!-- 筛选控件 -->
|
||||
<div class="filter-controls">
|
||||
<div class="filter-group">
|
||||
<label>原始类型</label>
|
||||
<select id="filterInputFormat" onchange="applyFilters()">
|
||||
<option value="">全部</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>目标数据类型</label>
|
||||
<select id="filterDataType" onchange="applyFilters()">
|
||||
<option value="">全部</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>字节序</label>
|
||||
<select id="filterByteOrder" onchange="applyFilters()">
|
||||
<option value="">全部</option>
|
||||
<option value="ABCD">ABCD (大端序)</option>
|
||||
<option value="DCBA">DCBA (小端序)</option>
|
||||
<option value="CDAB">CDAB (西门子常见)</option>
|
||||
<option value="BADC">BADC (欧姆龙常见)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>编码格式</label>
|
||||
<select id="filterEncoding" onchange="applyFilters()">
|
||||
<option value="">全部</option>
|
||||
<option value="DEC">DEC (十进制)</option>
|
||||
<option value="BCD">BCD (二进制编码十进制)</option>
|
||||
<option value="HEX">HEX (十六进制)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<button class="clear-filters-btn" onclick="clearFilters()">清除筛选</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选统计 -->
|
||||
<div class="filter-stats" id="filterStats">
|
||||
<span>显示 <strong id="visibleCount">0</strong> 条结果,共 <strong id="totalCount">0</strong> 条</span>
|
||||
</div>
|
||||
|
||||
<div id="allResultsGrid" class="result-grid"></div>
|
||||
</div>
|
||||
|
||||
<div id="filtered-results" class="tab-content">
|
||||
<div id="filteredResultsGrid" class="result-grid"></div>
|
||||
</div>
|
||||
|
||||
<div id="raw-data" class="tab-content">
|
||||
<div id="rawDataDisplay"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细解释弹窗 -->
|
||||
<div id="detailModal" class="detail-modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>🔍 转换过程详解</h2>
|
||||
<button class="close-btn" onclick="closeModal()">×</button>
|
||||
</div>
|
||||
<div id="modalContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentData = [];
|
||||
let currentInputFormat = 'ushort';
|
||||
let allResults = [];
|
||||
|
||||
// 解析输入数据
|
||||
function parseInput() {
|
||||
const rawDataElement = document.getElementById('rawData');
|
||||
const inputFormat = document.getElementById('inputFormat').value;
|
||||
const validation = document.getElementById('inputValidation');
|
||||
|
||||
try {
|
||||
const inputText = rawDataElement.value.trim();
|
||||
if (!inputText) {
|
||||
throw new Error('请输入数据');
|
||||
}
|
||||
|
||||
// 解析不同格式的输入
|
||||
let parsedData = [];
|
||||
currentInputFormat = inputFormat;
|
||||
|
||||
if (inputFormat === 'hex') {
|
||||
// 十六进制输入
|
||||
const hexPattern = /0x[0-9A-Fa-f]+|[0-9A-Fa-f]+/g;
|
||||
const hexMatches = inputText.match(hexPattern);
|
||||
if (!hexMatches) {
|
||||
throw new Error('无效的十六进制格式');
|
||||
}
|
||||
parsedData = hexMatches.map(hex => {
|
||||
const value = parseInt(hex.replace('0x', ''), 16);
|
||||
return value;
|
||||
});
|
||||
} else {
|
||||
// 数组格式输入 - 移除括号并分割
|
||||
const cleanText = inputText.replace(/[\[\]]/g, '');
|
||||
const values = cleanText.split(/[,\s\n]+/).filter(v => v.trim());
|
||||
|
||||
parsedData = values.map(v => {
|
||||
const num = v.includes('0x') ? parseInt(v, 16) : parseInt(v);
|
||||
if (isNaN(num)) {
|
||||
throw new Error(`无效的数值: ${v}`);
|
||||
}
|
||||
return num;
|
||||
});
|
||||
}
|
||||
|
||||
// 验证数据范围
|
||||
validateDataRange(parsedData, inputFormat);
|
||||
|
||||
currentData = parsedData;
|
||||
validation.innerHTML = `<div class="success">✅ 成功解析 ${parsedData.length} 个数据项</div>`;
|
||||
|
||||
// 自动转换
|
||||
convertData();
|
||||
|
||||
} catch (error) {
|
||||
validation.innerHTML = `<div class="error">❌ ${error.message}</div>`;
|
||||
currentData = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 验证数据范围
|
||||
function validateDataRange(data, format) {
|
||||
const ranges = {
|
||||
'byte': [0, 255],
|
||||
'sbyte': [-128, 127],
|
||||
'ushort': [0, 65535],
|
||||
'short': [-32768, 32767],
|
||||
'int': [-2147483648, 2147483647],
|
||||
'uint': [0, 4294967295],
|
||||
'hex': [0, 4294967295]
|
||||
};
|
||||
|
||||
if (ranges[format]) {
|
||||
const [min, max] = ranges[format];
|
||||
for (let value of data) {
|
||||
if (value < min || value > max) {
|
||||
throw new Error(`数值 ${value} 超出 ${format} 类型范围 [${min}, ${max}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换数据
|
||||
function convertData() {
|
||||
if (currentData.length === 0) {
|
||||
alert('请先输入并解析数据');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetType = document.getElementById('targetType').value;
|
||||
const byteOrder = document.getElementById('byteOrder').value;
|
||||
const encoding = document.getElementById('encoding').value;
|
||||
|
||||
// 显示结果区域
|
||||
document.getElementById('resultsSection').style.display = 'block';
|
||||
|
||||
// 生成所有结果
|
||||
allResults = generateAllResults();
|
||||
|
||||
// 更新筛选器选项
|
||||
updateFilterOptions();
|
||||
|
||||
// 显示结果
|
||||
displayResults(allResults, targetType, byteOrder, encoding);
|
||||
|
||||
// 显示原始数据
|
||||
displayRawData();
|
||||
}
|
||||
|
||||
// 生成所有可能的转换结果
|
||||
function generateAllResults() {
|
||||
const results = [];
|
||||
const byteOrders = ['ABCD', 'DCBA', 'CDAB', 'BADC'];
|
||||
const encodings = ['DEC', 'BCD', 'HEX'];
|
||||
const dataTypes = ['bool', 'byte', 'sbyte', 'short', 'ushort', 'int', 'uint', 'long', 'ulong', 'float', 'double', 'ascii'];
|
||||
|
||||
// 首先将输入数据转换为字节数组
|
||||
const bytes = convertToBytes(currentData, currentInputFormat);
|
||||
|
||||
for (let byteOrder of byteOrders) {
|
||||
for (let encoding of encodings) {
|
||||
for (let dataType of dataTypes) {
|
||||
try {
|
||||
const result = convertWithParams(bytes, dataType, byteOrder, encoding);
|
||||
if (result.success) {
|
||||
results.push({
|
||||
inputFormat: currentInputFormat,
|
||||
dataType,
|
||||
byteOrder,
|
||||
encoding,
|
||||
result: result.data,
|
||||
displayValue: result.displayValue,
|
||||
isValid: result.isValid
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略转换错误,继续下一个组合
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 更新筛选器选项
|
||||
function updateFilterOptions() {
|
||||
// 获取唯一值
|
||||
const inputFormats = [...new Set(allResults.map(r => r.inputFormat))];
|
||||
const dataTypes = [...new Set(allResults.map(r => r.dataType))];
|
||||
|
||||
// 更新原始类型筛选器
|
||||
const filterInputFormat = document.getElementById('filterInputFormat');
|
||||
filterInputFormat.innerHTML = '<option value="">全部</option>';
|
||||
inputFormats.forEach(format => {
|
||||
const option = document.createElement('option');
|
||||
option.value = format;
|
||||
option.textContent = format.toUpperCase();
|
||||
filterInputFormat.appendChild(option);
|
||||
});
|
||||
|
||||
// 更新目标数据类型筛选器
|
||||
const filterDataType = document.getElementById('filterDataType');
|
||||
filterDataType.innerHTML = '<option value="">全部</option>';
|
||||
dataTypes.forEach(type => {
|
||||
const option = document.createElement('option');
|
||||
option.value = type;
|
||||
option.textContent = type.toUpperCase();
|
||||
filterDataType.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 应用筛选
|
||||
function applyFilters() {
|
||||
const filterInputFormat = document.getElementById('filterInputFormat').value;
|
||||
const filterDataType = document.getElementById('filterDataType').value;
|
||||
const filterByteOrder = document.getElementById('filterByteOrder').value;
|
||||
const filterEncoding = document.getElementById('filterEncoding').value;
|
||||
|
||||
const resultItems = document.querySelectorAll('#allResultsGrid .result-item');
|
||||
let visibleCount = 0;
|
||||
|
||||
resultItems.forEach((item, index) => {
|
||||
const result = allResults[index];
|
||||
if (!result) return;
|
||||
|
||||
const matchesInputFormat = !filterInputFormat || result.inputFormat === filterInputFormat;
|
||||
const matchesDataType = !filterDataType || result.dataType === filterDataType;
|
||||
const matchesByteOrder = !filterByteOrder || result.byteOrder === filterByteOrder;
|
||||
const matchesEncoding = !filterEncoding || result.encoding === filterEncoding;
|
||||
|
||||
const shouldShow = matchesInputFormat && matchesDataType && matchesByteOrder && matchesEncoding;
|
||||
|
||||
if (shouldShow) {
|
||||
item.classList.remove('filtered-out');
|
||||
visibleCount++;
|
||||
} else {
|
||||
item.classList.add('filtered-out');
|
||||
}
|
||||
});
|
||||
|
||||
// 更新统计信息
|
||||
document.getElementById('visibleCount').textContent = visibleCount;
|
||||
document.getElementById('totalCount').textContent = allResults.length;
|
||||
}
|
||||
|
||||
// 清除筛选
|
||||
function clearFilters() {
|
||||
document.getElementById('filterInputFormat').value = '';
|
||||
document.getElementById('filterDataType').value = '';
|
||||
document.getElementById('filterByteOrder').value = '';
|
||||
document.getElementById('filterEncoding').value = '';
|
||||
|
||||
// 显示所有结果
|
||||
const resultItems = document.querySelectorAll('#allResultsGrid .result-item');
|
||||
resultItems.forEach(item => {
|
||||
item.classList.remove('filtered-out');
|
||||
});
|
||||
|
||||
// 更新统计信息
|
||||
document.getElementById('visibleCount').textContent = allResults.length;
|
||||
document.getElementById('totalCount').textContent = allResults.length;
|
||||
}
|
||||
|
||||
// 将输入数据转换为字节数组
|
||||
function convertToBytes(data, inputFormat) {
|
||||
const bytes = [];
|
||||
|
||||
for (let value of data) {
|
||||
switch (inputFormat) {
|
||||
case 'byte':
|
||||
bytes.push(value);
|
||||
break;
|
||||
case 'ushort':
|
||||
case 'short':
|
||||
// 16位数据转换为2个字节(小端序)
|
||||
bytes.push(value & 0xFF);
|
||||
bytes.push((value >> 8) & 0xFF);
|
||||
break;
|
||||
case 'int':
|
||||
case 'uint':
|
||||
case 'hex':
|
||||
// 32位数据转换为4个字节(小端序)
|
||||
bytes.push(value & 0xFF);
|
||||
bytes.push((value >> 8) & 0xFF);
|
||||
bytes.push((value >> 16) & 0xFF);
|
||||
bytes.push((value >> 24) & 0xFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// 使用指定参数进行转换
|
||||
function convertWithParams(bytes, dataType, byteOrder, encoding) {
|
||||
try {
|
||||
// 首先根据编码格式处理字节
|
||||
const processedBytes = applyEncoding(bytes, encoding);
|
||||
if (!processedBytes.success) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
// 然后根据数据类型和字节序进行转换
|
||||
const result = convertByDataType(processedBytes.data, dataType, byteOrder);
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 应用编码格式
|
||||
function applyEncoding(bytes, encoding) {
|
||||
switch (encoding) {
|
||||
case 'DEC':
|
||||
return { success: true, data: bytes };
|
||||
|
||||
case 'BCD':
|
||||
// 验证BCD格式(每个半字节都应该是0-9)
|
||||
for (let byte of bytes) {
|
||||
const high = (byte >> 4) & 0xF;
|
||||
const low = byte & 0xF;
|
||||
if (high > 9 || low > 9) {
|
||||
return { success: false, error: 'Invalid BCD format' };
|
||||
}
|
||||
}
|
||||
|
||||
// BCD转换为十进制
|
||||
const bcdBytes = bytes.map(byte => {
|
||||
const high = (byte >> 4) & 0xF;
|
||||
const low = byte & 0xF;
|
||||
return high * 10 + low;
|
||||
});
|
||||
return { success: true, data: bcdBytes };
|
||||
|
||||
case 'HEX':
|
||||
return { success: true, data: bytes };
|
||||
|
||||
default:
|
||||
return { success: true, data: bytes };
|
||||
}
|
||||
}
|
||||
|
||||
// 根据数据类型和字节序转换
|
||||
function convertByDataType(bytes, dataType, byteOrder) {
|
||||
const result = [];
|
||||
|
||||
try {
|
||||
switch (dataType) {
|
||||
case 'bool':
|
||||
for (let byte of bytes) {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result.push((byte & (1 << i)) !== 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'byte':
|
||||
result.push(...bytes);
|
||||
break;
|
||||
|
||||
case 'sbyte':
|
||||
for (let byte of bytes) {
|
||||
result.push(byte > 127 ? byte - 256 : byte);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'short':
|
||||
case 'ushort':
|
||||
for (let i = 0; i < bytes.length - 1; i += 2) {
|
||||
const orderedBytes = applyByteOrder([bytes[i], bytes[i + 1]], byteOrder, 2);
|
||||
const value = orderedBytes[0] | (orderedBytes[1] << 8);
|
||||
result.push(dataType === 'short' && value > 32767 ? value - 65536 : value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'int':
|
||||
case 'uint':
|
||||
for (let i = 0; i < bytes.length - 3; i += 4) {
|
||||
const orderedBytes = applyByteOrder([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]], byteOrder, 4);
|
||||
const value = orderedBytes[0] | (orderedBytes[1] << 8) | (orderedBytes[2] << 16) | (orderedBytes[3] << 24);
|
||||
result.push(dataType === 'int' && value > 2147483647 ? value - 4294967296 : value >>> 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
for (let i = 0; i < bytes.length - 3; i += 4) {
|
||||
const orderedBytes = applyByteOrder([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]], byteOrder, 4);
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
for (let j = 0; j < 4; j++) {
|
||||
view.setUint8(j, orderedBytes[j]);
|
||||
}
|
||||
const floatValue = view.getFloat32(0, true);
|
||||
result.push(floatValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'double':
|
||||
for (let i = 0; i < bytes.length - 7; i += 8) {
|
||||
const orderedBytes = applyByteOrder(bytes.slice(i, i + 8), byteOrder, 8);
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const view = new DataView(buffer);
|
||||
for (let j = 0; j < 8; j++) {
|
||||
view.setUint8(j, orderedBytes[j]);
|
||||
}
|
||||
const doubleValue = view.getFloat64(0, true);
|
||||
result.push(doubleValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ascii':
|
||||
const asciiString = bytes.map(byte =>
|
||||
byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '·'
|
||||
).join('');
|
||||
result.push(asciiString);
|
||||
break;
|
||||
}
|
||||
|
||||
// 格式化显示值
|
||||
const displayValue = formatDisplayValue(result, dataType);
|
||||
const isValid = validateResult(result, dataType);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
displayValue: displayValue,
|
||||
isValid: isValid
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 应用字节序
|
||||
function applyByteOrder(bytes, byteOrder, expectedLength) {
|
||||
// 确保字节数组长度正确
|
||||
const paddedBytes = [...bytes];
|
||||
while (paddedBytes.length < expectedLength) {
|
||||
paddedBytes.push(0);
|
||||
}
|
||||
|
||||
switch (byteOrder) {
|
||||
case 'ABCD': // 大端序
|
||||
return paddedBytes.reverse();
|
||||
case 'DCBA': // 小端序
|
||||
return paddedBytes;
|
||||
case 'CDAB': // 西门子常见
|
||||
if (expectedLength === 4) {
|
||||
return [paddedBytes[2], paddedBytes[3], paddedBytes[0], paddedBytes[1]];
|
||||
} else if (expectedLength === 2) {
|
||||
return [paddedBytes[1], paddedBytes[0]];
|
||||
}
|
||||
return paddedBytes;
|
||||
case 'BADC': // 欧姆龙常见
|
||||
if (expectedLength === 4) {
|
||||
return [paddedBytes[1], paddedBytes[0], paddedBytes[3], paddedBytes[2]];
|
||||
} else if (expectedLength === 2) {
|
||||
return [paddedBytes[1], paddedBytes[0]];
|
||||
}
|
||||
return paddedBytes;
|
||||
default:
|
||||
return paddedBytes;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化显示值
|
||||
function formatDisplayValue(result, dataType) {
|
||||
if (result.length === 0) return '';
|
||||
|
||||
switch (dataType) {
|
||||
case 'float':
|
||||
case 'double':
|
||||
return result.map(v => {
|
||||
if (isNaN(v) || !isFinite(v)) {
|
||||
return v.toString();
|
||||
}
|
||||
return parseFloat(v.toFixed(6));
|
||||
}).join(', ');
|
||||
case 'bool':
|
||||
return result.slice(0, 32).map(v => v ? '1' : '0').join('');
|
||||
case 'ascii':
|
||||
return result[0];
|
||||
default:
|
||||
return result.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
// 验证结果
|
||||
function validateResult(result, dataType) {
|
||||
if (result.length === 0) return false;
|
||||
|
||||
switch (dataType) {
|
||||
case 'float':
|
||||
case 'double':
|
||||
return result.some(v => isFinite(v) && !isNaN(v) && Math.abs(v) < 1e10);
|
||||
case 'ascii':
|
||||
return /[a-zA-Z0-9]/.test(result[0]);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
function displayResults(results, targetTypeFilter, byteOrderFilter, encodingFilter) {
|
||||
const allResultsGrid = document.getElementById('allResultsGrid');
|
||||
const filteredResultsGrid = document.getElementById('filteredResultsGrid');
|
||||
|
||||
// 显示所有结果
|
||||
allResultsGrid.innerHTML = '';
|
||||
let allCount = 0;
|
||||
|
||||
// 显示筛选结果
|
||||
filteredResultsGrid.innerHTML = '';
|
||||
let filteredCount = 0;
|
||||
|
||||
for (let result of results) {
|
||||
const resultElement = createResultElement(result);
|
||||
allResultsGrid.appendChild(resultElement.cloneNode(true));
|
||||
allCount++;
|
||||
|
||||
// 检查是否符合筛选条件
|
||||
const matchesType = targetTypeFilter === 'all' || result.dataType === targetTypeFilter;
|
||||
const matchesByteOrder = byteOrderFilter === 'all' || result.byteOrder === byteOrderFilter;
|
||||
const matchesEncoding = encodingFilter === 'all' || result.encoding === encodingFilter;
|
||||
|
||||
if (matchesType && matchesByteOrder && matchesEncoding) {
|
||||
filteredResultsGrid.appendChild(resultElement);
|
||||
filteredCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新标签
|
||||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||||
tabBtns[0].textContent = `全部结果 (${allCount})`;
|
||||
tabBtns[1].textContent = `筛选结果 (${filteredCount})`;
|
||||
|
||||
// 更新全部结果的统计信息
|
||||
document.getElementById('visibleCount').textContent = allCount;
|
||||
document.getElementById('totalCount').textContent = allCount;
|
||||
|
||||
// 清除之前的筛选
|
||||
clearFilters();
|
||||
}
|
||||
|
||||
// 创建结果元素
|
||||
function createResultElement(result) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'result-item';
|
||||
if (!result.isValid) {
|
||||
div.style.opacity = '0.6';
|
||||
}
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="result-header">
|
||||
<span>${result.inputFormat.toUpperCase()} → ${result.dataType.toUpperCase()} - ${result.byteOrder} - ${result.encoding}</span>
|
||||
<div>
|
||||
<button class="copy-btn" onclick="copyResult('${result.displayValue}')" title="复制结果">📋</button>
|
||||
<button class="copy-btn" onclick="showDetail('${result.dataType}', '${result.byteOrder}', '${result.encoding}')" title="查看详情">🔍</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-value">${result.displayValue}</div>
|
||||
`;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// 显示原始数据
|
||||
function displayRawData() {
|
||||
const rawDataDisplay = document.getElementById('rawDataDisplay');
|
||||
const bytes = convertToBytes(currentData, currentInputFormat);
|
||||
|
||||
rawDataDisplay.innerHTML = `
|
||||
<div class="process-step">
|
||||
<div class="step-title">📥 输入数据 (${currentInputFormat})</div>
|
||||
<div class="step-content">[${currentData.join(', ')}]</div>
|
||||
</div>
|
||||
<div class="process-step">
|
||||
<div class="step-title">🔢 转换为字节数组</div>
|
||||
<div class="step-content">[${bytes.join(', ')}]</div>
|
||||
</div>
|
||||
<div class="process-step">
|
||||
<div class="step-title">🔤 十六进制表示</div>
|
||||
<div class="step-content">[${bytes.map(b => '0x' + b.toString(16).padStart(2, '0').toUpperCase()).join(', ')}]</div>
|
||||
</div>
|
||||
<div class="process-step">
|
||||
<div class="step-title">💾 二进制表示</div>
|
||||
<div class="step-content">[${bytes.map(b => '0b' + b.toString(2).padStart(8, '0')).join(', ')}]</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 显示详细转换过程
|
||||
function showDetail(dataType, byteOrder, encoding) {
|
||||
const modal = document.getElementById('detailModal');
|
||||
const modalContent = document.getElementById('modalContent');
|
||||
|
||||
const bytes = convertToBytes(currentData, currentInputFormat);
|
||||
const processedBytes = applyEncoding(bytes, encoding);
|
||||
|
||||
let content = `
|
||||
<div class="process-step">
|
||||
<div class="step-title">🎯 转换参数</div>
|
||||
<div class="step-content">
|
||||
数据类型: ${dataType}<br>
|
||||
字节序: ${byteOrder}<br>
|
||||
编码格式: ${encoding}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="process-step">
|
||||
<div class="step-title">📥 原始输入 (${currentInputFormat})</div>
|
||||
<div class="step-content">[${currentData.join(', ')}]</div>
|
||||
</div>
|
||||
|
||||
<div class="process-step">
|
||||
<div class="step-title">🔢 转换为字节数组</div>
|
||||
<div class="step-content">[${bytes.join(', ')}]</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (encoding !== 'DEC') {
|
||||
content += `
|
||||
<div class="process-step">
|
||||
<div class="step-title">🔄 应用${encoding}编码</div>
|
||||
<div class="step-content">[${processedBytes.data.join(', ')}]</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 显示字节序转换过程
|
||||
if (dataType !== 'byte' && dataType !== 'bool' && dataType !== 'ascii') {
|
||||
const bytesPerElement = dataType === 'short' || dataType === 'ushort' ? 2 :
|
||||
dataType === 'float' || dataType === 'int' || dataType === 'uint' ? 4 : 8;
|
||||
|
||||
content += `
|
||||
<div class="process-step">
|
||||
<div class="step-title">🔀 应用字节序 ${byteOrder}</div>
|
||||
<div class="step-content">
|
||||
${byteOrder}字节序说明:<br>
|
||||
${getByteOrderDescription(byteOrder)}<br>
|
||||
每${bytesPerElement}字节为一组进行重排
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 显示最终结果
|
||||
const finalResult = convertWithParams(bytes, dataType, byteOrder, encoding);
|
||||
if (finalResult.success) {
|
||||
content += `
|
||||
<div class="process-step">
|
||||
<div class="step-title">✅ 最终结果</div>
|
||||
<div class="step-content">${finalResult.displayValue}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
modalContent.innerHTML = content;
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
// 获取字节序描述
|
||||
function getByteOrderDescription(byteOrder) {
|
||||
switch (byteOrder) {
|
||||
case 'ABCD':
|
||||
return '大端序 - 高位字节在前,低位字节在后';
|
||||
case 'DCBA':
|
||||
return '小端序 - 低位字节在前,高位字节在后';
|
||||
case 'CDAB':
|
||||
return '西门子常见序 - 中间两字节交换的大端序';
|
||||
case 'BADC':
|
||||
return '欧姆龙常见序 - 相邻字节交换的序列';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// 复制结果
|
||||
function copyResult(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
// 可以添加复制成功提示
|
||||
console.log('已复制到剪贴板');
|
||||
});
|
||||
}
|
||||
|
||||
// 切换标签
|
||||
function switchTab(tabName) {
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
|
||||
event.target.classList.add('active');
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
}
|
||||
|
||||
// 显示品牌信息
|
||||
function showBrandInfo() {
|
||||
const brandInfo = document.getElementById('brandInfo');
|
||||
brandInfo.style.display = brandInfo.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
function closeModal() {
|
||||
document.getElementById('detailModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// 清空输入
|
||||
function clearInput() {
|
||||
document.getElementById('rawData').value = '';
|
||||
document.getElementById('inputValidation').innerHTML = '';
|
||||
document.getElementById('resultsSection').style.display = 'none';
|
||||
currentData = [];
|
||||
allResults = [];
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('detailModal');
|
||||
if (event.target === modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后的初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 设置示例数据
|
||||
document.getElementById('rawData').value = '[13312,0,16128,0,13312,0,8704,0,5888,0]';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,1264 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>通用PLC数据转换工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.panel h3 {
|
||||
color: #4a5568;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.4em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
select, input, textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
select:focus, input:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.grid-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.grid-3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.grid-2, .grid-3, .grid-4 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: linear-gradient(45deg, #718096, #2d3748);
|
||||
}
|
||||
|
||||
button.success {
|
||||
background: linear-gradient(45deg, #48bb78, #38a169);
|
||||
}
|
||||
|
||||
button.warning {
|
||||
background: linear-gradient(45deg, #ed8936, #dd6b20);
|
||||
}
|
||||
|
||||
.result-panel {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.result {
|
||||
background: linear-gradient(135deg, #f0fff4, #e6fffa);
|
||||
border: 2px solid #48bb78;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-top: 15px;
|
||||
font-family: 'Courier New', monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: linear-gradient(135deg, #fed7d7, #ffeaea);
|
||||
border-color: #f56565;
|
||||
color: #c53030;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
border-bottom-color: #667eea;
|
||||
color: #667eea;
|
||||
background: #f7fafc;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.preset-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.preset-card {
|
||||
background: #f7fafc;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.preset-card:hover {
|
||||
border-color: #667eea;
|
||||
background: #eef2ff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.preset-card.active {
|
||||
border-color: #667eea;
|
||||
background: #eef2ff;
|
||||
}
|
||||
|
||||
.preset-card h4 {
|
||||
color: #4a5568;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.preset-card p {
|
||||
color: #718096;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.format-examples {
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #667eea;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.bit-display {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.bit {
|
||||
background: #f1f5f9;
|
||||
border: 1px solid #cbd5e0;
|
||||
border-radius: 4px;
|
||||
padding: 8px 4px;
|
||||
text-align: center;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bit.set {
|
||||
background: #48bb78;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.floating-help {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.help-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.8);
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.help-content {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
max-width: 800px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔧 通用PLC数据转换工具</h1>
|
||||
<p>支持所有主流PLC品牌和数据格式的专业转换工具</p>
|
||||
</div>
|
||||
|
||||
<div class="main-grid">
|
||||
<!-- 左侧:配置面板 -->
|
||||
<div class="panel">
|
||||
<h3><span class="icon">⚙️</span>转换配置</h3>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" onclick="switchTab('basic')">基础转换</div>
|
||||
<div class="tab" onclick="switchTab('advanced')">高级选项</div>
|
||||
<div class="tab" onclick="switchTab('presets')">预设配置</div>
|
||||
</div>
|
||||
|
||||
<!-- 基础转换 -->
|
||||
<div id="basic" class="tab-content active">
|
||||
<div class="form-group">
|
||||
<label>PLC品牌</label>
|
||||
<select id="plcBrand" onchange="updatePresets()">
|
||||
<option value="omron">欧姆龙 (Omron)</option>
|
||||
<option value="siemens">西门子 (Siemens)</option>
|
||||
<option value="mitsubishi">三菱 (Mitsubishi)</option>
|
||||
<option value="ab">罗克韦尔 (Allen-Bradley)</option>
|
||||
<option value="schneider">施耐德 (Schneider)</option>
|
||||
<option value="delta">台达 (Delta)</option>
|
||||
<option value="keyence">基恩士 (Keyence)</option>
|
||||
<option value="panasonic">松下 (Panasonic)</option>
|
||||
<option value="custom">自定义</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>源数据类型</label>
|
||||
<select id="sourceType">
|
||||
<option value="ushort">USHORT (16位)</option>
|
||||
<option value="short">SHORT (16位)</option>
|
||||
<option value="uint">UINT (32位)</option>
|
||||
<option value="int">INT (32位)</option>
|
||||
<option value="ulong">ULONG (64位)</option>
|
||||
<option value="long">LONG (64位)</option>
|
||||
<option value="byte">BYTE (8位)</option>
|
||||
<option value="bool">BOOL (1位)</option>
|
||||
<option value="real">REAL (32位浮点)</option>
|
||||
<option value="double">DOUBLE (64位浮点)</option>
|
||||
<option value="string">STRING</option>
|
||||
<option value="raw">原始数据</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>目标数据类型</label>
|
||||
<select id="targetType">
|
||||
<option value="int">INT (32位)</option>
|
||||
<option value="uint">UINT (32位)</option>
|
||||
<option value="short">SHORT (16位)</option>
|
||||
<option value="ushort">USHORT (16位)</option>
|
||||
<option value="long">LONG (64位)</option>
|
||||
<option value="ulong">ULONG (64位)</option>
|
||||
<option value="byte">BYTE (8位)</option>
|
||||
<option value="bool">BOOL (1位)</option>
|
||||
<option value="real">REAL (32位浮点)</option>
|
||||
<option value="double">DOUBLE (64位浮点)</option>
|
||||
<option value="string">STRING</option>
|
||||
<option value="hex">十六进制</option>
|
||||
<option value="binary">二进制</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-3">
|
||||
<div class="form-group">
|
||||
<label>字节序</label>
|
||||
<select id="byteOrder">
|
||||
<option value="ABCD">ABCD (大端序)</option>
|
||||
<option value="DCBA">DCBA (小端序)</option>
|
||||
<option value="BADC">BADC (混合序1)</option>
|
||||
<option value="CDAB">CDAB (混合序2)</option>
|
||||
<option value="auto">自动检测</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>位序</label>
|
||||
<select id="bitOrder">
|
||||
<option value="msb">MSB优先</option>
|
||||
<option value="lsb">LSB优先</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>数据格式</label>
|
||||
<select id="dataFormat">
|
||||
<option value="decimal">十进制</option>
|
||||
<option value="hex">十六进制</option>
|
||||
<option value="binary">二进制</option>
|
||||
<option value="array">数组</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 高级选项 -->
|
||||
<div id="advanced" class="tab-content">
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>偏移量 (字节)</label>
|
||||
<input type="number" id="offset" value="0" min="0">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>数据长度限制</label>
|
||||
<input type="number" id="lengthLimit" value="0" min="0" placeholder="0=无限制">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="signedData" checked>
|
||||
有符号数据
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="reverseOrder">
|
||||
反转数组顺序
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" id="ignoreErrors">
|
||||
忽略转换错误
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>浮点数精度</label>
|
||||
<input type="number" id="precision" value="6" min="0" max="15">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>字符串编码</label>
|
||||
<select id="encoding">
|
||||
<option value="utf8">UTF-8</option>
|
||||
<option value="ascii">ASCII</option>
|
||||
<option value="gbk">GBK</option>
|
||||
<option value="unicode">Unicode</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预设配置 -->
|
||||
<div id="presets" class="tab-content">
|
||||
<div class="preset-grid" id="presetGrid">
|
||||
<!-- 预设配置将在这里动态生成 -->
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="saveCurrentPreset()">💾 保存当前配置</button>
|
||||
<button onclick="loadPreset()" class="secondary">📁 加载配置文件</button>
|
||||
<input type="file" id="fileInput" style="display:none" accept=".json">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="convertData()">🔄 转换数据</button>
|
||||
<button onclick="autoDetect()" class="warning">🔍 自动检测</button>
|
||||
<button onclick="clearAll()" class="secondary">🗑️ 清空</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:数据输入面板 -->
|
||||
<div class="panel">
|
||||
<h3><span class="icon">📥</span>数据输入</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label>原始数据</label>
|
||||
<textarea id="inputData" placeholder="输入你的PLC数据,支持多种格式:
|
||||
[256,0,256,0,256,0]
|
||||
256,0,256,0,256,0
|
||||
0x0100,0x0000,0x0100
|
||||
01000000 00000000...">[256,0,256,0,256,0,256,0,256,0]</textarea>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="loadSampleData()" class="secondary">📝 加载示例</button>
|
||||
<button onclick="importFromFile()" class="secondary">📂 从文件导入</button>
|
||||
<button onclick="formatInput()">🎨 格式化</button>
|
||||
</div>
|
||||
|
||||
<div class="format-examples">
|
||||
<strong>支持的输入格式:</strong><br>
|
||||
数组: [1,2,3] 或 1,2,3<br>
|
||||
十六进制: 0x01,0x02 或 01 02<br>
|
||||
二进制: 00000001 00000010<br>
|
||||
混合: 1 0x02 00000011
|
||||
</div>
|
||||
|
||||
<!-- 位编辑器 -->
|
||||
<div class="form-group">
|
||||
<label>位编辑器 <small>(点击切换位状态)</small></label>
|
||||
<div id="bitEditor" class="bit-display">
|
||||
<!-- 位显示将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果面板 -->
|
||||
<div class="panel result-panel">
|
||||
<h3><span class="icon">📤</span>转换结果</h3>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="exportResult()" class="success">💾 导出结果</button>
|
||||
<button onclick="copyToClipboard()" class="success">📋 复制到剪贴板</button>
|
||||
<button onclick="showComparison()" class="secondary">🔍 显示对比</button>
|
||||
</div>
|
||||
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 浮动帮助按钮 -->
|
||||
<div class="floating-help" onclick="toggleHelp()">❓</div>
|
||||
|
||||
<!-- 帮助面板 -->
|
||||
<div class="help-panel" id="helpPanel" onclick="toggleHelp()">
|
||||
<div class="help-content" onclick="event.stopPropagation()">
|
||||
<h2>📚 使用帮助</h2>
|
||||
<h3>PLC品牌特色:</h3>
|
||||
<ul>
|
||||
<li><strong>欧姆龙:</strong> 通常使用小端序,支持CJ/CP/CS系列</li>
|
||||
<li><strong>西门子:</strong> S7系列,大端序为主</li>
|
||||
<li><strong>三菱:</strong> FX/Q系列,混合字节序常见</li>
|
||||
<li><strong>AB:</strong> CompactLogix/ControlLogix,小端序</li>
|
||||
<li><strong>施耐德:</strong> Modicon系列,大端序</li>
|
||||
</ul>
|
||||
|
||||
<h3>数据类型说明:</h3>
|
||||
<ul>
|
||||
<li><strong>BOOL:</strong> 1位布尔值</li>
|
||||
<li><strong>BYTE:</strong> 8位无符号整数 (0-255)</li>
|
||||
<li><strong>USHORT:</strong> 16位无符号整数 (0-65535)</li>
|
||||
<li><strong>INT:</strong> 32位有符号整数</li>
|
||||
<li><strong>REAL:</strong> 32位IEEE754浮点数</li>
|
||||
</ul>
|
||||
|
||||
<h3>字节序说明:</h3>
|
||||
<ul>
|
||||
<li><strong>ABCD:</strong> 大端序,高字节在前</li>
|
||||
<li><strong>DCBA:</strong> 小端序,低字节在前</li>
|
||||
<li><strong>BADC/CDAB:</strong> 字节对调的混合模式</li>
|
||||
</ul>
|
||||
|
||||
<button onclick="toggleHelp()" style="margin-top: 20px;">关闭帮助</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 全局变量
|
||||
let currentPresets = {};
|
||||
let conversionHistory = [];
|
||||
|
||||
// PLC品牌预设配置
|
||||
const plcPresets = {
|
||||
omron: {
|
||||
name: "欧姆龙默认",
|
||||
byteOrder: "DCBA",
|
||||
bitOrder: "lsb",
|
||||
signed: true,
|
||||
description: "适用于CJ/CP/CS系列"
|
||||
},
|
||||
siemens: {
|
||||
name: "西门子默认",
|
||||
byteOrder: "ABCD",
|
||||
bitOrder: "msb",
|
||||
signed: true,
|
||||
description: "适用于S7-300/400/1500"
|
||||
},
|
||||
mitsubishi: {
|
||||
name: "三菱默认",
|
||||
byteOrder: "BADC",
|
||||
bitOrder: "lsb",
|
||||
signed: true,
|
||||
description: "适用于FX/Q系列"
|
||||
},
|
||||
ab: {
|
||||
name: "AB默认",
|
||||
byteOrder: "DCBA",
|
||||
bitOrder: "lsb",
|
||||
signed: true,
|
||||
description: "适用于CompactLogix"
|
||||
},
|
||||
schneider: {
|
||||
name: "施耐德默认",
|
||||
byteOrder: "ABCD",
|
||||
bitOrder: "msb",
|
||||
signed: true,
|
||||
description: "适用于Modicon系列"
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化
|
||||
window.onload = function() {
|
||||
updatePresets();
|
||||
updateBitEditor();
|
||||
loadSampleData();
|
||||
};
|
||||
|
||||
// 切换标签页
|
||||
function switchTab(tabName) {
|
||||
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
|
||||
event.target.classList.add('active');
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
}
|
||||
|
||||
// 更新预设配置
|
||||
function updatePresets() {
|
||||
const brand = document.getElementById('plcBrand').value;
|
||||
const presetGrid = document.getElementById('presetGrid');
|
||||
|
||||
presetGrid.innerHTML = '';
|
||||
|
||||
Object.entries(plcPresets).forEach(([key, preset]) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'preset-card';
|
||||
if (key === brand) card.classList.add('active');
|
||||
|
||||
card.innerHTML = `
|
||||
<h4>${preset.name}</h4>
|
||||
<p>${preset.description}</p>
|
||||
`;
|
||||
|
||||
card.onclick = () => applyPreset(key);
|
||||
presetGrid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// 应用预设配置
|
||||
function applyPreset(presetKey) {
|
||||
const preset = plcPresets[presetKey];
|
||||
if (!preset) return;
|
||||
|
||||
document.getElementById('byteOrder').value = preset.byteOrder;
|
||||
document.getElementById('bitOrder').value = preset.bitOrder;
|
||||
document.getElementById('signedData').checked = preset.signed;
|
||||
document.getElementById('plcBrand').value = presetKey;
|
||||
|
||||
updatePresets();
|
||||
}
|
||||
|
||||
// 加载示例数据
|
||||
function loadSampleData() {
|
||||
const samples = [
|
||||
"[256,0,256,0,256,0,256,0,256,0]",
|
||||
"0x0100,0x0000,0x0100,0x0000",
|
||||
"1,0,1,0,1,0,1,0,1,0",
|
||||
"00000001 00000000 00000001 00000000"
|
||||
];
|
||||
|
||||
const randomSample = samples[Math.floor(Math.random() * samples.length)];
|
||||
document.getElementById('inputData').value = randomSample;
|
||||
}
|
||||
|
||||
// 解析输入数据
|
||||
function parseInputData(input) {
|
||||
input = input.trim();
|
||||
|
||||
// 移除数组括号
|
||||
if (input.startsWith('[') && input.endsWith(']')) {
|
||||
input = input.slice(1, -1);
|
||||
}
|
||||
|
||||
// 分割数据
|
||||
let values = [];
|
||||
|
||||
if (input.includes(',')) {
|
||||
values = input.split(',');
|
||||
} else {
|
||||
values = input.split(/\s+/);
|
||||
}
|
||||
|
||||
// 解析每个值
|
||||
return values.map(val => {
|
||||
val = val.trim();
|
||||
if (val.startsWith('0x') || val.startsWith('0X')) {
|
||||
return parseInt(val, 16);
|
||||
} else if (/^[01]+$/.test(val) && val.length > 4) {
|
||||
return parseInt(val, 2);
|
||||
} else {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
}).filter(val => !isNaN(val));
|
||||
}
|
||||
|
||||
// 主要转换函数
|
||||
function convertData() {
|
||||
const inputData = document.getElementById('inputData').value;
|
||||
const sourceType = document.getElementById('sourceType').value;
|
||||
const targetType = document.getElementById('targetType').value;
|
||||
const byteOrder = document.getElementById('byteOrder').value;
|
||||
const signed = document.getElementById('signedData').checked;
|
||||
const resultDiv = document.getElementById('result');
|
||||
|
||||
try {
|
||||
const parsedData = parseInputData(inputData);
|
||||
if (parsedData.length === 0) {
|
||||
throw new Error('无法解析输入数据');
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
// 根据转换类型进行处理
|
||||
if (sourceType === 'ushort' && targetType === 'int') {
|
||||
result = convertUshortToInt(parsedData, byteOrder, signed);
|
||||
} else if (sourceType === 'int' && targetType === 'ushort') {
|
||||
result = convertIntToUshort(parsedData, byteOrder);
|
||||
} else if (targetType === 'real') {
|
||||
result = convertToFloat(parsedData, byteOrder);
|
||||
} else if (targetType === 'double') {
|
||||
result = convertToDouble(parsedData, byteOrder);
|
||||
} else if (targetType === 'hex') {
|
||||
result = convertToHex(parsedData);
|
||||
} else if (targetType === 'binary') {
|
||||
result = convertToBinary(parsedData);
|
||||
} else if (targetType === 'bool') {
|
||||
result = convertToBool(parsedData);
|
||||
} else {
|
||||
result = convertGeneric(parsedData, sourceType, targetType, byteOrder, signed);
|
||||
}
|
||||
|
||||
displayResult(result, parsedData, sourceType, targetType, byteOrder);
|
||||
updateBitEditor(parsedData[0] || 0);
|
||||
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `<div class="result error">转换错误: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 转换函数实现
|
||||
function convertUshortToInt(ushortArray, byteOrder, signed = true) {
|
||||
if (ushortArray.length % 2 !== 0) {
|
||||
throw new Error('USHORT到INT转换需要偶数个数据');
|
||||
}
|
||||
|
||||
const intArray = [];
|
||||
|
||||
for (let i = 0; i < ushortArray.length; i += 2) {
|
||||
const low = ushortArray[i];
|
||||
const high = ushortArray[i + 1];
|
||||
|
||||
let intValue;
|
||||
switch (byteOrder) {
|
||||
case 'ABCD':
|
||||
intValue = (high << 16) | low;
|
||||
break;
|
||||
case 'DCBA':
|
||||
intValue = (low << 16) | high;
|
||||
break;
|
||||
case 'BADC':
|
||||
intValue = ((low & 0xFF) << 24) | (((low >> 8) & 0xFF) << 16) |
|
||||
((high & 0xFF) << 8) | ((high >> 8) & 0xFF);
|
||||
break;
|
||||
case 'CDAB':
|
||||
intValue = ((high & 0xFF) << 24) | (((high >> 8) & 0xFF) << 16) |
|
||||
((low & 0xFF) << 8) | ((low >> 8) & 0xFF);
|
||||
break;
|
||||
default:
|
||||
intValue = (low << 16) | high;
|
||||
}
|
||||
|
||||
if (signed && intValue > 0x7FFFFFFF) {
|
||||
intValue = intValue - 0x100000000;
|
||||
}
|
||||
|
||||
intArray.push(intValue);
|
||||
}
|
||||
|
||||
return intArray;
|
||||
}
|
||||
|
||||
function convertIntToUshort(intArray, byteOrder) {
|
||||
const ushortArray = [];
|
||||
|
||||
intArray.forEach(intValue => {
|
||||
const uintValue = intValue >>> 0;
|
||||
|
||||
switch (byteOrder) {
|
||||
case 'ABCD':
|
||||
ushortArray.push(uintValue & 0xFFFF);
|
||||
ushortArray.push((uintValue >> 16) & 0xFFFF);
|
||||
break;
|
||||
case 'DCBA':
|
||||
ushortArray.push((uintValue >> 16) & 0xFFFF);
|
||||
ushortArray.push(uintValue & 0xFFFF);
|
||||
break;
|
||||
default:
|
||||
ushortArray.push((uintValue >> 16) & 0xFFFF);
|
||||
ushortArray.push(uintValue & 0xFFFF);
|
||||
}
|
||||
});
|
||||
|
||||
return ushortArray;
|
||||
}
|
||||
|
||||
function convertToFloat(data, byteOrder) {
|
||||
const floatArray = [];
|
||||
|
||||
for (let i = 0; i < data.length; i += 2) {
|
||||
if (i + 1 >= data.length) break;
|
||||
|
||||
const intValue = convertUshortToInt([data[i], data[i + 1]], byteOrder, false)[0];
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const intView = new Uint32Array(buffer);
|
||||
const floatView = new Float32Array(buffer);
|
||||
|
||||
intView[0] = intValue;
|
||||
floatArray.push(floatView[0]);
|
||||
}
|
||||
|
||||
return floatArray;
|
||||
}
|
||||
|
||||
function convertToDouble(data, byteOrder) {
|
||||
const doubleArray = [];
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
if (i + 3 >= data.length) break;
|
||||
|
||||
// 64位双精度需要4个16位数据
|
||||
const part1 = convertUshortToInt([data[i], data[i + 1]], byteOrder, false)[0];
|
||||
const part2 = convertUshortToInt([data[i + 2], data[i + 3]], byteOrder, false)[0];
|
||||
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const intView = new Uint32Array(buffer);
|
||||
const doubleView = new Float64Array(buffer);
|
||||
|
||||
if (byteOrder === 'ABCD' || byteOrder === 'BADC') {
|
||||
intView[1] = part1;
|
||||
intView[0] = part2;
|
||||
} else {
|
||||
intView[0] = part1;
|
||||
intView[1] = part2;
|
||||
}
|
||||
|
||||
doubleArray.push(doubleView[0]);
|
||||
}
|
||||
|
||||
return doubleArray;
|
||||
}
|
||||
|
||||
function convertToHex(data) {
|
||||
return data.map(val => '0x' + val.toString(16).toUpperCase().padStart(4, '0'));
|
||||
}
|
||||
|
||||
function convertToBinary(data) {
|
||||
return data.map(val => val.toString(2).padStart(16, '0'));
|
||||
}
|
||||
|
||||
function convertToBool(data) {
|
||||
return data.map(val => val !== 0);
|
||||
}
|
||||
|
||||
function convertGeneric(data, sourceType, targetType, byteOrder, signed) {
|
||||
// 通用转换逻辑
|
||||
return data.map(val => {
|
||||
switch (targetType) {
|
||||
case 'byte':
|
||||
return val & 0xFF;
|
||||
case 'short':
|
||||
return signed && val > 32767 ? val - 65536 : val;
|
||||
case 'ushort':
|
||||
return val & 0xFFFF;
|
||||
case 'uint':
|
||||
return val >>> 0;
|
||||
case 'long':
|
||||
case 'ulong':
|
||||
return val;
|
||||
default:
|
||||
return val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 自动检测最佳配置
|
||||
function autoDetect() {
|
||||
const inputData = document.getElementById('inputData').value;
|
||||
const parsedData = parseInputData(inputData);
|
||||
|
||||
if (parsedData.length === 0) return;
|
||||
|
||||
const resultDiv = document.getElementById('result');
|
||||
let bestFormat = 'DCBA';
|
||||
let bestResults = {};
|
||||
|
||||
// 测试所有字节序格式
|
||||
const formats = ['ABCD', 'DCBA', 'BADC', 'CDAB'];
|
||||
|
||||
let analysisResult = '<div class="result"><strong>🔍 自动检测结果:</strong>\n\n';
|
||||
|
||||
formats.forEach(format => {
|
||||
try {
|
||||
if (parsedData.length % 2 === 0) {
|
||||
const converted = convertUshortToInt(parsedData, format);
|
||||
bestResults[format] = converted;
|
||||
|
||||
const score = analyzePattern(converted);
|
||||
analysisResult += `${format}: [${converted.slice(0, 5).join(', ')}${converted.length > 5 ? '...' : ''}] (评分: ${score})\n`;
|
||||
|
||||
if (score > (bestResults[bestFormat] ? analyzePattern(bestResults[bestFormat]) : 0)) {
|
||||
bestFormat = format;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
analysisResult += `${format}: 转换失败\n`;
|
||||
}
|
||||
});
|
||||
|
||||
analysisResult += `\n推荐使用: ${bestFormat} ✅\n`;
|
||||
analysisResult += '</div>';
|
||||
|
||||
// 应用最佳配置
|
||||
document.getElementById('byteOrder').value = bestFormat;
|
||||
|
||||
resultDiv.innerHTML = analysisResult;
|
||||
}
|
||||
|
||||
// 分析数据模式评分
|
||||
function analyzePattern(data) {
|
||||
let score = 0;
|
||||
|
||||
// 检查重复模式
|
||||
const uniqueValues = new Set(data);
|
||||
if (uniqueValues.size < data.length / 2) score += 10;
|
||||
|
||||
// 检查合理范围
|
||||
const maxVal = Math.max(...data);
|
||||
const minVal = Math.min(...data);
|
||||
if (maxVal < 10000 && minVal >= 0) score += 20;
|
||||
|
||||
// 检查序列模式
|
||||
let isSequential = true;
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
if (Math.abs(data[i] - data[i-1]) > 100) {
|
||||
isSequential = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isSequential) score += 15;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// 显示转换结果
|
||||
function displayResult(result, originalData, sourceType, targetType, byteOrder) {
|
||||
const resultDiv = document.getElementById('result');
|
||||
const precision = parseInt(document.getElementById('precision').value);
|
||||
|
||||
let formattedResult;
|
||||
if (targetType === 'real' || targetType === 'double') {
|
||||
formattedResult = result.map(val => val.toFixed(precision)).join(', ');
|
||||
} else if (targetType === 'hex') {
|
||||
formattedResult = result.join(', ');
|
||||
} else if (targetType === 'binary') {
|
||||
formattedResult = result.join(' ');
|
||||
} else if (targetType === 'bool') {
|
||||
formattedResult = result.map(val => val ? 'True' : 'False').join(', ');
|
||||
} else {
|
||||
formattedResult = result.join(', ');
|
||||
}
|
||||
|
||||
const verification = verifyConversion(result, originalData, sourceType, targetType, byteOrder);
|
||||
|
||||
resultDiv.innerHTML = `<div class="result">
|
||||
<strong>✅ 转换成功:</strong>
|
||||
源数据 (${sourceType}): [${originalData.join(', ')}]
|
||||
转换结果 (${targetType}): [${formattedResult}]
|
||||
|
||||
<strong>转换参数:</strong>
|
||||
字节序: ${byteOrder}
|
||||
数据长度: ${originalData.length} → ${result.length}
|
||||
${verification}
|
||||
|
||||
<strong>详细分析:</strong>
|
||||
${getDetailedAnalysis(originalData, result, sourceType, targetType, byteOrder)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// 验证转换结果
|
||||
function verifyConversion(result, originalData, sourceType, targetType, byteOrder) {
|
||||
if (sourceType === 'ushort' && targetType === 'int' && originalData.length % 2 === 0) {
|
||||
try {
|
||||
const backToUshort = convertIntToUshort(result, byteOrder);
|
||||
const matches = JSON.stringify(backToUshort) === JSON.stringify(originalData);
|
||||
return `反向验证: ${matches ? '✅ 通过' : '❌ 失败'}`;
|
||||
} catch (error) {
|
||||
return `反向验证: ❌ 错误 - ${error.message}`;
|
||||
}
|
||||
}
|
||||
return '验证: ℹ️ 不适用此转换类型';
|
||||
}
|
||||
|
||||
// 获取详细分析
|
||||
function getDetailedAnalysis(originalData, result, sourceType, targetType, byteOrder) {
|
||||
let analysis = '';
|
||||
|
||||
if (sourceType === 'ushort' && targetType === 'int') {
|
||||
for (let i = 0; i < Math.min(originalData.length / 2, 3); i++) {
|
||||
const idx = i * 2;
|
||||
if (idx + 1 < originalData.length && i < result.length) {
|
||||
const low = originalData[idx];
|
||||
const high = originalData[idx + 1];
|
||||
const intVal = result[i];
|
||||
|
||||
analysis += `第${i + 1}组: [${low}, ${high}] → ${intVal} `;
|
||||
analysis += `(0x${low.toString(16).padStart(4, '0')}, 0x${high.toString(16).padStart(4, '0')} → 0x${(intVal >>> 0).toString(16).padStart(8, '0')})\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return analysis || '数据转换完成';
|
||||
}
|
||||
|
||||
// 更新位编辑器
|
||||
function updateBitEditor(value = 0) {
|
||||
const bitEditor = document.getElementById('bitEditor');
|
||||
bitEditor.innerHTML = '';
|
||||
|
||||
for (let i = 15; i >= 0; i--) {
|
||||
const bit = document.createElement('div');
|
||||
bit.className = 'bit';
|
||||
bit.textContent = ((value >> i) & 1).toString();
|
||||
bit.title = `位 ${i}`;
|
||||
|
||||
if ((value >> i) & 1) {
|
||||
bit.classList.add('set');
|
||||
}
|
||||
|
||||
bit.onclick = () => toggleBit(i);
|
||||
bitEditor.appendChild(bit);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换位状态
|
||||
function toggleBit(bitIndex) {
|
||||
const inputData = document.getElementById('inputData').value;
|
||||
const parsedData = parseInputData(inputData);
|
||||
|
||||
if (parsedData.length > 0) {
|
||||
parsedData[0] ^= (1 << bitIndex);
|
||||
document.getElementById('inputData').value = '[' + parsedData.join(',') + ']';
|
||||
updateBitEditor(parsedData[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化输入
|
||||
function formatInput() {
|
||||
const input = document.getElementById('inputData').value;
|
||||
const parsed = parseInputData(input);
|
||||
document.getElementById('inputData').value = '[' + parsed.join(', ') + ']';
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
function copyToClipboard() {
|
||||
const resultDiv = document.getElementById('result');
|
||||
if (resultDiv.textContent) {
|
||||
navigator.clipboard.writeText(resultDiv.textContent).then(() => {
|
||||
alert('结果已复制到剪贴板!');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 导出结果
|
||||
function exportResult() {
|
||||
const resultDiv = document.getElementById('result');
|
||||
if (!resultDiv.textContent) {
|
||||
alert('没有可导出的结果');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
timestamp: new Date().toISOString(),
|
||||
input: document.getElementById('inputData').value,
|
||||
result: resultDiv.textContent,
|
||||
config: {
|
||||
plcBrand: document.getElementById('plcBrand').value,
|
||||
sourceType: document.getElementById('sourceType').value,
|
||||
targetType: document.getElementById('targetType').value,
|
||||
byteOrder: document.getElementById('byteOrder').value
|
||||
}
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `plc_conversion_${new Date().getTime()}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// 清空所有数据
|
||||
function clearAll() {
|
||||
document.getElementById('inputData').value = '';
|
||||
document.getElementById('result').innerHTML = '';
|
||||
updateBitEditor(0);
|
||||
}
|
||||
|
||||
// 显示对比
|
||||
function showComparison() {
|
||||
// 实现数据对比功能
|
||||
alert('对比功能开发中...');
|
||||
}
|
||||
|
||||
// 切换帮助面板
|
||||
function toggleHelp() {
|
||||
const helpPanel = document.getElementById('helpPanel');
|
||||
helpPanel.style.display = helpPanel.style.display === 'flex' ? 'none' : 'flex';
|
||||
}
|
||||
|
||||
// 从文件导入
|
||||
function importFromFile() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.txt,.csv,.json';
|
||||
input.onchange = function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
document.getElementById('inputData').value = e.target.result;
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
// 保存当前预设
|
||||
function saveCurrentPreset() {
|
||||
const name = prompt('请输入预设名称:');
|
||||
if (name) {
|
||||
const config = {
|
||||
name: name,
|
||||
plcBrand: document.getElementById('plcBrand').value,
|
||||
sourceType: document.getElementById('sourceType').value,
|
||||
targetType: document.getElementById('targetType').value,
|
||||
byteOrder: document.getElementById('byteOrder').value,
|
||||
signed: document.getElementById('signedData').checked
|
||||
};
|
||||
|
||||
localStorage.setItem('plc_preset_' + name, JSON.stringify(config));
|
||||
alert('预设已保存!');
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时自动转换示例数据
|
||||
setTimeout(() => {
|
||||
convertData();
|
||||
}, 500);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,461 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PLC数据转换工具</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 30px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #4a5568;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.2em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #f8fafc;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
margin-bottom: 25px;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.section:hover {
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.section h3 {
|
||||
color: #2d3748;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.3em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
padding: 15px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
margin: 10px 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.result {
|
||||
background: linear-gradient(135deg, #e8f5e8, #f0f8ff);
|
||||
border: 2px solid #48bb78;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
font-family: 'Courier New', monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: linear-gradient(135deg, #fed7d7, #ffeaea);
|
||||
border: 2px solid #f56565;
|
||||
color: #c53030;
|
||||
}
|
||||
|
||||
.explanation {
|
||||
background: linear-gradient(135deg, #e6fffa, #f0fff4);
|
||||
border: 2px solid #38b2ac;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.explanation h4 {
|
||||
color: #2c7a7b;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.byte-order-options {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin: 15px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 15px;
|
||||
background: white;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.radio-group:hover {
|
||||
border-color: #667eea;
|
||||
background: #f7fafc;
|
||||
}
|
||||
|
||||
.radio-group input[type="radio"] {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.radio-group.selected {
|
||||
border-color: #667eea;
|
||||
background: #eef2ff;
|
||||
}
|
||||
|
||||
.example {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
border-radius: 0 8px 8px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔧 PLC数据转换工具</h1>
|
||||
|
||||
<div class="section">
|
||||
<h3><span class="icon">📥</span>输入ushort数据</h3>
|
||||
<textarea id="input" placeholder="请输入读取的ushort数组,用逗号分隔,例如:256,0,256,0,256,0,256,0,256,0">[256,0,256,0,256,0,256,0,256,0]</textarea>
|
||||
|
||||
<div>
|
||||
<h4 style="margin-top: 20px; margin-bottom: 10px;">选择字节序模式:</h4>
|
||||
<div class="byte-order-options">
|
||||
<label class="radio-group selected">
|
||||
<input type="radio" name="byteOrder" value="ABCD" checked>
|
||||
<span>ABCD (大端序)</span>
|
||||
</label>
|
||||
<label class="radio-group">
|
||||
<input type="radio" name="byteOrder" value="DCBA">
|
||||
<span>DCBA (小端序)</span>
|
||||
</label>
|
||||
<label class="radio-group">
|
||||
<input type="radio" name="byteOrder" value="BADC">
|
||||
<span>BADC (混合序1)</span>
|
||||
</label>
|
||||
<label class="radio-group">
|
||||
<input type="radio" name="byteOrder" value="CDAB">
|
||||
<span>CDAB (混合序2)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button onclick="convertData()">🔄 转换数据</button>
|
||||
<button onclick="testAllFormats()">🧪 测试所有格式</button>
|
||||
<button onclick="clearAll()">🗑️ 清空</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3><span class="icon">📤</span>转换结果</h3>
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
|
||||
<div class="explanation">
|
||||
<h4>📚 转换原理说明</h4>
|
||||
<p><strong>问题分析:</strong></p>
|
||||
<p>你写入了5个int值[1,1,1,1,1],但读取时得到了10个ushort值[256,0,256,0,256,0,256,0,256,0]。这是因为:</p>
|
||||
<ul>
|
||||
<li>每个32位int被拆分成2个16位ushort读取</li>
|
||||
<li>存在字节序转换问题</li>
|
||||
<li>256 (0x0100) 表明原本的0x01字节被放在了高位</li>
|
||||
</ul>
|
||||
|
||||
<div class="example">
|
||||
原始int: 1 (0x00000001)
|
||||
内存布局: [01][00][00][00] (小端序)
|
||||
按ushort读取:
|
||||
- 第1个ushort: [00][01] → 256
|
||||
- 第2个ushort: [00][00] → 0
|
||||
</div>
|
||||
|
||||
<p><strong>字节序说明:</strong></p>
|
||||
<ul>
|
||||
<li><strong>ABCD (大端序):</strong> 高字节在前,低字节在后</li>
|
||||
<li><strong>DCBA (小端序):</strong> 低字节在前,高字节在后</li>
|
||||
<li><strong>BADC/CDAB:</strong> 字节对调的混合模式</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 更新单选按钮样式
|
||||
document.querySelectorAll('input[name="byteOrder"]').forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
document.querySelectorAll('.radio-group').forEach(group => {
|
||||
group.classList.remove('selected');
|
||||
});
|
||||
this.closest('.radio-group').classList.add('selected');
|
||||
});
|
||||
});
|
||||
|
||||
function convertData() {
|
||||
const input = document.getElementById('input').value.trim();
|
||||
const byteOrder = document.querySelector('input[name="byteOrder"]:checked').value;
|
||||
const resultDiv = document.getElementById('result');
|
||||
|
||||
try {
|
||||
// 解析输入数据
|
||||
let ushortArray;
|
||||
if (input.startsWith('[') && input.endsWith(']')) {
|
||||
ushortArray = JSON.parse(input);
|
||||
} else {
|
||||
ushortArray = input.split(',').map(x => parseInt(x.trim()));
|
||||
}
|
||||
|
||||
if (ushortArray.length % 2 !== 0) {
|
||||
throw new Error('ushort数组长度必须是偶数');
|
||||
}
|
||||
|
||||
const intArray = convertUshortToInt(ushortArray, byteOrder);
|
||||
|
||||
resultDiv.innerHTML = `<div class="result">
|
||||
<strong>转换结果:</strong>
|
||||
原始ushort数组: [${ushortArray.join(', ')}]
|
||||
转换后的int数组: [${intArray.join(', ')}]
|
||||
|
||||
<strong>详细转换过程:</strong>
|
||||
${getConversionDetails(ushortArray, intArray, byteOrder)}
|
||||
|
||||
<strong>验证:</strong>
|
||||
${verifyConversion(intArray, ushortArray, byteOrder)}
|
||||
</div>`;
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `<div class="result error">错误: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function convertUshortToInt(ushortArray, byteOrder) {
|
||||
const intArray = [];
|
||||
|
||||
for (let i = 0; i < ushortArray.length; i += 2) {
|
||||
const low = ushortArray[i];
|
||||
const high = ushortArray[i + 1];
|
||||
|
||||
let intValue;
|
||||
switch (byteOrder) {
|
||||
case 'ABCD': // 大端序:高字在前
|
||||
intValue = (high << 16) | low;
|
||||
break;
|
||||
case 'DCBA': // 小端序:低字在前
|
||||
intValue = (low << 16) | high;
|
||||
break;
|
||||
case 'BADC': // 字节对调1
|
||||
intValue = ((low & 0xFF) << 24) | (((low >> 8) & 0xFF) << 16) |
|
||||
((high & 0xFF) << 8) | ((high >> 8) & 0xFF);
|
||||
break;
|
||||
case 'CDAB': // 字节对调2
|
||||
intValue = ((high & 0xFF) << 24) | (((high >> 8) & 0xFF) << 16) |
|
||||
((low & 0xFF) << 8) | ((low >> 8) & 0xFF);
|
||||
break;
|
||||
}
|
||||
|
||||
// 处理有符号整数
|
||||
if (intValue > 0x7FFFFFFF) {
|
||||
intValue = intValue - 0x100000000;
|
||||
}
|
||||
|
||||
intArray.push(intValue);
|
||||
}
|
||||
|
||||
return intArray;
|
||||
}
|
||||
|
||||
function getConversionDetails(ushortArray, intArray, byteOrder) {
|
||||
let details = '';
|
||||
for (let i = 0; i < ushortArray.length; i += 2) {
|
||||
const low = ushortArray[i];
|
||||
const high = ushortArray[i + 1];
|
||||
const intValue = intArray[i / 2];
|
||||
|
||||
details += `第${i/2 + 1}个int: ushort[${i}]=${low}(0x${low.toString(16).padStart(4, '0')}), ushort[${i+1}]=${high}(0x${high.toString(16).padStart(4, '0')}) → int=${intValue}(0x${(intValue >>> 0).toString(16).padStart(8, '0')})\n`;
|
||||
}
|
||||
return details;
|
||||
}
|
||||
|
||||
function verifyConversion(intArray, originalUshort, byteOrder) {
|
||||
// 反向验证:将转换后的int再转回ushort看是否匹配
|
||||
const backToUshort = [];
|
||||
|
||||
intArray.forEach(intValue => {
|
||||
const uintValue = intValue >>> 0; // 转为无符号
|
||||
|
||||
switch (byteOrder) {
|
||||
case 'ABCD':
|
||||
backToUshort.push(uintValue & 0xFFFF);
|
||||
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
||||
break;
|
||||
case 'DCBA':
|
||||
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
||||
backToUshort.push(uintValue & 0xFFFF);
|
||||
break;
|
||||
case 'BADC':
|
||||
const b1 = (uintValue >> 24) & 0xFF;
|
||||
const b2 = (uintValue >> 16) & 0xFF;
|
||||
const b3 = (uintValue >> 8) & 0xFF;
|
||||
const b4 = uintValue & 0xFF;
|
||||
backToUshort.push((b1 << 8) | b2);
|
||||
backToUshort.push((b3 << 8) | b4);
|
||||
break;
|
||||
case 'CDAB':
|
||||
const c1 = (uintValue >> 24) & 0xFF;
|
||||
const c2 = (uintValue >> 16) & 0xFF;
|
||||
const c3 = (uintValue >> 8) & 0xFF;
|
||||
const c4 = uintValue & 0xFF;
|
||||
backToUshort.push((c3 << 8) | c4);
|
||||
backToUshort.push((c1 << 8) | c2);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const matches = JSON.stringify(backToUshort) === JSON.stringify(originalUshort);
|
||||
return `反向转换: [${backToUshort.join(', ')}]\n匹配原始数据: ${matches ? '✅ 是' : '❌ 否'}`;
|
||||
}
|
||||
|
||||
function testAllFormats() {
|
||||
const input = document.getElementById('input').value.trim();
|
||||
const resultDiv = document.getElementById('result');
|
||||
|
||||
try {
|
||||
let ushortArray;
|
||||
if (input.startsWith('[') && input.endsWith(']')) {
|
||||
ushortArray = JSON.parse(input);
|
||||
} else {
|
||||
ushortArray = input.split(',').map(x => parseInt(x.trim()));
|
||||
}
|
||||
|
||||
if (ushortArray.length % 2 !== 0) {
|
||||
throw new Error('ushort数组长度必须是偶数');
|
||||
}
|
||||
|
||||
let result = '<div class="result"><strong>🧪 所有字节序格式测试结果:</strong>\n\n';
|
||||
|
||||
['ABCD', 'DCBA', 'BADC', 'CDAB'].forEach(byteOrder => {
|
||||
const intArray = convertUshortToInt(ushortArray, byteOrder);
|
||||
const backToUshort = [];
|
||||
|
||||
// 反向验证
|
||||
intArray.forEach(intValue => {
|
||||
const uintValue = intValue >>> 0;
|
||||
switch (byteOrder) {
|
||||
case 'ABCD':
|
||||
backToUshort.push(uintValue & 0xFFFF);
|
||||
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
||||
break;
|
||||
case 'DCBA':
|
||||
backToUshort.push((uintValue >> 16) & 0xFFFF);
|
||||
backToUshort.push(uintValue & 0xFFFF);
|
||||
break;
|
||||
case 'BADC':
|
||||
const b1 = (uintValue >> 24) & 0xFF;
|
||||
const b2 = (uintValue >> 16) & 0xFF;
|
||||
const b3 = (uintValue >> 8) & 0xFF;
|
||||
const b4 = uintValue & 0xFF;
|
||||
backToUshort.push((b1 << 8) | b2);
|
||||
backToUshort.push((b3 << 8) | b4);
|
||||
break;
|
||||
case 'CDAB':
|
||||
const c1 = (uintValue >> 24) & 0xFF;
|
||||
const c2 = (uintValue >> 16) & 0xFF;
|
||||
const c3 = (uintValue >> 8) & 0xFF;
|
||||
const c4 = uintValue & 0xFF;
|
||||
backToUshort.push((c3 << 8) | c4);
|
||||
backToUshort.push((c1 << 8) | c2);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const matches = JSON.stringify(backToUshort) === JSON.stringify(ushortArray);
|
||||
result += `${byteOrder}格式: [${intArray.join(', ')}] ${matches ? '✅' : '❌'}\n`;
|
||||
});
|
||||
|
||||
result += '</div>';
|
||||
resultDiv.innerHTML = result;
|
||||
} catch (error) {
|
||||
resultDiv.innerHTML = `<div class="result error">错误: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
document.getElementById('input').value = '';
|
||||
document.getElementById('result').innerHTML = '';
|
||||
}
|
||||
|
||||
// 页面加载时自动转换示例数据
|
||||
window.onload = function() {
|
||||
convertData();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+2390
@@ -0,0 +1,2390 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>智能PLC数据格式转换与调试工具</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #2c3e50, #3498db);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.input-section, .control-section {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
||||
border: 1px solid #e0e6ed;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.4em;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #3498db;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
|
||||
input:focus, select:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(52, 152, 219, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #95a5a6, #7f8c8d);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #27ae60, #229954);
|
||||
}
|
||||
|
||||
.btn-smart {
|
||||
background: linear-gradient(135deg, #e74c3c, #c0392b);
|
||||
}
|
||||
|
||||
/* 智能检测面板样式 - 紧凑版 */
|
||||
.smart-detection {
|
||||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||
border: 2px solid #17a2b8;
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.smart-detection::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, #17a2b8, #138496);
|
||||
}
|
||||
|
||||
.detection-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detection-results {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detection-results::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.detection-results::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.detection-results::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.detection-item {
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.detection-category {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.confidence-high {
|
||||
border-left: 3px solid #28a745;
|
||||
background: linear-gradient(90deg, rgba(40, 167, 69, 0.08), white);
|
||||
}
|
||||
|
||||
.confidence-medium {
|
||||
border-left: 3px solid #ffc107;
|
||||
background: linear-gradient(90deg, rgba(255, 193, 7, 0.08), white);
|
||||
}
|
||||
|
||||
.confidence-low {
|
||||
border-left: 3px solid #dc3545;
|
||||
background: linear-gradient(90deg, rgba(220, 53, 69, 0.08), white);
|
||||
}
|
||||
|
||||
.confidence-badge {
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-high {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.badge-medium {
|
||||
background: #ffc107;
|
||||
}
|
||||
|
||||
.badge-low {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.recommendation-panel {
|
||||
background: linear-gradient(135deg, #e8f5e8, #f0f8f0);
|
||||
border: 2px solid #28a745;
|
||||
border-radius: 12px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.recommendation-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-weight: 700;
|
||||
color: #155724;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.recommended-item {
|
||||
background: white;
|
||||
border: 1px solid #c3e6cb;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
margin: 8px 0;
|
||||
position: relative;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.recommended-badge {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: 10px;
|
||||
background: #28a745;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.results-section {
|
||||
grid-column: 1 / -1;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
||||
border: 1px solid #e0e6ed;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.results-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 10px 20px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: #7f8c8d;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: #3498db;
|
||||
border-bottom-color: #3498db;
|
||||
}
|
||||
|
||||
.tab-btn.smart {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.tab-btn.smart.active {
|
||||
border-bottom-color: #e74c3c;
|
||||
}
|
||||
|
||||
/* Tab计数器样式 */
|
||||
.tab-counter {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: #e74c3c;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.result-item:hover {
|
||||
background: #e3f2fd;
|
||||
border-color: #3498db;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.result-item.recommended {
|
||||
border: 2px solid #28a745;
|
||||
background: linear-gradient(135deg, rgba(40, 167, 69, 0.1), #f8f9fa);
|
||||
}
|
||||
|
||||
.result-item.failed {
|
||||
opacity: 0.5;
|
||||
border-color: #dc3545;
|
||||
background: linear-gradient(135deg, rgba(220, 53, 69, 0.1), #f8f9fa);
|
||||
}
|
||||
|
||||
.result-header {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.result-header-sub{
|
||||
/*max-width: 210px;*/
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
background: white;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee2e6;
|
||||
word-break: break-all;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #138496;
|
||||
}
|
||||
|
||||
.details-btn {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.details-btn:hover {
|
||||
background: #1e7e34;
|
||||
}
|
||||
|
||||
.success-indicator {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.success-indicator.success {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.success-indicator.warning {
|
||||
background: #ffc107;
|
||||
}
|
||||
|
||||
.success-indicator.error {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
/* 详情模态框样式 - 增大尺寸 */
|
||||
.details-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 2% auto;
|
||||
padding: 0;
|
||||
border-radius: 15px;
|
||||
width: 95%;
|
||||
max-width: 1200px;
|
||||
max-height: 95vh;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
animation: modalSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: linear-gradient(135deg, #2c3e50, #3498db);
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
border-radius: 15px 15px 0 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
transform: rotate(90deg);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 25px 30px;
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.modal-body::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-body::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 详情内容样式 */
|
||||
.detail-section {
|
||||
margin-bottom: 25px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.byteorder-test {
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.byteorder-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.byteorder-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.byteorder-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.byteorder-value {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
background: #f1f3f4;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.byteorder-status {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.principle-box {
|
||||
background: #e8f5e8;
|
||||
border: 1px solid #c3e6cb;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.principle-item {
|
||||
margin: 8px 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.principle-item strong {
|
||||
min-width: 100px;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.memory-layout {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
|
||||
.memory-step {
|
||||
margin: 5px 0;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.memory-bytes {
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 原始数据展示样式 */
|
||||
.process-step {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.process-step:hover {
|
||||
background: #e3f2fd;
|
||||
border-color: #3498db;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
background: white;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #dee2e6;
|
||||
word-break: break-all;
|
||||
color: #495057;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #e74c3c;
|
||||
background: #fadbd8;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #155724;
|
||||
background: #d4edda;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.statistics-panel {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ffecb5;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 自动选择提示 */
|
||||
.auto-select-notice {
|
||||
background: #d1ecf1;
|
||||
border: 1px solid #bee5eb;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
margin: 10px 0;
|
||||
font-size: 12px;
|
||||
color: #0c5460;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auto-select-notice::before {
|
||||
content: 'ℹ️';
|
||||
}
|
||||
|
||||
/* 禁用body滚动 */
|
||||
.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.result-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 98%;
|
||||
margin: 1% auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>PLC数据格式转换工具</h1>
|
||||
<p>数据格式检测、转换与智能推荐系统</p>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 数据输入区 -->
|
||||
<div class="input-section">
|
||||
<div class="section-title">
|
||||
📥 数据输入区
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>输入格式选择:</label>
|
||||
<select id="inputFormat">
|
||||
<option value="byte">byte[] (0-255)</option>
|
||||
<option value="ushort" selected>ushort[] (0-65535)</option>
|
||||
<option value="short">short[] (-32768 to 32767)</option>
|
||||
<option value="int">int[] (-2147483648 to 2147483647)</option>
|
||||
<option value="uint">uint[] (0 to 4294967295)</option>
|
||||
<option value="hex">Hex 字符串</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>原始数据输入:</label>
|
||||
<textarea id="rawData" placeholder="示例:[13312,0,16128,0,13312,0,8704,0,5888,0] 或者:0x3400,0x0000,0x3F00,0x0000 支持逗号、空格、换行分隔"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-smart" onclick="intelligentAnalysis()">🧠 智能分析</button>
|
||||
<button class="btn" onclick="parseInput()">📊 解析数据</button>
|
||||
<button class="btn btn-secondary" onclick="clearInput()">🗑️ 清空</button>
|
||||
</div>
|
||||
|
||||
<div id="inputValidation"></div>
|
||||
|
||||
<!-- 智能检测结果面板 - 移到输入区下方 -->
|
||||
<div id="smartDetection" class="smart-detection" style="display: none;">
|
||||
<div class="detection-header">
|
||||
🔍 智能检测结果
|
||||
</div>
|
||||
<div id="detectionResults" class="detection-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 控制选项区 -->
|
||||
<div class="control-section">
|
||||
<div class="section-title">
|
||||
⚙️ 转换控制
|
||||
</div>
|
||||
|
||||
<!-- 自动选择提示 -->
|
||||
<div id="autoSelectNotice" class="auto-select-notice" style="display: none;">
|
||||
已根据智能检测结果自动选择最优配置
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>目标数据类型:</label>
|
||||
<select id="targetType" onchange="filterResults()">
|
||||
<option value="all">🔄 显示所有类型</option>
|
||||
<option value="bool">bool (布尔)</option>
|
||||
<option value="byte">byte (无符号8位)</option>
|
||||
<option value="sbyte">sbyte (有符号8位)</option>
|
||||
<option value="short">short (有符号16位)</option>
|
||||
<option value="ushort">ushort (无符号16位)</option>
|
||||
<option value="int">int (有符号32位)</option>
|
||||
<option value="uint">uint (无符号32位)</option>
|
||||
<option value="long">long (有符号64位)</option>
|
||||
<option value="ulong">ulong (无符号64位)</option>
|
||||
<option value="float">float (32位浮点)</option>
|
||||
<option value="double">double (64位浮点)</option>
|
||||
<option value="ascii">ASCII 字符串</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>字节序选择:</label>
|
||||
<select id="byteOrder" onchange="filterResults()">
|
||||
<option value="all">🔄 显示所有字节序</option>
|
||||
<option value="ABCD">ABCD (大端序)</option>
|
||||
<option value="DCBA">DCBA (小端序)</option>
|
||||
<option value="CDAB">CDAB (西门子常见)</option>
|
||||
<option value="BADC">BADC (欧姆龙常见)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>编码格式:</label>
|
||||
<select id="encoding" onchange="filterResults()">
|
||||
<option value="all">🔄 显示所有编码</option>
|
||||
<option value="DEC">DEC (十进制编码)</option>
|
||||
<option value="BCD">BCD (二进制编码十进制)</option>
|
||||
<option value="HEX">HEX (十六进制编码)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-success" onclick="convertData()">🚀 开始转换</button>
|
||||
</div>
|
||||
|
||||
<!-- 推荐配置面板 -->
|
||||
<div id="recommendationPanel" class="recommendation-panel" style="display: none;">
|
||||
<div class="recommendation-header">
|
||||
⭐ 智能推荐配置
|
||||
</div>
|
||||
<div id="recommendedConfigs"></div>
|
||||
<button class="btn btn-success" onclick="applyRecommendation()">✅ 应用推荐配置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 结果展示区 -->
|
||||
<div class="results-section" id="resultsSection" style="display: none;">
|
||||
<div class="section-title">
|
||||
📊 转换结果
|
||||
</div>
|
||||
|
||||
<!-- 统计信息面板 -->
|
||||
<div id="statisticsPanel" class="statistics-panel" style="display: none;">
|
||||
<h4>📈 转换统计</h4>
|
||||
<div id="statisticsGrid" class="stats-grid"></div>
|
||||
</div>
|
||||
|
||||
<div class="results-tabs">
|
||||
<button class="tab-btn smart active" onclick="switchTab('smart-results')">🧠 智能推荐</button>
|
||||
<button class="tab-btn" onclick="switchTab('all-results')">
|
||||
全部结果
|
||||
<span class="tab-counter" id="allResultsCounter" style="display: none;">0</span>
|
||||
</button>
|
||||
<button class="tab-btn" onclick="switchTab('raw-data')">原始数据</button>
|
||||
</div>
|
||||
|
||||
<div id="smart-results" class="tab-content active">
|
||||
<div id="smartResultsGrid" class="result-grid"></div>
|
||||
</div>
|
||||
|
||||
<div id="all-results" class="tab-content">
|
||||
<div id="allResultsGrid" class="result-grid"></div>
|
||||
</div>
|
||||
|
||||
<div id="raw-data" class="tab-content">
|
||||
<div id="rawDataDisplay"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情模态框 -->
|
||||
<div id="detailsModal" class="details-modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title" id="modalTitle">转换详情</h2>
|
||||
<button class="close-btn" onclick="closeDetailsModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body" id="modalBody">
|
||||
<!-- 详情内容将在这里动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentData = [];
|
||||
let currentInputFormat = 'ushort';
|
||||
let allResults = [];
|
||||
let smartAnalysisResults = {};
|
||||
let recommendedConfigs = [];
|
||||
|
||||
// 智能分析函数
|
||||
function intelligentAnalysis() {
|
||||
if (!parseInput(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 清空之前的结果
|
||||
clearPreviousResults();
|
||||
|
||||
// 执行智能检测
|
||||
smartAnalysisResults = performSmartDetection();
|
||||
|
||||
// 显示检测结果
|
||||
displaySmartDetection();
|
||||
|
||||
// 生成推荐配置
|
||||
generateRecommendations();
|
||||
|
||||
// 自动应用最佳配置
|
||||
autoApplyBestConfig();
|
||||
|
||||
// 自动转换并显示结果
|
||||
convertData(true);
|
||||
}
|
||||
|
||||
// 自动应用最佳配置
|
||||
function autoApplyBestConfig() {
|
||||
let bestEncoding = null;
|
||||
let bestEncodingScore = 0;
|
||||
let bestByteOrder = null;
|
||||
let bestByteOrderScore = 0;
|
||||
let bestDataType = null;
|
||||
let autoSelected = false;
|
||||
|
||||
// 找出最佳编码格式(只选择高置信度的)
|
||||
for (let [format, result] of Object.entries(smartAnalysisResults.encoding)) {
|
||||
if (result.confidence === 'high' && result.score > bestEncodingScore) {
|
||||
bestEncoding = format;
|
||||
bestEncodingScore = result.score;
|
||||
}
|
||||
}
|
||||
|
||||
// 找出最佳字节序(只选择高置信度的)
|
||||
for (let [order, result] of Object.entries(smartAnalysisResults.byteOrder)) {
|
||||
if (result.confidence === 'high' && result.score > bestByteOrderScore) {
|
||||
bestByteOrder = order;
|
||||
bestByteOrderScore = result.score;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据推荐配置确定最佳数据类型
|
||||
if (recommendedConfigs.length > 0 && recommendedConfigs[0].confidence === 'high') {
|
||||
bestDataType = recommendedConfigs[0].dataType;
|
||||
}
|
||||
|
||||
// 应用高置信度的配置
|
||||
if (bestEncoding && bestEncodingScore > 80) {
|
||||
document.getElementById('encoding').value = bestEncoding;
|
||||
autoSelected = true;
|
||||
}
|
||||
|
||||
if (bestByteOrder && bestByteOrderScore > 70) {
|
||||
document.getElementById('byteOrder').value = bestByteOrder;
|
||||
autoSelected = true;
|
||||
}
|
||||
|
||||
if (bestDataType) {
|
||||
document.getElementById('targetType').value = bestDataType;
|
||||
autoSelected = true;
|
||||
}
|
||||
|
||||
// 显示自动选择提示
|
||||
if (autoSelected) {
|
||||
document.getElementById('autoSelectNotice').style.display = 'flex';
|
||||
setTimeout(() => {
|
||||
document.getElementById('autoSelectNotice').style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行智能检测
|
||||
function performSmartDetection() {
|
||||
const bytes = convertToBytes(currentData, currentInputFormat);
|
||||
const analysis = {
|
||||
encoding: analyzeEncoding(bytes),
|
||||
byteOrder: analyzeByteOrder(bytes),
|
||||
dataType: analyzeDataType(bytes),
|
||||
patterns: analyzePatterns(bytes)
|
||||
};
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
// 分析编码格式
|
||||
function analyzeEncoding(bytes) {
|
||||
const results = {};
|
||||
|
||||
// BCD检测
|
||||
let bcdValid = true;
|
||||
let bcdScore = 0;
|
||||
for (let byte of bytes) {
|
||||
const high = (byte >> 4) & 0xF;
|
||||
const low = byte & 0xF;
|
||||
if (high <= 9 && low <= 9) {
|
||||
bcdScore++;
|
||||
} else {
|
||||
bcdValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bcdValid && bytes.length > 0) {
|
||||
results.BCD = {
|
||||
confidence: bcdScore === bytes.length ? 'high' : 'medium',
|
||||
score: (bcdScore / bytes.length) * 100,
|
||||
reason: `${bcdScore}/${bytes.length} 字节符合BCD格式`
|
||||
};
|
||||
} else {
|
||||
results.BCD = {
|
||||
confidence: 'low',
|
||||
score: 0,
|
||||
reason: '存在非BCD格式字节'
|
||||
};
|
||||
}
|
||||
|
||||
// ASCII检测
|
||||
let asciiCount = 0;
|
||||
let printableCount = 0;
|
||||
for (let byte of bytes) {
|
||||
if (byte >= 0 && byte <= 127) {
|
||||
asciiCount++;
|
||||
if (byte >= 32 && byte <= 126) {
|
||||
printableCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const asciiRatio = asciiCount / bytes.length;
|
||||
const printableRatio = printableCount / bytes.length;
|
||||
|
||||
if (printableRatio > 0.7) {
|
||||
results.ASCII = {
|
||||
confidence: 'high',
|
||||
score: printableRatio * 100,
|
||||
reason: `${printableCount}/${bytes.length} 字节为可打印字符`
|
||||
};
|
||||
} else if (asciiRatio > 0.8) {
|
||||
results.ASCII = {
|
||||
confidence: 'medium',
|
||||
score: asciiRatio * 100,
|
||||
reason: `${asciiCount}/${bytes.length} 字节为ASCII字符`
|
||||
};
|
||||
} else {
|
||||
results.ASCII = {
|
||||
confidence: 'low',
|
||||
score: asciiRatio * 100,
|
||||
reason: 'ASCII字符比例较低'
|
||||
};
|
||||
}
|
||||
|
||||
// DEC检测(默认总是可能的)
|
||||
results.DEC = {
|
||||
confidence: 'high',
|
||||
score: 100,
|
||||
reason: '十进制编码始终有效'
|
||||
};
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 分析字节序
|
||||
function analyzeByteOrder(bytes) {
|
||||
const results = {};
|
||||
const byteOrders = ['ABCD', 'DCBA', 'CDAB', 'BADC'];
|
||||
|
||||
// 尝试将字节转换为浮点数,看哪种字节序产生最合理的结果
|
||||
for (let byteOrder of byteOrders) {
|
||||
let validFloatCount = 0;
|
||||
let reasonableFloatCount = 0;
|
||||
let totalAttempts = 0;
|
||||
|
||||
for (let i = 0; i < bytes.length - 3; i += 4) {
|
||||
try {
|
||||
const orderedBytes = applyByteOrder([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]], byteOrder, 4);
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
for (let j = 0; j < 4; j++) {
|
||||
view.setUint8(j, orderedBytes[j]);
|
||||
}
|
||||
const floatValue = view.getFloat32(0, true);
|
||||
totalAttempts++;
|
||||
|
||||
if (isFinite(floatValue) && !isNaN(floatValue)) {
|
||||
validFloatCount++;
|
||||
// 检查是否为合理的浮点数(不是极大或极小的数)
|
||||
if (Math.abs(floatValue) > 0.0001 && Math.abs(floatValue) < 1000000) {
|
||||
reasonableFloatCount++;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略转换错误
|
||||
}
|
||||
}
|
||||
|
||||
let confidence = 'low';
|
||||
let score = 0;
|
||||
let reason = '';
|
||||
|
||||
if (totalAttempts > 0) {
|
||||
const validRatio = validFloatCount / totalAttempts;
|
||||
const reasonableRatio = reasonableFloatCount / totalAttempts;
|
||||
score = reasonableRatio * 100;
|
||||
|
||||
if (reasonableRatio > 0.7) {
|
||||
confidence = 'high';
|
||||
reason = `${reasonableFloatCount}/${totalAttempts} 个浮点数转换合理`;
|
||||
} else if (validRatio > 0.5) {
|
||||
confidence = 'medium';
|
||||
reason = `${validFloatCount}/${totalAttempts} 个浮点数转换有效`;
|
||||
} else {
|
||||
reason = '浮点数转换效果不佳';
|
||||
}
|
||||
} else {
|
||||
reason = '数据长度不足以进行浮点数分析';
|
||||
}
|
||||
|
||||
results[byteOrder] = {
|
||||
confidence: confidence,
|
||||
score: score,
|
||||
reason: reason
|
||||
};
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 分析数据类型
|
||||
function analyzeDataType(bytes) {
|
||||
const results = {};
|
||||
|
||||
// 检查16位整数的合理性
|
||||
let reasonableShortCount = 0;
|
||||
let totalShorts = 0;
|
||||
for (let i = 0; i < bytes.length - 1; i += 2) {
|
||||
const value = bytes[i] | (bytes[i + 1] << 8);
|
||||
totalShorts++;
|
||||
// 检查是否为合理的16位数值范围
|
||||
if (value < 10000 && value > 0) {
|
||||
reasonableShortCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalShorts > 0) {
|
||||
const shortRatio = reasonableShortCount / totalShorts;
|
||||
results.short = {
|
||||
confidence: shortRatio > 0.6 ? 'high' : shortRatio > 0.3 ? 'medium' : 'low',
|
||||
score: shortRatio * 100,
|
||||
reason: `${reasonableShortCount}/${totalShorts} 个16位值合理`
|
||||
};
|
||||
}
|
||||
|
||||
// 检查字节级数据
|
||||
let reasonableByteCount = 0;
|
||||
for (let byte of bytes) {
|
||||
if (byte > 0 && byte < 250) {
|
||||
reasonableByteCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const byteRatio = reasonableByteCount / bytes.length;
|
||||
results.byte = {
|
||||
confidence: byteRatio > 0.8 ? 'high' : byteRatio > 0.5 ? 'medium' : 'low',
|
||||
score: byteRatio * 100,
|
||||
reason: `${reasonableByteCount}/${bytes.length} 个字节值合理`
|
||||
};
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 分析数据模式
|
||||
function analyzePatterns(bytes) {
|
||||
const patterns = {};
|
||||
|
||||
// 检查重复模式
|
||||
const counts = {};
|
||||
for (let byte of bytes) {
|
||||
counts[byte] = (counts[byte] || 0) + 1;
|
||||
}
|
||||
|
||||
const uniqueValues = Object.keys(counts).length;
|
||||
const maxRepeats = Math.max(...Object.values(counts));
|
||||
|
||||
patterns.repetition = {
|
||||
uniqueValues: uniqueValues,
|
||||
maxRepeats: maxRepeats,
|
||||
entropy: uniqueValues / bytes.length
|
||||
};
|
||||
|
||||
// 检查零值模式
|
||||
const zeroCount = counts[0] || 0;
|
||||
patterns.zeros = {
|
||||
count: zeroCount,
|
||||
ratio: zeroCount / bytes.length
|
||||
};
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
// 显示智能检测结果 - 紧凑版
|
||||
function displaySmartDetection() {
|
||||
const panel = document.getElementById('smartDetection');
|
||||
const results = document.getElementById('detectionResults');
|
||||
|
||||
let html = '';
|
||||
|
||||
// 编码格式检测结果 - 只显示高置信度的
|
||||
const highConfidenceEncodings = Object.entries(smartAnalysisResults.encoding)
|
||||
.filter(([_, result]) => result.confidence !== 'low')
|
||||
.sort((a, b) => b[1].score - a[1].score);
|
||||
|
||||
if (highConfidenceEncodings.length > 0) {
|
||||
html += '<div class="detection-category">编码格式</div>';
|
||||
for (let [format, result] of highConfidenceEncodings.slice(0, 3)) {
|
||||
const confidenceClass = `confidence-${result.confidence}`;
|
||||
const badgeClass = `badge-${result.confidence}`;
|
||||
|
||||
html += `
|
||||
<div class="detection-item ${confidenceClass}">
|
||||
<div>
|
||||
<strong>${format}</strong> - ${result.reason}
|
||||
</div>
|
||||
<div>
|
||||
<span class="confidence-badge ${badgeClass}">${Math.round(result.score)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 字节序检测结果 - 只显示高置信度的
|
||||
const highConfidenceByteOrders = Object.entries(smartAnalysisResults.byteOrder)
|
||||
.filter(([_, result]) => result.confidence !== 'low')
|
||||
.sort((a, b) => b[1].score - a[1].score);
|
||||
|
||||
if (highConfidenceByteOrders.length > 0) {
|
||||
html += '<div class="detection-category">字节序</div>';
|
||||
for (let [order, result] of highConfidenceByteOrders.slice(0, 3)) {
|
||||
const confidenceClass = `confidence-${result.confidence}`;
|
||||
const badgeClass = `badge-${result.confidence}`;
|
||||
|
||||
html += `
|
||||
<div class="detection-item ${confidenceClass}">
|
||||
<div>
|
||||
<strong>${order}</strong> - ${result.reason}
|
||||
</div>
|
||||
<div>
|
||||
<span class="confidence-badge ${badgeClass}">${Math.round(result.score)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
results.innerHTML = html || '<div class="detection-item">未发现高置信度的格式特征</div>';
|
||||
panel.style.display = 'block';
|
||||
}
|
||||
|
||||
// 生成推荐配置
|
||||
function generateRecommendations() {
|
||||
recommendedConfigs = [];
|
||||
|
||||
// 找出最佳编码格式
|
||||
let bestEncoding = 'DEC';
|
||||
let bestEncodingScore = 0;
|
||||
for (let [format, result] of Object.entries(smartAnalysisResults.encoding)) {
|
||||
if (result.score > bestEncodingScore && result.confidence !== 'low') {
|
||||
bestEncoding = format;
|
||||
bestEncodingScore = result.score;
|
||||
}
|
||||
}
|
||||
|
||||
// 找出最佳字节序
|
||||
let bestByteOrder = 'DCBA';
|
||||
let bestByteOrderScore = 0;
|
||||
for (let [order, result] of Object.entries(smartAnalysisResults.byteOrder)) {
|
||||
if (result.score > bestByteOrderScore) {
|
||||
bestByteOrder = order;
|
||||
bestByteOrderScore = result.score;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成推荐配置
|
||||
if (smartAnalysisResults.encoding.ASCII.confidence === 'high') {
|
||||
recommendedConfigs.push({
|
||||
encoding: 'DEC',
|
||||
byteOrder: 'DCBA',
|
||||
dataType: 'ascii',
|
||||
reason: 'ASCII字符检测置信度高,推荐字符串解析',
|
||||
confidence: 'high'
|
||||
});
|
||||
}
|
||||
|
||||
if (smartAnalysisResults.encoding.BCD.confidence === 'high') {
|
||||
recommendedConfigs.push({
|
||||
encoding: 'BCD',
|
||||
byteOrder: bestByteOrder,
|
||||
dataType: 'uint',
|
||||
reason: 'BCD编码检测置信度高,推荐无符号整数',
|
||||
confidence: 'high'
|
||||
});
|
||||
}
|
||||
|
||||
// 浮点数推荐
|
||||
if (bestByteOrderScore > 50) {
|
||||
recommendedConfigs.push({
|
||||
encoding: bestEncoding,
|
||||
byteOrder: bestByteOrder,
|
||||
dataType: 'float',
|
||||
reason: `${bestByteOrder}字节序浮点数转换效果最佳`,
|
||||
confidence: bestByteOrderScore > 70 ? 'high' : 'medium'
|
||||
});
|
||||
}
|
||||
|
||||
// 整数推荐
|
||||
if (smartAnalysisResults.dataType && smartAnalysisResults.dataType.short &&
|
||||
smartAnalysisResults.dataType.short.confidence !== 'low') {
|
||||
recommendedConfigs.push({
|
||||
encoding: bestEncoding,
|
||||
byteOrder: bestByteOrder,
|
||||
dataType: 'ushort',
|
||||
reason: '16位无符号整数解析效果较好',
|
||||
confidence: smartAnalysisResults.dataType.short.confidence
|
||||
});
|
||||
}
|
||||
|
||||
// 显示推荐配置
|
||||
displayRecommendations();
|
||||
}
|
||||
|
||||
// 显示推荐配置
|
||||
function displayRecommendations() {
|
||||
const panel = document.getElementById('recommendationPanel');
|
||||
const container = document.getElementById('recommendedConfigs');
|
||||
|
||||
if (recommendedConfigs.length === 0) {
|
||||
panel.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
recommendedConfigs.forEach((config, index) => {
|
||||
const badgeText = config.confidence === 'high' ? '强烈推荐' : '推荐';
|
||||
html += `
|
||||
<div class="recommended-item" data-index="${index}">
|
||||
<div class="recommended-badge">${badgeText}</div>
|
||||
<div>
|
||||
<strong>${config.dataType.toUpperCase()} - ${config.byteOrder} - ${config.encoding}</strong><br>
|
||||
<small>${config.reason}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
panel.style.display = 'block';
|
||||
}
|
||||
|
||||
// 应用推荐配置
|
||||
function applyRecommendation() {
|
||||
if (recommendedConfigs.length === 0) {
|
||||
alert('没有推荐配置可应用');
|
||||
return;
|
||||
}
|
||||
|
||||
// 应用第一个(最佳)推荐配置
|
||||
const config = recommendedConfigs[0];
|
||||
document.getElementById('targetType').value = config.dataType;
|
||||
document.getElementById('byteOrder').value = config.byteOrder;
|
||||
document.getElementById('encoding').value = config.encoding;
|
||||
|
||||
// 自动转换
|
||||
convertData(true);
|
||||
}
|
||||
|
||||
// 解析输入数据(增强版)
|
||||
function parseInput(showMessage = true) {
|
||||
const rawDataElement = document.getElementById('rawData');
|
||||
const inputFormat = document.getElementById('inputFormat').value;
|
||||
const validation = document.getElementById('inputValidation');
|
||||
|
||||
try {
|
||||
const inputText = rawDataElement.value.trim();
|
||||
if (!inputText) {
|
||||
throw new Error('请输入数据');
|
||||
}
|
||||
|
||||
// 清空之前的结果
|
||||
clearPreviousResults();
|
||||
|
||||
let parsedData = [];
|
||||
currentInputFormat = inputFormat;
|
||||
|
||||
if (inputFormat === 'hex') {
|
||||
const hexPattern = /0x[0-9A-Fa-f]+|[0-9A-Fa-f]+/g;
|
||||
const hexMatches = inputText.match(hexPattern);
|
||||
if (!hexMatches) {
|
||||
throw new Error('无效的十六进制格式');
|
||||
}
|
||||
parsedData = hexMatches.map(hex => {
|
||||
const value = parseInt(hex.replace('0x', ''), 16);
|
||||
return value;
|
||||
});
|
||||
} else {
|
||||
const cleanText = inputText.replace(/[\[\]]/g, '');
|
||||
const values = cleanText.split(/[,\s\n]+/).filter(v => v.trim());
|
||||
|
||||
parsedData = values.map(v => {
|
||||
const num = v.includes('0x') ? parseInt(v, 16) : parseInt(v);
|
||||
if (isNaN(num)) {
|
||||
throw new Error(`无效的数值: ${v}`);
|
||||
}
|
||||
return num;
|
||||
});
|
||||
}
|
||||
|
||||
validateDataRange(parsedData, inputFormat);
|
||||
currentData = parsedData;
|
||||
|
||||
if (showMessage) {
|
||||
validation.innerHTML = `<div class="success">✅ 成功解析 ${parsedData.length} 个数据项</div>`;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
if (showMessage) {
|
||||
validation.innerHTML = `<div class="error">❌ ${error.message}</div>`;
|
||||
}
|
||||
currentData = [];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证数据范围
|
||||
function validateDataRange(data, format) {
|
||||
const ranges = {
|
||||
'byte': [0, 255],
|
||||
'sbyte': [-128, 127],
|
||||
'ushort': [0, 65535],
|
||||
'short': [-32768, 32767],
|
||||
'int': [-2147483648, 2147483647],
|
||||
'uint': [0, 4294967295],
|
||||
'hex': [0, 4294967295]
|
||||
};
|
||||
|
||||
if (ranges[format]) {
|
||||
const [min, max] = ranges[format];
|
||||
for (let value of data) {
|
||||
if (value < min || value > max) {
|
||||
throw new Error(`数值 ${value} 超出 ${format} 类型范围 [${min}, ${max}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换数据(增强版)
|
||||
function convertData(isSmartMode = false) {
|
||||
if (currentData.length === 0) {
|
||||
alert('请先输入并解析数据');
|
||||
return;
|
||||
}
|
||||
|
||||
const targetType = document.getElementById('targetType').value;
|
||||
const byteOrder = document.getElementById('byteOrder').value;
|
||||
const encoding = document.getElementById('encoding').value;
|
||||
|
||||
document.getElementById('resultsSection').style.display = 'block';
|
||||
|
||||
// 生成所有结果
|
||||
allResults = generateAllResults();
|
||||
|
||||
// 计算统计信息
|
||||
const statistics = calculateStatistics();
|
||||
displayStatistics(statistics);
|
||||
|
||||
// 自动滚动到结果区域并切换标签
|
||||
setTimeout(() => {
|
||||
const resultsSection = document.getElementById('resultsSection');
|
||||
resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
// 切换到相应的标签
|
||||
if (!isSmartMode) {
|
||||
switchTabProgrammatically('all-results');
|
||||
} else {
|
||||
switchTabProgrammatically('smart-results');
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 显示结果
|
||||
if (isSmartMode) {
|
||||
displaySmartResults();
|
||||
} else {
|
||||
displayResults(allResults, targetType, byteOrder, encoding);
|
||||
}
|
||||
|
||||
displayRawData();
|
||||
}
|
||||
|
||||
// 更新结果计数器
|
||||
function updateResultCounter(results = null) {
|
||||
const counter = document.getElementById('allResultsCounter');
|
||||
|
||||
if (results === null) {
|
||||
// 如果没有传入结果,隐藏计数器
|
||||
counter.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const validResults = results.filter(r => r.isValid).length;
|
||||
counter.textContent = validResults;
|
||||
counter.style.display = validResults > 0 ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
// 生成所有转换结果(增强版)
|
||||
function generateAllResults() {
|
||||
const results = [];
|
||||
const byteOrders = ['ABCD', 'DCBA', 'CDAB', 'BADC'];
|
||||
const encodings = ['DEC', 'BCD', 'HEX'];
|
||||
const dataTypes = ['bool', 'byte', 'sbyte', 'short', 'ushort', 'int', 'uint', 'long', 'ulong', 'float', 'double', 'ascii'];
|
||||
|
||||
const bytes = convertToBytes(currentData, currentInputFormat);
|
||||
|
||||
for (let byteOrder of byteOrders) {
|
||||
for (let encoding of encodings) {
|
||||
for (let dataType of dataTypes) {
|
||||
try {
|
||||
const result = convertWithParams(bytes, dataType, byteOrder, encoding);
|
||||
if (result.success) {
|
||||
// 计算智能评分
|
||||
const smartScore = calculateSmartScore(result, dataType, byteOrder, encoding);
|
||||
|
||||
results.push({
|
||||
inputFormat: currentInputFormat,
|
||||
dataType,
|
||||
byteOrder,
|
||||
encoding,
|
||||
result: result.data,
|
||||
displayValue: result.displayValue,
|
||||
isValid: result.isValid,
|
||||
smartScore: smartScore,
|
||||
isRecommended: smartScore > 70,
|
||||
originalBytes: bytes
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// 记录失败的转换
|
||||
results.push({
|
||||
inputFormat: currentInputFormat,
|
||||
dataType,
|
||||
byteOrder,
|
||||
encoding,
|
||||
result: null,
|
||||
displayValue: '转换失败',
|
||||
isValid: false,
|
||||
smartScore: 0,
|
||||
isRecommended: false,
|
||||
error: error.message,
|
||||
originalBytes: bytes
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// 计算智能评分
|
||||
function calculateSmartScore(result, dataType, byteOrder, encoding) {
|
||||
let score = 0;
|
||||
|
||||
// 基础有效性评分
|
||||
if (result.isValid) {
|
||||
score += 30;
|
||||
}
|
||||
|
||||
// 根据智能分析结果加分
|
||||
if (smartAnalysisResults.encoding && smartAnalysisResults.encoding[encoding]) {
|
||||
const encodingAnalysis = smartAnalysisResults.encoding[encoding];
|
||||
if (encodingAnalysis.confidence === 'high') {
|
||||
score += 25;
|
||||
} else if (encodingAnalysis.confidence === 'medium') {
|
||||
score += 15;
|
||||
}
|
||||
}
|
||||
|
||||
if (smartAnalysisResults.byteOrder && smartAnalysisResults.byteOrder[byteOrder]) {
|
||||
const byteOrderAnalysis = smartAnalysisResults.byteOrder[byteOrder];
|
||||
if (byteOrderAnalysis.confidence === 'high') {
|
||||
score += 25;
|
||||
} else if (byteOrderAnalysis.confidence === 'medium') {
|
||||
score += 15;
|
||||
}
|
||||
}
|
||||
|
||||
// 数据类型特定评分
|
||||
if (dataType === 'float' || dataType === 'double') {
|
||||
if (result.data && result.data.length > 0) {
|
||||
const validNumbers = result.data.filter(v => isFinite(v) && !isNaN(v) && Math.abs(v) > 0.0001 && Math.abs(v) < 1000000).length;
|
||||
score += (validNumbers / result.data.length) * 20;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.min(100, score);
|
||||
}
|
||||
|
||||
// 计算统计信息
|
||||
function calculateStatistics() {
|
||||
const stats = {
|
||||
total: allResults.length,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
recommended: 0,
|
||||
byEncoding: {},
|
||||
byByteOrder: {},
|
||||
byDataType: {}
|
||||
};
|
||||
|
||||
for (let result of allResults) {
|
||||
if (result.isValid) {
|
||||
stats.successful++;
|
||||
} else {
|
||||
stats.failed++;
|
||||
}
|
||||
|
||||
if (result.isRecommended) {
|
||||
stats.recommended++;
|
||||
}
|
||||
|
||||
// 按分类统计
|
||||
stats.byEncoding[result.encoding] = (stats.byEncoding[result.encoding] || 0) + 1;
|
||||
stats.byByteOrder[result.byteOrder] = (stats.byByteOrder[result.byteOrder] || 0) + 1;
|
||||
stats.byDataType[result.dataType] = (stats.byDataType[result.dataType] || 0) + 1;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
// 显示统计信息
|
||||
function displayStatistics(stats) {
|
||||
const panel = document.getElementById('statisticsPanel');
|
||||
const grid = document.getElementById('statisticsGrid');
|
||||
|
||||
const successRate = ((stats.successful / stats.total) * 100).toFixed(1);
|
||||
const recommendRate = ((stats.recommended / stats.total) * 100).toFixed(1);
|
||||
|
||||
grid.innerHTML = `
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">${stats.total}</div>
|
||||
<div class="stat-label">总转换数</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">${stats.successful}</div>
|
||||
<div class="stat-label">成功转换</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">${stats.failed}</div>
|
||||
<div class="stat-label">转换失败</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">${stats.recommended}</div>
|
||||
<div class="stat-label">智能推荐</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">${successRate}%</div>
|
||||
<div class="stat-label">成功率</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-value">${recommendRate}%</div>
|
||||
<div class="stat-label">推荐率</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
panel.style.display = 'block';
|
||||
}
|
||||
|
||||
// 显示智能推荐结果
|
||||
function displaySmartResults() {
|
||||
const grid = document.getElementById('smartResultsGrid');
|
||||
|
||||
// 按智能评分排序
|
||||
const sortedResults = [...allResults].sort((a, b) => b.smartScore - a.smartScore);
|
||||
|
||||
// 只显示评分较高的结果
|
||||
const topResults = sortedResults.filter(r => r.smartScore > 30).slice(0, 20);
|
||||
|
||||
grid.innerHTML = '';
|
||||
for (let result of topResults) {
|
||||
const element = createResultElement(result, true);
|
||||
grid.appendChild(element);
|
||||
}
|
||||
|
||||
if (topResults.length === 0) {
|
||||
grid.innerHTML = '<div class="result-item">没有找到高置信度的转换结果</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 创建结果元素(增强版)
|
||||
function createResultElement(result, showSmartInfo = false) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'result-item';
|
||||
|
||||
if (result.isRecommended) {
|
||||
div.classList.add('recommended');
|
||||
}
|
||||
|
||||
if (!result.isValid) {
|
||||
div.classList.add('failed');
|
||||
}
|
||||
|
||||
let smartInfo = '';
|
||||
if (showSmartInfo && result.smartScore !== undefined) {
|
||||
const scoreClass = result.smartScore > 70 ? 'success' : result.smartScore > 40 ? 'warning' : 'error';
|
||||
smartInfo = `
|
||||
<div class="success-indicator ${scoreClass}" title="智能评分: ${result.smartScore}"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
let statusIcon = '✅';
|
||||
if (!result.isValid) {
|
||||
statusIcon = '❌';
|
||||
} else if (result.isRecommended) {
|
||||
statusIcon = '⭐';
|
||||
}
|
||||
|
||||
const resultIndex = allResults.indexOf(result);
|
||||
|
||||
div.innerHTML = `
|
||||
${smartInfo}
|
||||
<div class="result-header">
|
||||
<span class="result-header-sub">${statusIcon} ${result.inputFormat.toUpperCase()} → ${result.dataType.toUpperCase()} - ${result.byteOrder} - ${result.encoding}</span>
|
||||
<div>
|
||||
<button class="copy-btn" data-copy-value="${result.displayValue.replace(/"/g, '"')}" onclick="copyResultFromButton(this)" title="复制结果">📋</button>
|
||||
<button class="details-btn" onclick="showConversionDetails(${resultIndex})" title="查看详情">🔍</button>
|
||||
${showSmartInfo ? `<span style="font-size: 12px; color: #6c757d;">${result.smartScore}分</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-value">${result.displayValue}</div>
|
||||
`;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// 从按钮复制结果的新方法
|
||||
function copyResultFromButton(button) {
|
||||
const value = button.getAttribute('data-copy-value').replace(/"/g, '"');
|
||||
copyResult(value);
|
||||
}
|
||||
|
||||
// 显示转换详情(增强版,包含禁用body滚动)
|
||||
function showConversionDetails(resultIndex) {
|
||||
const result = allResults[resultIndex];
|
||||
if (!result) return;
|
||||
|
||||
const modal = document.getElementById('detailsModal');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalBody = document.getElementById('modalBody');
|
||||
|
||||
modalTitle.textContent = `${result.inputFormat.toUpperCase()} → ${result.dataType.toUpperCase()} 转换详情`;
|
||||
|
||||
// 生成详细内容
|
||||
const detailsHtml = generateConversionDetails(result);
|
||||
modalBody.innerHTML = detailsHtml;
|
||||
|
||||
// 禁用body滚动
|
||||
document.body.classList.add('modal-open');
|
||||
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
|
||||
// 生成转换详情内容
|
||||
function generateConversionDetails(result) {
|
||||
const bytes = result.originalBytes;
|
||||
const byteOrders = ['ABCD', 'DCBA', 'CDAB', 'BADC'];
|
||||
|
||||
let html = `
|
||||
<div class="detail-section">
|
||||
<div class="detail-title">📤 转换结果</div>
|
||||
<div class="byteorder-test">
|
||||
<strong>当前配置:</strong>
|
||||
<div class="byteorder-item">
|
||||
<span class="byteorder-label">数据类型</span>
|
||||
<span class="byteorder-value">${result.dataType.toUpperCase()}</span>
|
||||
<span class="byteorder-status">${result.isValid ? '✅' : '❌'}</span>
|
||||
</div>
|
||||
<div class="byteorder-item">
|
||||
<span class="byteorder-label">字节序</span>
|
||||
<span class="byteorder-value">${result.byteOrder}</span>
|
||||
<span class="byteorder-status">🔄</span>
|
||||
</div>
|
||||
<div class="byteorder-item">
|
||||
<span class="byteorder-label">编码格式</span>
|
||||
<span class="byteorder-value">${result.encoding}</span>
|
||||
<span class="byteorder-status">🎯</span>
|
||||
</div>
|
||||
<div class="byteorder-item">
|
||||
<span class="byteorder-label">转换结果</span>
|
||||
<span class="byteorder-value" style="max-width: 300px; word-break: break-all;">${result.displayValue}</span>
|
||||
<span class="byteorder-status">${result.isValid ? '✅' : '❌'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 显示所有字节序测试结果
|
||||
if (result.dataType !== 'ascii' && result.dataType !== 'bool' && result.dataType !== 'byte') {
|
||||
html += `
|
||||
<div class="detail-section">
|
||||
<div class="detail-title">🧪 所有字节序格式测试结果</div>
|
||||
<div class="byteorder-test">
|
||||
`;
|
||||
|
||||
for (let byteOrder of byteOrders) {
|
||||
try {
|
||||
const testResult = convertWithParams(bytes, result.dataType, byteOrder, result.encoding);
|
||||
const isSuccess = testResult.success && testResult.isValid;
|
||||
const displayValue = testResult.success ? testResult.displayValue : '转换失败';
|
||||
|
||||
html += `
|
||||
<div class="byteorder-item">
|
||||
<span class="byteorder-label">${byteOrder}格式</span>
|
||||
<span class="byteorder-value">[${displayValue}]</span>
|
||||
<span class="byteorder-status">${isSuccess ? '✅' : '❌'}</span>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
html += `
|
||||
<div class="byteorder-item">
|
||||
<span class="byteorder-label">${byteOrder}格式</span>
|
||||
<span class="byteorder-value">转换失败</span>
|
||||
<span class="byteorder-status">❌</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
// 转换原理说明
|
||||
html += `
|
||||
<div class="detail-section">
|
||||
<div class="detail-title">📚 转换原理说明</div>
|
||||
<div class="principle-box">
|
||||
<div class="principle-item">
|
||||
<strong>问题分析:</strong>
|
||||
<span>你输入了${currentData.length}个${currentInputFormat}值:[${currentData.slice(0, 10).join(', ')}${currentData.length > 10 ? '...' : ''}],转换为${result.dataType}类型。</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 具体的转换分析
|
||||
if (currentInputFormat === 'ushort' && result.dataType === 'int') {
|
||||
html += `
|
||||
<div class="principle-item">
|
||||
<strong>数据分析:</strong>
|
||||
<span>每2个16位ushort值被组合成1个32位int值,这可能是因为原始数据是32位整数被拆分读取造成的。</span>
|
||||
</div>
|
||||
`;
|
||||
} else if (currentInputFormat === 'int' && result.dataType === 'ushort') {
|
||||
html += `
|
||||
<div class="principle-item">
|
||||
<strong>数据分析:</strong>
|
||||
<span>每个32位int被拆分成2个16位ushort读取,如果读取结果不符合预期,可能存在字节序转换问题。</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 字节序说明
|
||||
html += `
|
||||
<div class="principle-item">
|
||||
<strong>字节序说明:</strong>
|
||||
<div style="margin-top: 8px;">
|
||||
<div>• <strong>ABCD (大端序):</strong> 高字节在前,低字节在后,如 0x12345678 存储为 [12][34][56][78]</div>
|
||||
<div>• <strong>DCBA (小端序):</strong> 低字节在前,高字节在后,如 0x12345678 存储为 [78][56][34][12]</div>
|
||||
<div>• <strong>CDAB (中端序):</strong> 字对交换的大端序,西门子PLC常见</div>
|
||||
<div>• <strong>BADC (中端序):</strong> 字对交换的小端序,欧姆龙PLC常见</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 内存布局展示
|
||||
if (bytes.length > 0) {
|
||||
html += `
|
||||
<div class="detail-section">
|
||||
<div class="detail-title">💾 内存布局分析</div>
|
||||
<div class="memory-layout">
|
||||
<div class="memory-step">原始数据: [${currentData.slice(0, 5).join(', ')}${currentData.length > 5 ? '...' : ''}]</div>
|
||||
<div class="memory-step">字节表示: [${bytes.slice(0, 20).map(b => '0x' + b.toString(16).padStart(2, '0')).join(', ')}${bytes.length > 20 ? '...' : ''}]</div>
|
||||
`;
|
||||
|
||||
if (result.dataType === 'float' && bytes.length >= 4) {
|
||||
const firstFourBytes = bytes.slice(0, 4);
|
||||
html += `
|
||||
<div class="memory-step">
|
||||
浮点数转换示例 (前4字节):
|
||||
<div style="margin-top: 5px;">
|
||||
DCBA顺序: <span class="memory-bytes">[${firstFourBytes.map(b => '0x' + b.toString(16).padStart(2, '0')).join('][')}]}</span> → ${getFloatFromBytes(firstFourBytes, 'DCBA')}
|
||||
</div>
|
||||
<div>
|
||||
ABCD顺序: <span class="memory-bytes">[${firstFourBytes.slice().reverse().map(b => '0x' + b.toString(16).padStart(2, '0')).join('][')}]}</span> → ${getFloatFromBytes(firstFourBytes, 'ABCD')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</div></div>';
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// 从字节数组获取浮点数(用于演示)
|
||||
function getFloatFromBytes(bytes, byteOrder) {
|
||||
try {
|
||||
const orderedBytes = applyByteOrder([...bytes], byteOrder, 4);
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
view.setUint8(i, orderedBytes[i]);
|
||||
}
|
||||
const value = view.getFloat32(0, true);
|
||||
return isFinite(value) ? value.toFixed(6) : value.toString();
|
||||
} catch (error) {
|
||||
return '转换失败';
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情模态框(增强版,恢复body滚动)
|
||||
function closeDetailsModal() {
|
||||
document.getElementById('detailsModal').style.display = 'none';
|
||||
// 恢复body滚动
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('detailsModal');
|
||||
if (event.target === modal) {
|
||||
closeDetailsModal();
|
||||
}
|
||||
}
|
||||
|
||||
// ESC键关闭模态框
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.key === 'Escape') {
|
||||
const modal = document.getElementById('detailsModal');
|
||||
if (modal.style.display === 'block') {
|
||||
closeDetailsModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 显示结果
|
||||
function displayResults(results, targetTypeFilter, byteOrderFilter, encodingFilter) {
|
||||
const allResultsGrid = document.getElementById('allResultsGrid');
|
||||
allResultsGrid.innerHTML = '';
|
||||
|
||||
let filteredResults = results;
|
||||
if (targetTypeFilter !== 'all') {
|
||||
filteredResults = filteredResults.filter(r => r.dataType === targetTypeFilter);
|
||||
}
|
||||
if (byteOrderFilter !== 'all') {
|
||||
filteredResults = filteredResults.filter(r => r.byteOrder === byteOrderFilter);
|
||||
}
|
||||
if (encodingFilter !== 'all') {
|
||||
filteredResults = filteredResults.filter(r => r.encoding === encodingFilter);
|
||||
}
|
||||
|
||||
// 更新筛选后的计数器
|
||||
updateResultCounter(filteredResults);
|
||||
|
||||
for (let result of filteredResults) {
|
||||
const element = createResultElement(result);
|
||||
allResultsGrid.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
// 筛选结果
|
||||
function filterResults() {
|
||||
if (allResults.length === 0) return;
|
||||
|
||||
const targetType = document.getElementById('targetType').value;
|
||||
const byteOrder = document.getElementById('byteOrder').value;
|
||||
const encoding = document.getElementById('encoding').value;
|
||||
|
||||
displayResults(allResults, targetType, byteOrder, encoding);
|
||||
}
|
||||
|
||||
// 原有的辅助函数保持不变
|
||||
function convertToBytes(data, inputFormat) {
|
||||
const bytes = [];
|
||||
|
||||
for (let value of data) {
|
||||
switch (inputFormat) {
|
||||
case 'byte':
|
||||
bytes.push(value);
|
||||
break;
|
||||
case 'ushort':
|
||||
case 'short':
|
||||
bytes.push(value & 0xFF);
|
||||
bytes.push((value >> 8) & 0xFF);
|
||||
break;
|
||||
case 'int':
|
||||
case 'uint':
|
||||
case 'hex':
|
||||
bytes.push(value & 0xFF);
|
||||
bytes.push((value >> 8) & 0xFF);
|
||||
bytes.push((value >> 16) & 0xFF);
|
||||
bytes.push((value >> 24) & 0xFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function convertWithParams(bytes, dataType, byteOrder, encoding) {
|
||||
try {
|
||||
const processedBytes = applyEncoding(bytes, encoding);
|
||||
if (!processedBytes.success) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
const result = convertByDataType(processedBytes.data, dataType, byteOrder);
|
||||
return result;
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
function applyEncoding(bytes, encoding) {
|
||||
switch (encoding) {
|
||||
case 'DEC':
|
||||
return { success: true, data: bytes };
|
||||
|
||||
case 'BCD':
|
||||
for (let byte of bytes) {
|
||||
const high = (byte >> 4) & 0xF;
|
||||
const low = byte & 0xF;
|
||||
if (high > 9 || low > 9) {
|
||||
return { success: false, error: 'Invalid BCD format' };
|
||||
}
|
||||
}
|
||||
|
||||
const bcdBytes = bytes.map(byte => {
|
||||
const high = (byte >> 4) & 0xF;
|
||||
const low = byte & 0xF;
|
||||
return high * 10 + low;
|
||||
});
|
||||
return { success: true, data: bcdBytes };
|
||||
|
||||
case 'HEX':
|
||||
return { success: true, data: bytes };
|
||||
|
||||
default:
|
||||
return { success: true, data: bytes };
|
||||
}
|
||||
}
|
||||
|
||||
function convertByDataType(bytes, dataType, byteOrder) {
|
||||
const result = [];
|
||||
|
||||
try {
|
||||
switch (dataType) {
|
||||
case 'bool':
|
||||
for (let byte of bytes) {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result.push((byte & (1 << i)) !== 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'byte':
|
||||
result.push(...bytes);
|
||||
break;
|
||||
|
||||
case 'sbyte':
|
||||
for (let byte of bytes) {
|
||||
result.push(byte > 127 ? byte - 256 : byte);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'short':
|
||||
case 'ushort':
|
||||
for (let i = 0; i < bytes.length - 1; i += 2) {
|
||||
const orderedBytes = applyByteOrder([bytes[i], bytes[i + 1]], byteOrder, 2);
|
||||
const value = orderedBytes[0] | (orderedBytes[1] << 8);
|
||||
result.push(dataType === 'short' && value > 32767 ? value - 65536 : value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'int':
|
||||
case 'uint':
|
||||
for (let i = 0; i < bytes.length - 3; i += 4) {
|
||||
const orderedBytes = applyByteOrder([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]], byteOrder, 4);
|
||||
const value = orderedBytes[0] | (orderedBytes[1] << 8) | (orderedBytes[2] << 16) | (orderedBytes[3] << 24);
|
||||
result.push(dataType === 'int' && value > 2147483647 ? value - 4294967296 : value >>> 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'long':
|
||||
case 'ulong':
|
||||
for (let i = 0; i < bytes.length - 7; i += 8) {
|
||||
const orderedBytes = applyByteOrder(bytes.slice(i, i + 8), byteOrder, 8);
|
||||
let value = BigInt(0);
|
||||
for (let j = 0; j < 8; j++) {
|
||||
value |= BigInt(orderedBytes[j]) << BigInt(j * 8);
|
||||
}
|
||||
result.push(Number(value));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
for (let i = 0; i < bytes.length - 3; i += 4) {
|
||||
const orderedBytes = applyByteOrder([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]], byteOrder, 4);
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
for (let j = 0; j < 4; j++) {
|
||||
view.setUint8(j, orderedBytes[j]);
|
||||
}
|
||||
const floatValue = view.getFloat32(0, true);
|
||||
result.push(floatValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'double':
|
||||
for (let i = 0; i < bytes.length - 7; i += 8) {
|
||||
const orderedBytes = applyByteOrder(bytes.slice(i, i + 8), byteOrder, 8);
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const view = new DataView(buffer);
|
||||
for (let j = 0; j < 8; j++) {
|
||||
view.setUint8(j, orderedBytes[j]);
|
||||
}
|
||||
const doubleValue = view.getFloat64(0, true);
|
||||
result.push(doubleValue);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ascii':
|
||||
const asciiString = bytes.map(byte =>
|
||||
byte >= 32 && byte <= 126 ? String.fromCharCode(byte) : '·'
|
||||
).join('');
|
||||
result.push(asciiString);
|
||||
break;
|
||||
}
|
||||
|
||||
const displayValue = formatDisplayValue(result, dataType);
|
||||
const isValid = validateResult(result, dataType);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
displayValue: displayValue,
|
||||
isValid: isValid
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
function applyByteOrder(bytes, byteOrder, expectedLength) {
|
||||
const paddedBytes = [...bytes];
|
||||
while (paddedBytes.length < expectedLength) {
|
||||
paddedBytes.push(0);
|
||||
}
|
||||
|
||||
switch (byteOrder) {
|
||||
case 'ABCD':
|
||||
return paddedBytes.reverse();
|
||||
case 'DCBA':
|
||||
return paddedBytes;
|
||||
case 'CDAB':
|
||||
if (expectedLength === 4) {
|
||||
return [paddedBytes[2], paddedBytes[3], paddedBytes[0], paddedBytes[1]];
|
||||
} else if (expectedLength === 2) {
|
||||
return [paddedBytes[1], paddedBytes[0]];
|
||||
} else if (expectedLength === 8) {
|
||||
return [paddedBytes[6], paddedBytes[7], paddedBytes[4], paddedBytes[5], paddedBytes[2], paddedBytes[3], paddedBytes[0], paddedBytes[1]];
|
||||
}
|
||||
return paddedBytes;
|
||||
case 'BADC':
|
||||
if (expectedLength === 4) {
|
||||
return [paddedBytes[1], paddedBytes[0], paddedBytes[3], paddedBytes[2]];
|
||||
} else if (expectedLength === 2) {
|
||||
return [paddedBytes[1], paddedBytes[0]];
|
||||
} else if (expectedLength === 8) {
|
||||
return [paddedBytes[1], paddedBytes[0], paddedBytes[3], paddedBytes[2], paddedBytes[5], paddedBytes[4], paddedBytes[7], paddedBytes[6]];
|
||||
}
|
||||
return paddedBytes;
|
||||
default:
|
||||
return paddedBytes;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDisplayValue(result, dataType) {
|
||||
if (result.length === 0) return '';
|
||||
|
||||
switch (dataType) {
|
||||
case 'float':
|
||||
case 'double':
|
||||
return result.map(v => {
|
||||
if (isNaN(v) || !isFinite(v)) {
|
||||
return v.toString();
|
||||
}
|
||||
return parseFloat(v.toFixed(6));
|
||||
}).join(', ');
|
||||
case 'bool':
|
||||
return result.slice(0, 32).map(v => v ? '1' : '0').join('');
|
||||
case 'ascii':
|
||||
return result[0];
|
||||
default:
|
||||
return result.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
function validateResult(result, dataType) {
|
||||
if (result.length === 0) return false;
|
||||
|
||||
switch (dataType) {
|
||||
case 'float':
|
||||
case 'double':
|
||||
return result.some(v => isFinite(v) && !isNaN(v) && Math.abs(v) < 1e10);
|
||||
case 'ascii':
|
||||
return /[a-zA-Z0-9]/.test(result[0]);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function displayRawData() {
|
||||
const rawDataDisplay = document.getElementById('rawDataDisplay');
|
||||
const bytes = convertToBytes(currentData, currentInputFormat);
|
||||
|
||||
rawDataDisplay.innerHTML = `
|
||||
<div class="process-step">
|
||||
<div class="step-title">📥 输入数据 (${currentInputFormat})</div>
|
||||
<div class="step-content">[${currentData.join(', ')}]</div>
|
||||
</div>
|
||||
<div class="process-step">
|
||||
<div class="step-title">🔢 转换为字节数组</div>
|
||||
<div class="step-content">[${bytes.join(', ')}]</div>
|
||||
</div>
|
||||
<div class="process-step">
|
||||
<div class="step-title">🔤 十六进制表示</div>
|
||||
<div class="step-content">[${bytes.map(b => '0x' + b.toString(16).padStart(2, '0').toUpperCase()).join(', ')}]</div>
|
||||
</div>
|
||||
<div class="process-step">
|
||||
<div class="step-title">💾 二进制表示</div>
|
||||
<div class="step-content">[${bytes.map(b => '0b' + b.toString(2).padStart(8, '0')).join(', ')}]</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function copyResult(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
console.log('已复制到剪贴板');
|
||||
});
|
||||
}
|
||||
|
||||
function switchTab(tabName) {
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
|
||||
event.target.classList.add('active');
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
}
|
||||
|
||||
// 程序化切换标签(不依赖event对象)
|
||||
function switchTabProgrammatically(tabName) {
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
|
||||
// 根据标签名找到对应的按钮并激活
|
||||
const tabMapping = {
|
||||
'smart-results': 0,
|
||||
'all-results': 1,
|
||||
'raw-data': 2
|
||||
};
|
||||
|
||||
const buttons = document.querySelectorAll('.tab-btn');
|
||||
const targetIndex = tabMapping[tabName];
|
||||
if (targetIndex !== undefined && buttons[targetIndex]) {
|
||||
buttons[targetIndex].classList.add('active');
|
||||
}
|
||||
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
}
|
||||
|
||||
// 清空之前的结果
|
||||
function clearPreviousResults() {
|
||||
allResults = [];
|
||||
document.getElementById('allResultsGrid').innerHTML = '';
|
||||
document.getElementById('smartResultsGrid').innerHTML = '';
|
||||
document.getElementById('rawDataDisplay').innerHTML = '';
|
||||
document.getElementById('statisticsPanel').style.display = 'none';
|
||||
updateResultCounter(null); // 隐藏计数器
|
||||
}
|
||||
|
||||
function clearInput() {
|
||||
document.getElementById('rawData').value = '';
|
||||
document.getElementById('inputValidation').innerHTML = '';
|
||||
document.getElementById('resultsSection').style.display = 'none';
|
||||
document.getElementById('smartDetection').style.display = 'none';
|
||||
document.getElementById('recommendationPanel').style.display = 'none';
|
||||
document.getElementById('autoSelectNotice').style.display = 'none';
|
||||
|
||||
// 清空所有相关数据和结果
|
||||
currentData = [];
|
||||
smartAnalysisResults = {};
|
||||
recommendedConfigs = [];
|
||||
clearPreviousResults();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('rawData').value = '[13312,0,16128,0,13312,0,8704,0,5888,0]';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user