<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Data转CSV转换器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Arial, sans-serif;
}
body {
background-color: #f0f2f5;
padding: 2rem;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #1a73e8;
margin-bottom: 1.5rem;
font-weight: 600;
}
.form-group {
margin-bottom: 1.2rem;
}
label {
display: block;
margin-bottom: 0.5rem;
color: #5f6368;
font-weight: 500;
}
.input-group {
display: flex;
gap: 0.5rem;
align-items: center;
}
input[type="text"] {
flex: 1;
padding: 0.75rem;
border: 1px solid #dadce0;
border-radius: 4px;
font-size: 1rem;
transition: border 0.2s;
}
input[type="text"]:focus {
outline: none;
border-color: #1a73e8;
}
button {
padding: 0.75rem 1.25rem;
border: none;
border-radius: 4px;
background: #1a73e8;
color: white;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
}
button:hover {
background: #1557b0;
}
button#convertBtn {
background: #34a853;
width: 100%;
padding: 0.9rem;
font-size: 1.05rem;
margin-top: 0.5rem;
}
button#convertBtn:hover {
background: #2d8643;
}
.info-section {
margin: 1.5rem 0;
padding: 1rem;
background: #f8f9fa;
border-radius: 6px;
border-left: 3px solid #1a73e8;
}
.info-title {
font-weight: 600;
color: #202124;
margin-bottom: 0.8rem;
font-size: 1.05rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.8rem;
}
.info-item {
display: flex;
justify-content: space-between;
padding: 0.4rem 0;
}
.info-key {
color: #5f6368;
}
.info-value {
color: #202124;
font-weight: 500;
}
.status {
margin-top: 1rem;
text-align: center;
padding: 0.75rem;
border-radius: 4px;
background: #f1f3f4;
color: #5f6368;
}
.error {
background: #fce8e6;
color: #d93025;
}
.success {
background: #e6f4ea;
color: #137333;
}
select {
width: 100%;
padding: 0.75rem;
border: 1px solid #dadce0;
border-radius: 4px;
font-size: 1rem;
background-color: white;
transition: border 0.2s;
}
select:focus {
outline: none;
border-color: #1a73e8;
}
</style>
</head>
<body>
<div class="container">
<h1>Data转CSV转换器</h1>
<!-- 编码选择 -->
<div class="form-group">
<label for="encodingSelect">选择编码格式</label>
<select id="encodingSelect">
<option value="gb2312">GB2312</option>
<option value="utf-8">UTF-8</option>
<option value="gbk">GBK</option>
<option value="big5">Big5</option>
</select>
</div>
<!-- 输入文件选择 -->
<div class="form-group">
<label for="inputFilePath">输入文件(*.data)</label>
<div class="input-group">
<input type="text" id="inputFilePath" placeholder="未选择文件" readonly>
<button id="browseBtn">浏览</button>
<input type="file" id="fileInput" accept=".data" style="display: none;">
</div>
</div>
<!-- 输出文件路径 -->
<div class="form-group">
<label for="outputFilePath">输出文件(*.csv)</label>
<input type="text" id="outputFilePath" placeholder="自动生成输出路径">
</div>
<!-- 文件信息显示区域 -->
<div class="info-section">
<div class="info-title">文件结构信息</div>
<div class="info-grid">
<div class="info-item">
<span class="info-key">每条记录最大字节数:</span>
<span class="info-value" id="recordLength">--</span>
</div>
<div class="info-item">
<span class="info-key">字段数量:</span>
<span class="info-value" id="fieldCount">--</span>
</div>
<div class="info-item">
<span class="info-key">最大行数:</span>
<span class="info-value" id="maxRows">--</span>
</div>
<div class="info-item">
<span class="info-key">实际行数:</span>
<span class="info-value" id="actualRows">--</span>
</div>
</div>
</div>
<!-- 转换按钮和状态 -->
<button id="convertBtn">开始转换</button>
<div class="status" id="status">就绪</div>
</div>
<script>
// 全局变量存储文件数据和解析信息
let fileData = null;
let fileInfo = {
recordLength: 0,
fieldCount: 0,
maxRows: 0,
actualRows: 0
};
// DOM元素
const browseBtn = document.getElementById('browseBtn');
const fileInput = document.getElementById('fileInput');
const inputFilePath = document.getElementById('inputFilePath');
const outputFilePath = document.getElementById('outputFilePath');
const encodingSelect = document.getElementById('encodingSelect');
const convertBtn = document.getElementById('convertBtn');
const statusEl = document.getElementById('status');
// 信息显示元素
const recordLengthEl = document.getElementById('recordLength');
const fieldCountEl = document.getElementById('fieldCount');
const maxRowsEl = document.getElementById('maxRows');
const actualRowsEl = document.getElementById('actualRows');
// 浏览文件按钮点击事件
browseBtn.addEventListener('click', () => {
fileInput.click();
});
// 文件选择事件
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
// 显示输入文件路径
inputFilePath.value = file.name;
// 自动生成输出文件路径
const outputName = file.name.replace(/\.data$/, '.csv');
outputFilePath.value = outputName;
try {
// 读取文件数据
const reader = new FileReader();
reader.onload = (event) => {
fileData = new Uint8Array(event.target.result);
// 解析文件信息
parseFileInfo();
statusEl.textContent = "文件加载成功";
statusEl.className = "status success";
};
reader.onerror = () => {
throw new Error("文件读取失败");
};
reader.readAsArrayBuffer(file);
} catch (error) {
statusEl.textContent = `错误:${error.message}`;
statusEl.className = "status error";
resetFileInfo();
}
});
// 解析文件信息(从指定地址读取数据)
function parseFileInfo() {
try {
// 每条记录最大字节数:0x020c位置,4字节(小端)
fileInfo.recordLength = readUint32LE(0x020c);
// 字段数量:0x0214位置,4字节(小端)
fileInfo.fieldCount = readUint32LE(0x0214);
// 最大行数:0x0218位置,4字节(小端)
fileInfo.maxRows = readUint32LE(0x0218);
// 实际行数:0x0208位置,4字节(小端)
fileInfo.actualRows = readUint32LE(0x0208);
// 更新界面显示
recordLengthEl.textContent = fileInfo.recordLength;
fieldCountEl.textContent = fileInfo.fieldCount;
maxRowsEl.textContent = fileInfo.maxRows;
actualRowsEl.textContent = fileInfo.actualRows;
} catch (error) {
statusEl.textContent = `解析错误:${error.message}`;
statusEl.className = "status error";
resetFileInfo();
}
}
// 从指定偏移量读取4字节小端无符号整数
function readUint32LE(offset) {
if (!fileData || offset + 4 > fileData.length) {
throw new Error("文件数据不足或格式错误");
}
return (
fileData[offset] +
(fileData[offset + 1] << 8) +
(fileData[offset + 2] << 16) +
(fileData[offset + 3] << 24)
);
}
// 重置文件信息显示
function resetFileInfo() {
fileInfo = {
recordLength: 0,
fieldCount: 0,
maxRows: 0,
actualRows: 0
};
recordLengthEl.textContent = "--";
fieldCountEl.textContent = "--";
maxRowsEl.textContent = "--";
actualRowsEl.textContent = "--";
fileData = null;
}
// 转换按钮点击事件
convertBtn.addEventListener('click', async () => {
if (!fileData) {
statusEl.textContent = "错误:请先选择有效的.data文件";
statusEl.className = "status error";
return;
}
if (!outputFilePath.value) {
statusEl.textContent = "错误:请指定输出文件路径";
statusEl.className = "status error";
return;
}
try {
statusEl.textContent = "正在转换...";
statusEl.className = "status";
convertBtn.disabled = true;
// 执行转换
await convertDataToCsv();
statusEl.textContent = "转换成功!文件已下载";
statusEl.className = "status success";
} catch (error) {
statusEl.textContent = `转换错误:${error.message}`;
statusEl.className = "status error";
} finally {
convertBtn.disabled = false;
}
});
// 核心转换逻辑
async function convertDataToCsv() {
const { recordLength, fieldCount, actualRows } = fileInfo;
const encoding = encodingSelect.value;
const firstRecordOffset = 0x0400;
const fieldLength = recordLength / fieldCount;
if (!Number.isInteger(fieldLength)) {
throw new Error("记录长度不能被字段数量整除,文件结构异常");
}
// 存储CSV内容
const csvLines = [];
// 遍历所有实际记录
for (let row = 0; row < actualRows; row++) {
const rowData = [];
// 计算当前记录的起始偏移量
const recordOffset = firstRecordOffset + row * recordLength;
// 遍历每个字段
for (let field = 0; field < fieldCount; field++) {
const fieldOffset = recordOffset + field * fieldLength;
// 读取字段数据(到\0结束或字段长度结束)
let fieldBytes = [];
for (let i = 0; i < fieldLength; i++) {
const byte = fileData[fieldOffset + i];
if (byte === 0x00) break; // 遇到字符串结束符'\0'停止
fieldBytes.push(byte);
}
// 解码字段(处理不同编码)
let fieldStr = await decodeBytes(fieldBytes, encoding);
// 替换^为英文逗号
fieldStr = fieldStr.replace(/\^/g, ',');
// 去除前后空格
fieldStr = fieldStr.trim();
rowData.push(fieldStr);
}
// 添加当前行到CSV(不使用双引号包裹)
csvLines.push(rowData.join(','));
}
// 生成CSV字符串
const csvContent = csvLines.join('\n');
// 下载文件
downloadFile(csvContent, outputFilePath.value, 'text/csv');
}
// 解码字节数组(支持多种编码)
async function decodeBytes(bytes, encoding) {
// 处理编码名称映射(TextDecoder使用的名称可能不同)
const encodingMap = {
'gb2312': 'gbk', // 浏览器通常用gbk兼容gb2312
'utf-8': 'utf-8',
'gbk': 'gbk',
'big5': 'big5'
};
const decoder = new TextDecoder(encodingMap[encoding] || encoding, { fatal: false });
return decoder.decode(new Uint8Array(bytes));
}
// 下载文件
function downloadFile(content, fileName, mimeType) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
}
</script>
</body>
</html>