Files
jinshen_calculator/src/components/Modules/MultiLayerPaperTapeWidthAngleCalculate.vue

500 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>