发布 0.3.0 并增加 RSA 签名验证
This commit is contained in:
+65
-114
@@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { AlertCircle, Calculator, CheckCircle2, Copy, FileUp, Trash2, Upload } from '@lucide/vue'
|
||||
import { AlertCircle, Calculator, CheckCircle2, Copy, FileUp } from '@lucide/vue'
|
||||
import { computed, onBeforeUnmount, ref, shallowRef } from 'vue'
|
||||
import FileDropField from '@/components/FileDropField.vue'
|
||||
import ToolPage from '@/components/ToolPage.vue'
|
||||
import { useTransactionalFileWorker } from '@/composables/useTransactionalFileWorker'
|
||||
import { copyText } from '@/lib/browser'
|
||||
import { CRC_PRESETS, type CrcParameters } from '@/lib/crc'
|
||||
import type { CrcBlockResult, CrcWorkerRequest, CrcWorkerResponse } from '@/lib/crcWorker'
|
||||
import type { CrcBlockResult, CrcWorkerCalculateRequest, CrcWorkerResponse } from '@/lib/crcWorker'
|
||||
import { MAX_FIRMWARE_FILE_SIZE, type FirmwareFormat } from '@/lib/firmware'
|
||||
|
||||
interface FirmwareData {
|
||||
@@ -17,9 +19,7 @@ interface FirmwareData {
|
||||
}
|
||||
|
||||
const defaultPreset = CRC_PRESETS[0]
|
||||
const fileInput = ref<HTMLInputElement>()
|
||||
const firmware = shallowRef<FirmwareData>()
|
||||
const dragging = ref(false)
|
||||
const selectedPreset = ref(defaultPreset.id)
|
||||
const width = ref(defaultPreset.width)
|
||||
const polynomial = ref(defaultPreset.polynomial.toString(16).toUpperCase())
|
||||
@@ -29,14 +29,8 @@ const inputReflected = ref(defaultPreset.inputReflected)
|
||||
const outputReflected = ref(defaultPreset.outputReflected)
|
||||
const combinedResult = ref('')
|
||||
const blockResults = ref<CrcBlockResult[]>([])
|
||||
const parsing = ref(false)
|
||||
const calculating = ref(false)
|
||||
const error = ref('')
|
||||
const success = ref('')
|
||||
let worker: Worker | undefined
|
||||
let requestSequence = 0
|
||||
let activeParseRequest: number | undefined
|
||||
let activeCalculationRequest: number | undefined
|
||||
|
||||
const activeParameterName = computed(() => CRC_PRESETS.find((preset) => preset.id === selectedPreset.value)?.name ?? '自定义参数')
|
||||
|
||||
@@ -79,109 +73,66 @@ function getParameters(): CrcParameters {
|
||||
}
|
||||
|
||||
function clearResults() {
|
||||
invalidateCalculation()
|
||||
combinedResult.value = ''
|
||||
blockResults.value = []
|
||||
success.value = ''
|
||||
}
|
||||
|
||||
function invalidateResults() {
|
||||
activeCalculationRequest = undefined
|
||||
calculating.value = false
|
||||
clearResults()
|
||||
error.value = ''
|
||||
}
|
||||
|
||||
function destroyWorker() {
|
||||
worker?.terminate()
|
||||
worker = undefined
|
||||
activeParseRequest = undefined
|
||||
activeCalculationRequest = undefined
|
||||
parsing.value = false
|
||||
calculating.value = false
|
||||
}
|
||||
|
||||
function createWorker(): Worker {
|
||||
const nextWorker = new Worker(new URL('../workers/crc.worker.ts', import.meta.url), { type: 'module' })
|
||||
nextWorker.onmessage = (event: MessageEvent<CrcWorkerResponse>) => {
|
||||
if (nextWorker !== worker) return
|
||||
const response = event.data
|
||||
if (response.type === 'parsed') {
|
||||
if (response.id !== activeParseRequest) return
|
||||
activeParseRequest = undefined
|
||||
parsing.value = false
|
||||
firmware.value = {
|
||||
fileName: pendingFileName,
|
||||
fileSize: pendingFileSize,
|
||||
format: response.format,
|
||||
totalBytes: response.totalBytes,
|
||||
blockCount: response.blockCount,
|
||||
warnings: response.warnings,
|
||||
}
|
||||
success.value = `已解析 ${response.blockCount} 个数据块`
|
||||
return
|
||||
const {
|
||||
parsing,
|
||||
calculating,
|
||||
parseFile,
|
||||
calculate: calculateInWorker,
|
||||
invalidateCalculation,
|
||||
destroyWorkers,
|
||||
} = useTransactionalFileWorker<Omit<CrcWorkerCalculateRequest, 'type' | 'id'>, CrcWorkerResponse>({
|
||||
createWorker: () => new Worker(new URL('../workers/crc.worker.ts', import.meta.url), { type: 'module' }),
|
||||
onParsed: (response, selected) => {
|
||||
clearResults()
|
||||
firmware.value = {
|
||||
fileName: selected.name,
|
||||
fileSize: selected.size,
|
||||
format: response.format,
|
||||
totalBytes: response.totalBytes,
|
||||
blockCount: response.blockCount,
|
||||
warnings: response.warnings,
|
||||
}
|
||||
if (response.type === 'calculated') {
|
||||
if (response.id !== activeCalculationRequest) return
|
||||
activeCalculationRequest = undefined
|
||||
calculating.value = false
|
||||
combinedResult.value = response.combinedResult
|
||||
blockResults.value = response.blockResults
|
||||
success.value = `${activeCalculationName} 计算完成`
|
||||
return
|
||||
}
|
||||
if (response.operation === 'parse' && response.id === activeParseRequest) {
|
||||
activeParseRequest = undefined
|
||||
parsing.value = false
|
||||
firmware.value = undefined
|
||||
error.value = response.error
|
||||
} else if (response.operation === 'calculate' && response.id === activeCalculationRequest) {
|
||||
activeCalculationRequest = undefined
|
||||
calculating.value = false
|
||||
error.value = response.error
|
||||
}
|
||||
}
|
||||
nextWorker.onerror = () => {
|
||||
if (nextWorker !== worker) return
|
||||
destroyWorker()
|
||||
success.value = `已解析 ${response.blockCount} 个数据块`
|
||||
},
|
||||
onCalculated: (response) => {
|
||||
combinedResult.value = response.combinedResult
|
||||
blockResults.value = response.blockResults
|
||||
success.value = `${activeCalculationName} 计算完成`
|
||||
},
|
||||
onParseError: (message) => { error.value = message },
|
||||
onCalculationError: (message) => { error.value = message },
|
||||
onPendingWorkerError: (hasCurrentFile) => {
|
||||
error.value = hasCurrentFile
|
||||
? '新文件解析失败,当前文件未更改'
|
||||
: 'CRC 后台线程异常终止,请重新选择文件'
|
||||
},
|
||||
onActiveWorkerError: () => {
|
||||
firmware.value = undefined
|
||||
clearResults()
|
||||
error.value = 'CRC 后台线程异常终止,请重新选择文件'
|
||||
}
|
||||
worker = nextWorker
|
||||
return nextWorker
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
let pendingFileName = ''
|
||||
let pendingFileSize = 0
|
||||
let activeCalculationName = ''
|
||||
|
||||
function selectFile(selected?: File) {
|
||||
if (!selected) return
|
||||
destroyWorker()
|
||||
firmware.value = undefined
|
||||
clearResults()
|
||||
error.value = ''
|
||||
function selectFile(selected: File) {
|
||||
if (selected.size === 0) { error.value = '文件为空'; return }
|
||||
if (selected.size > MAX_FIRMWARE_FILE_SIZE) { error.value = '文件不能超过 64 MiB'; return }
|
||||
pendingFileName = selected.name
|
||||
pendingFileSize = selected.size
|
||||
const id = ++requestSequence
|
||||
activeParseRequest = id
|
||||
parsing.value = true
|
||||
const request: CrcWorkerRequest = { type: 'parse', id, file: selected }
|
||||
createWorker().postMessage(request)
|
||||
}
|
||||
|
||||
function onFileInput(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const selected = input.files?.[0]
|
||||
input.value = ''
|
||||
selectFile(selected)
|
||||
}
|
||||
|
||||
function onDrop(event: DragEvent) {
|
||||
dragging.value = false
|
||||
void selectFile(event.dataTransfer?.files[0])
|
||||
if (!/\.(?:hex|ihx|bin)$/i.test(selected.name)) { error.value = '请选择 .hex、.ihx 或 .bin 文件'; return }
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
parseFile(selected)
|
||||
}
|
||||
|
||||
function calculate() {
|
||||
@@ -189,16 +140,10 @@ function calculate() {
|
||||
if (!firmware.value) { error.value = '请先选择 BIN 或 Intel HEX 文件'; return }
|
||||
try {
|
||||
const parameters = getParameters()
|
||||
if (!worker) throw new Error('文件数据已失效,请重新选择文件')
|
||||
const id = ++requestSequence
|
||||
activeCalculationRequest = id
|
||||
activeCalculationName = activeParameterName.value
|
||||
calculating.value = true
|
||||
const request: CrcWorkerRequest = { type: 'calculate', id, parameters }
|
||||
worker.postMessage(request)
|
||||
const startStatus = calculateInWorker({ parameters })
|
||||
if (startStatus === 'unavailable') throw new Error('文件数据已失效,请重新选择文件')
|
||||
} catch (reason) {
|
||||
activeCalculationRequest = undefined
|
||||
calculating.value = false
|
||||
error.value = reason instanceof Error ? reason.message : 'CRC 计算失败'
|
||||
}
|
||||
}
|
||||
@@ -209,26 +154,32 @@ async function copyResult() {
|
||||
}
|
||||
|
||||
function clearFile() {
|
||||
destroyWorker()
|
||||
destroyWorkers()
|
||||
invalidateResults()
|
||||
firmware.value = undefined
|
||||
if (fileInput.value) fileInput.value.value = ''
|
||||
}
|
||||
|
||||
onBeforeUnmount(destroyWorker)
|
||||
onBeforeUnmount(destroyWorkers)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToolPage category="校验计算" title="CRC" description="解析 BIN 或 Intel HEX 有效数据,并使用预设或自定义参数计算 CRC。">
|
||||
<label v-if="!firmware" class="upload-zone compact-upload" :class="{ 'is-dragging': dragging }" @dragover.prevent="dragging = true" @dragleave.prevent="dragging = false" @drop.prevent="onDrop">
|
||||
<input id="crc-file" name="crc-file" ref="fileInput" type="file" accept=".hex,.ihx,.bin,application/octet-stream" :disabled="parsing" @change="onFileInput" />
|
||||
<span><FileUp :size="28" aria-hidden="true" /><strong>{{ parsing ? '正在解析文件…' : '选择或拖入 HEX / BIN 文件' }}</strong><span>Intel HEX 会校验记录并识别多个数据块</span></span>
|
||||
</label>
|
||||
<div v-else class="firmware-summary">
|
||||
<div><strong>{{ firmware.fileName }}</strong><span>{{ firmware.format }} · {{ firmware.totalBytes.toLocaleString() }} 有效字节 · {{ firmware.blockCount }} 个 block</span></div>
|
||||
<label class="action-button"><Upload :size="15" />更换文件<input id="crc-replacement-file" name="crc-replacement-file" class="sr-only-file" type="file" accept=".hex,.ihx,.bin,application/octet-stream" @change="onFileInput" /></label>
|
||||
<button class="action-button" type="button" @click="clearFile"><Trash2 :size="15" />清空</button>
|
||||
</div>
|
||||
<FileDropField
|
||||
input-id="crc-file"
|
||||
replacement-input-id="crc-replacement-file"
|
||||
accept=".hex,.ihx,.bin,application/octet-stream"
|
||||
:has-file="Boolean(firmware)"
|
||||
:busy="parsing"
|
||||
initial-label="选择或拖入 HEX / BIN 文件"
|
||||
initial-busy-label="正在解析文件…"
|
||||
hint="Intel HEX 会校验记录并识别多个数据块"
|
||||
compact
|
||||
@select="selectFile"
|
||||
@clear="clearFile"
|
||||
>
|
||||
<template #icon><FileUp :size="28" aria-hidden="true" /></template>
|
||||
<template v-if="firmware"><strong>{{ firmware.fileName }}</strong><span>{{ firmware.format }} · {{ firmware.totalBytes.toLocaleString() }} 有效字节 · {{ firmware.blockCount }} 个 block</span></template>
|
||||
</FileDropField>
|
||||
|
||||
<div class="crc-config">
|
||||
<label class="parameter-field preset-field"><span>参数预设(Preset)</span>
|
||||
|
||||
Reference in New Issue
Block a user