500 lines
16 KiB
Vue
500 lines
16 KiB
Vue
<template>
|
||
<div class="calculator-container">
|
||
<v-row justify="center">
|
||
<!-- 参数输入区域 -->
|
||
<v-col
|
||
cols="12"
|
||
lg="5"
|
||
md="6"
|
||
>
|
||
<v-card
|
||
class="pa-6 parameter-card"
|
||
elevation="8"
|
||
rounded="xl"
|
||
>
|
||
<v-card-title class="text-h5 mb-6 d-flex align-center">
|
||
<v-icon
|
||
class="mr-3"
|
||
color="primary"
|
||
icon="mdi-tune"
|
||
size="large"
|
||
/>
|
||
{{ $t('parameters') }}
|
||
</v-card-title>
|
||
|
||
<v-row>
|
||
<v-col cols="12">
|
||
<param-input-field
|
||
v-model="paperCoreDiameter"
|
||
:disabled="recordList.length > 0"
|
||
:label="`${$t('paperCoreDiameter')} (${paperCoreDiameter.unit})`"
|
||
/>
|
||
</v-col>
|
||
<v-col cols="12">
|
||
<param-input-field
|
||
v-model="bottomPaperWidth"
|
||
:disabled="recordList.length > 0"
|
||
:label="`${$t('bottomPaperWidth')} (${bottomPaperWidth.unit})`"
|
||
/>
|
||
</v-col>
|
||
<v-col cols="12">
|
||
<param-input-field
|
||
v-model="paperGrammage"
|
||
:label="`${$t('paperGrammage')} (${paperGrammage.unit})`"
|
||
/>
|
||
</v-col>
|
||
<v-col cols="12">
|
||
<param-input-field
|
||
v-model="paperDensity"
|
||
:label="`${$t('paperDensity')} (${paperDensity.unit})`"
|
||
/>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<v-divider class="my-6" />
|
||
|
||
<v-row>
|
||
<v-col cols="12">
|
||
<div class="d-flex flex-column flex-sm-row justify-end align-center ga-3">
|
||
<!-- 次要功能按钮组 -->
|
||
<div class="d-flex ga-2 order-2 order-sm-1">
|
||
<v-btn
|
||
color="warning"
|
||
size="small"
|
||
variant="outlined"
|
||
@click="clearAll"
|
||
>
|
||
<v-icon class="mr-1">mdi-refresh</v-icon>
|
||
{{ $t('clear') }}
|
||
</v-btn>
|
||
<v-btn
|
||
color="success"
|
||
size="small"
|
||
variant="outlined"
|
||
@click="saveParams"
|
||
>
|
||
<v-icon class="mr-1">mdi-content-save</v-icon>
|
||
{{ $t('save') }}
|
||
</v-btn>
|
||
</div>
|
||
|
||
<!-- 主要功能按钮组 -->
|
||
<v-btn-group
|
||
class="order-1 order-sm-2"
|
||
color="primary"
|
||
divided
|
||
variant="elevated"
|
||
>
|
||
<v-btn
|
||
:size="$vuetify.display.xs ? 'default' : 'large'"
|
||
@click="removeLayer"
|
||
>
|
||
<v-icon class="mr-1">mdi-minus</v-icon>
|
||
{{ $t('remove') }}
|
||
</v-btn>
|
||
<v-btn
|
||
:size="$vuetify.display.xs ? 'default' : 'large'"
|
||
@click="addLayer"
|
||
>
|
||
<v-icon class="mr-1">mdi-plus</v-icon>
|
||
{{ $t('add') }}
|
||
</v-btn>
|
||
</v-btn-group>
|
||
</div>
|
||
</v-col>
|
||
</v-row>
|
||
</v-card>
|
||
</v-col>
|
||
|
||
<!-- 结果显示区域 -->
|
||
<v-col
|
||
cols="12"
|
||
lg="5"
|
||
md="6"
|
||
>
|
||
<v-card
|
||
class="pa-6 result-card"
|
||
elevation="8"
|
||
rounded="xl"
|
||
>
|
||
<v-card-title class="text-h5 mb-6 d-flex align-center">
|
||
<v-icon
|
||
class="mr-3"
|
||
color="primary"
|
||
icon="mdi-calculator"
|
||
size="large"
|
||
/>
|
||
{{ $t('results') }}
|
||
</v-card-title>
|
||
|
||
<v-table
|
||
fixed-header
|
||
theme="primary"
|
||
>
|
||
<thead>
|
||
<tr>
|
||
<th>{{ $t('layer') }}</th>
|
||
<th>{{ $t('paperGrammage') }}</th>
|
||
<th>{{ $t('cumulativeThickness') }}</th>
|
||
<th>{{ $t('angle') }}</th>
|
||
<th>{{ $t('paperWidth') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<transition-group name="fade" tag="tbody">
|
||
<tr v-for="(record, index) in recordList" :key="index" class="table-row-item">
|
||
<td>{{ record.layer }}</td>
|
||
<td>{{ record.grammage.value.toFixed(2) }} {{ record.grammage.unit }}</td>
|
||
<td>{{ record.cumulativeThickness.value.toFixed(2) }} {{ record.cumulativeThickness.unit }}</td>
|
||
<td>{{ record.angle.value.toFixed(2) }} {{ record.angle.unit }}</td>
|
||
<td>{{ record.paperWidth.value.toFixed(2) }} {{ record.paperWidth.unit }}</td>
|
||
</tr>
|
||
</transition-group>
|
||
</v-table>
|
||
</v-card>
|
||
|
||
</v-col>
|
||
</v-row>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import * as EXCEL from 'exceljs'
|
||
import { computed, ref } from 'vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { createParam, type Param } from '@/types/param'
|
||
import { degreesToRadians, radiansToDegrees } from '@/utils/angle'
|
||
|
||
const { t } = useI18n()
|
||
|
||
interface LayerRecord {
|
||
layer: number
|
||
grammage: Param
|
||
cumulativeThickness: Param
|
||
angle: Param
|
||
paperWidth: Param
|
||
}
|
||
|
||
const paperCoreDiameter = ref<Param>(createParam(76.2, 'mm'))
|
||
const bottomPaperWidth = ref<Param>(createParam(100, 'mm'))
|
||
const paperGrammage = ref<Param>(createParam(420, 'g/m²'))
|
||
const paperDensity = ref<Param>(createParam(0.76, 'g/cm³'))
|
||
|
||
const recordList = ref<LayerRecord[]>([])
|
||
|
||
const result = computed(() => {
|
||
const paperRollWallThickness = paperGrammage.value.value / paperDensity.value.value / 1000
|
||
// eslint-disable-next-line unicorn/prefer-at
|
||
const paperTotalThickness = recordList.value.length === 0 ? 0 : recordList.value[recordList.value.length - 1].cumulativeThickness.value
|
||
const paperRollExternalDiameter = paperCoreDiameter.value.value + 2 * paperTotalThickness
|
||
const paperAngle = 90 - radiansToDegrees(Math.acos(bottomPaperWidth.value.value / (paperCoreDiameter.value.value * Math.PI)))
|
||
const leadingLength = bottomPaperWidth.value.value / Math.sin(degreesToRadians(90 - paperAngle))
|
||
const beltAngle = 90 - radiansToDegrees(Math.atan(paperRollExternalDiameter * Math.PI / leadingLength))
|
||
const paperWidth = leadingLength * Math.sin(degreesToRadians(radiansToDegrees(Math.atan(paperRollExternalDiameter * Math.PI / leadingLength))))
|
||
|
||
return {
|
||
layer: recordList.value.length + 1,
|
||
grammage: paperGrammage.value,
|
||
cumulativeThickness: createParam(paperTotalThickness + paperRollWallThickness, 'mm'),
|
||
angle: createParam(beltAngle, '°'),
|
||
paperWidth: createParam(paperWidth, 'mm'),
|
||
}
|
||
})
|
||
|
||
// 按钮功能方法
|
||
const clearAll = () => {
|
||
recordList.value = []
|
||
}
|
||
|
||
const saveParams = async () => {
|
||
if (recordList.value.length === 0) {
|
||
console.warn('No records to save.')
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 创建工作簿和工作表
|
||
const workbook = new EXCEL.Workbook()
|
||
const worksheet = workbook.addWorksheet(t('multiLayerExcelOutputFile'))
|
||
|
||
// 创建样式
|
||
// headerStyle: 表头样式,根据Excel内建样式Calculation设置
|
||
const headerStyle: EXCEL.Style = {
|
||
fill: {
|
||
type: 'pattern',
|
||
pattern: 'solid',
|
||
fgColor: { argb: 'FFF2F2F2' },
|
||
},
|
||
font: {
|
||
name: 'Arial',
|
||
size: 11,
|
||
bold: true,
|
||
color: { argb: 'FFFA7D00' },
|
||
},
|
||
alignment: {
|
||
horizontal: 'center',
|
||
vertical: 'middle',
|
||
},
|
||
numFmt: 'General',
|
||
protection: {},
|
||
border: {
|
||
top: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
bottom: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
left: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
right: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
},
|
||
}
|
||
// parameterStyle: 参数样式,使用默认样式
|
||
const parameterStyle: EXCEL.Style = {
|
||
font: {
|
||
name: 'Arial',
|
||
size: 12,
|
||
color: { argb: 'FF000000' },
|
||
},
|
||
alignment: {
|
||
horizontal: 'center',
|
||
vertical: 'middle',
|
||
},
|
||
numFmt: 'General',
|
||
protection: {},
|
||
border: {},
|
||
fill: {
|
||
type: 'pattern',
|
||
pattern: 'none',
|
||
},
|
||
}
|
||
|
||
// valueStyle: 数值样式,使用默认样式,保留2位小数
|
||
const valueStyle: EXCEL.Style = {
|
||
font: {
|
||
name: 'Arial',
|
||
size: 12,
|
||
color: { argb: 'FF000000' },
|
||
},
|
||
alignment: {
|
||
horizontal: 'center',
|
||
vertical: 'middle',
|
||
},
|
||
numFmt: '0.00',
|
||
protection: {},
|
||
border: {},
|
||
fill: {
|
||
type: 'pattern',
|
||
pattern: 'none',
|
||
},
|
||
}
|
||
// conditionStyle: 条件样式,使用默认样式
|
||
const conditionStyle: EXCEL.Style = {
|
||
font: {
|
||
name: 'Arial',
|
||
size: 16,
|
||
bold: true,
|
||
color: { argb: 'FF444444' },
|
||
},
|
||
alignment: {
|
||
horizontal: 'center',
|
||
vertical: 'middle',
|
||
},
|
||
numFmt: 'General',
|
||
protection: {},
|
||
border: {
|
||
top: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
bottom: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
left: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
right: { style: 'thin', color: { argb: 'FF7F7F7F' } },
|
||
},
|
||
fill: {
|
||
type: 'pattern',
|
||
pattern: 'none',
|
||
},
|
||
}
|
||
|
||
// 添加Parameters表头
|
||
worksheet.getCell('A1').value = t('parameters')
|
||
worksheet.getCell('A1').style = headerStyle
|
||
worksheet.mergeCells('A1:D1')
|
||
|
||
worksheet.getCell('A2').value = `${t('paperCoreDiameter')} (${paperCoreDiameter.value.unit})`
|
||
worksheet.getCell('A2').style = parameterStyle
|
||
worksheet.mergeCells('A2:B2')
|
||
|
||
worksheet.getCell('C2').value = `${t('bottomPaperWidth')} (${bottomPaperWidth.value.unit})`
|
||
worksheet.getCell('C2').style = parameterStyle
|
||
worksheet.mergeCells('C2:D2')
|
||
|
||
// 添加Parameters数据
|
||
worksheet.getCell('A3').value = paperCoreDiameter.value.value
|
||
worksheet.getCell('A3').style = valueStyle
|
||
worksheet.mergeCells('A3:B3')
|
||
|
||
worksheet.getCell('C3').value = bottomPaperWidth.value.value
|
||
worksheet.getCell('C3').style = valueStyle
|
||
worksheet.mergeCells('C3:D3')
|
||
|
||
// 添加Results表头
|
||
worksheet.getCell('A7').value = t('results')
|
||
worksheet.getCell('A7').style = headerStyle
|
||
worksheet.mergeCells('A7:E7')
|
||
|
||
// 添加Results表头列名
|
||
worksheet.getCell('A8').value = t('layer')
|
||
worksheet.getCell('A8').style = parameterStyle
|
||
worksheet.getCell('B8').value = t('paperGrammage') + ` (${recordList.value[0].grammage.unit})`
|
||
worksheet.getCell('B8').style = parameterStyle
|
||
worksheet.getCell('C8').value = t('cumulativeThickness') + ` (${recordList.value[0].cumulativeThickness.unit})`
|
||
worksheet.getCell('C8').style = parameterStyle
|
||
worksheet.getCell('D8').value = t('angle') + ` (${recordList.value[0].angle.unit})`
|
||
worksheet.getCell('D8').style = parameterStyle
|
||
worksheet.getCell('E8').value = t('paperWidth') + ` (${recordList.value[0].paperWidth.unit})`
|
||
worksheet.getCell('E8').style = parameterStyle
|
||
|
||
// 添加Results数据
|
||
for (const [index, record] of recordList.value.entries()) {
|
||
worksheet.getCell(`A${index + 9}`).value = record.layer
|
||
worksheet.getCell(`A${index + 9}`).style = parameterStyle
|
||
worksheet.getCell(`B${index + 9}`).value = record.grammage.value
|
||
worksheet.getCell(`B${index + 9}`).style = valueStyle
|
||
worksheet.getCell(`C${index + 9}`).value = record.cumulativeThickness.value
|
||
worksheet.getCell(`C${index + 9}`).style = valueStyle
|
||
worksheet.getCell(`D${index + 9}`).value = record.angle.value
|
||
worksheet.getCell(`D${index + 9}`).style = valueStyle
|
||
worksheet.getCell(`E${index + 9}`).value = record.paperWidth.value
|
||
worksheet.getCell(`E${index + 9}`).style = valueStyle
|
||
}
|
||
|
||
// 添加Reference表头
|
||
worksheet.getCell('F1').value = t('reference')
|
||
worksheet.getCell('F1').style = headerStyle
|
||
worksheet.mergeCells('F1:H1')
|
||
worksheet.getCell('F2').value = t('angle')
|
||
worksheet.getCell('F2').style = parameterStyle
|
||
worksheet.mergeCells('F2:F3')
|
||
worksheet.getCell('G2').value = t('minimum')
|
||
worksheet.getCell('G2').style = parameterStyle
|
||
worksheet.getCell('H2').value = t('maximum')
|
||
worksheet.getCell('H2').style = parameterStyle
|
||
|
||
// 添加Reference数据
|
||
worksheet.getCell('G3').value = 10
|
||
worksheet.getCell('G3').style = parameterStyle
|
||
worksheet.getCell('H3').value = 45
|
||
worksheet.getCell('H3').style = parameterStyle
|
||
worksheet.getCell('F4').value = t('calculatedValue')
|
||
worksheet.getCell('F4').style = parameterStyle
|
||
// 所有记录中最小角度
|
||
worksheet.getCell('G4').value = {
|
||
formula: `MIN(D9: D${recordList.value.length + 8})`,
|
||
}
|
||
worksheet.getCell('G4').style = valueStyle
|
||
// 所有记录中最大角度
|
||
worksheet.getCell('H4').value = {
|
||
formula: `MAX(D9: D${recordList.value.length + 8})`,
|
||
}
|
||
worksheet.getCell('H4').style = valueStyle
|
||
|
||
// 计算结果: 最小角度>10 且 最大角度<45
|
||
worksheet.getCell('F5').value = {
|
||
formula: `IF(AND(G4>10, H4<45), "${t('fit')}", "${t('notFit')}")`,
|
||
}
|
||
worksheet.getCell('F5').style = conditionStyle
|
||
worksheet.addConditionalFormatting({
|
||
ref: 'F5',
|
||
rules: [
|
||
{
|
||
type: 'expression',
|
||
formulae: ['OR(G4<=10, H4>=45)'],
|
||
style: {
|
||
fill: { type: 'pattern', pattern: 'solid', bgColor: { argb: 'FFFF7F7F' } }, // 红色背景
|
||
},
|
||
priority: 1,
|
||
},
|
||
{
|
||
type: 'expression',
|
||
formulae: ['AND(G4>10, H4<45)'],
|
||
style: {
|
||
fill: { type: 'pattern', pattern: 'solid', bgColor: { argb: 'FFA6E65C' } }, // 绿色背景
|
||
},
|
||
priority: 1,
|
||
},
|
||
],
|
||
})
|
||
worksheet.mergeCells('F5:H6')
|
||
|
||
// 自动调整列宽
|
||
for (const [index, column] of worksheet.columns.entries()) {
|
||
let maxLength = 0
|
||
|
||
const columnLetter = String.fromCodePoint(65 + index)
|
||
|
||
if (column && typeof column.eachCell === 'function') {
|
||
column.eachCell({ includeEmpty: true }, cell => {
|
||
const cellValue = cell.value ? String(cell.value) : ''
|
||
if (cellValue.length > maxLength) {
|
||
maxLength = cellValue.length
|
||
}
|
||
})
|
||
|
||
// 设置列宽
|
||
column.width = maxLength + 4 // 增加一些额外空间
|
||
}
|
||
}
|
||
|
||
// 设置文件名
|
||
const timeStamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 15)
|
||
const filename = `${t('multiLayerExcelOutputFile')}_${timeStamp}.xlsx`
|
||
|
||
// 导出为文件
|
||
const buffer = await workbook.xlsx.writeBuffer()
|
||
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||
|
||
const url = window.URL.createObjectURL(blob)
|
||
const link = document.createElement('a')
|
||
link.href = url
|
||
link.download = filename
|
||
document.body.append(link)
|
||
link.click()
|
||
link.remove()
|
||
window.URL.revokeObjectURL(url)
|
||
|
||
console.log('Saved as Excel file:', filename)
|
||
} catch (error) {
|
||
console.error('Error saving parameters:', error)
|
||
return
|
||
}
|
||
}
|
||
|
||
const addLayer = () => {
|
||
// 添加层的逻辑
|
||
recordList.value.push({
|
||
layer: result.value.layer,
|
||
grammage: result.value.grammage,
|
||
cumulativeThickness: result.value.cumulativeThickness,
|
||
angle: result.value.angle,
|
||
paperWidth: result.value.paperWidth,
|
||
})
|
||
}
|
||
|
||
const removeLayer = () => {
|
||
// 移除层的逻辑
|
||
recordList.value.pop()
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.table-row-item {
|
||
/* ensure rows still render as table rows */
|
||
display: table-row;
|
||
}
|
||
|
||
/* enter/leave transitions */
|
||
.fade-enter-active,
|
||
.fade-leave-active {
|
||
transition: all 300ms ease;
|
||
}
|
||
.fade-enter-from,
|
||
.fade-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(-10px);
|
||
}
|
||
|
||
</style>
|