flaunt-web/src/views/other/gongzidan.vue

509 lines
17 KiB
Vue

<template>
<div class="flex page-conatiner">
<div class="left-box">
<div class="menu-box">
<div class="flex align-center menu-name">
<img class="icon" src="@/public/other/gongzidan/icon.png" alt="">
<span class="title">工资单</span>
</div>
<div class="menu-item active">纸质版</div>
<div class="menu-item">电子版</div>
</div>
<img class="stay-tuned-img" src="@/public/common/stay-tuned.png" alt="">
</div>
<div class="main-box">
<img class="main-bg" src="@/public/other/gongzidan/gongzidan.png" alt="">
<span class="item-info" style="left: 156px;bottom: 210px;">{{ formData.job }}</span>
<span class="item-info" style="left: 202px;bottom: 210px;">{{ formData.name }}</span>
<span class="item-info" style="left: 248px;bottom: 210px;">{{ formData.basicSalary }}</span>
<span class="item-info" style="left: 288px;bottom: 210px;">{{ formData.performanceSalary }}</span>
<span class="item-info" style="left: 326px;bottom: 210px;">{{ formData.fullAttendanceBonus }}</span>
<span class="item-info" style="left: 366px;bottom: 210px;">{{ formData.otherBonus }}</span>
<span class="item-info" style="left: 408px;bottom: 210px;">{{ formData.totalSalary }}</span>
<span class="item-info" style="left: 452px;bottom: 210px;">{{ formData.socialInsurance }}</span>
<span class="item-info" style="left: 492px;bottom: 210px;">{{ formData.taxDeduction }}</span>
<span class="item-info" style="left: 530px;bottom: 210px;">{{ formData.attendanceDeduction }}</span>
<span class="item-info" style="left: 568px;bottom: 210px;">{{ formData.otherDeduction }}</span>
<span class="item-info" style="left: 607px;bottom: 210px;">{{ formData.totalDeduction }}</span>
<span class="item-info" style="left: 646px;bottom: 210px;">{{ formData.netSalary }}</span>
<span class="item-info time" style="left: 434px; bottom: 298px;">{{ formData.year }}</span>
<span class="item-info time" style="left: 488px; bottom: 298px;">{{ formData.month }}</span>
<div class="watermark-overlay"></div>
</div>
<!-- <div class="right-box"></div> -->
<div class="right-box content-box">
<div class="right-title">
<div class="touzi-icon"></div>
<span>工资单编辑</span>
<div class="touzi-icon"></div>
<!-- <img class="touzi-icon" src="@/public/common/touzi.png" alt=""> -->
</div>
<div class="from-box">
<div class="form-item">
<span class="label">时间</span>
<el-select class="custom-select" style="margin-right: 4px;width: 90px;" v-model="formData.year"
placeholder="年">
<el-option v-for="year in yearOptions" :key="year" :label="year + '年'" :value="year" />
</el-select>
<el-select class="custom-select" style="width: 70px;" v-model="formData.month" placeholder="月">
<el-option v-for="month in monthOptions" :key="month" :label="month + '月'" :value="month" />
</el-select>
</div>
<div class="form-item">
<span class="label">岗位</span>
<el-input class="custom-input" v-model="formData.job" placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">姓名</span>
<el-input class="custom-input" v-model="formData.name" placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">基本工资</span>
<el-input class="custom-input" type="number" v-model="formData.basicSalary" placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">绩效工资</span>
<el-input class="custom-input" type="number" v-model="formData.performanceSalary"
placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">全勤奖金</span>
<el-input class="custom-input" type="number" v-model="formData.fullAttendanceBonus"
placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">其它奖金</span>
<el-input class="custom-input" type="number" v-model="formData.otherBonus" placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">社保缴纳</span>
<el-input class="custom-input" type="number" v-model="formData.socialInsurance" placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">个税缴纳</span>
<el-input class="custom-input" type="number" v-model="formData.taxDeduction" placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">考勤扣款</span>
<el-input class="custom-input" type="number" v-model="formData.attendanceDeduction"
placeholder="请输入" />
</div>
<div class="form-item">
<span class="label">其他扣款</span>
<el-input class="custom-input" type="number" v-model="formData.otherDeduction" placeholder="请输入" />
</div>
</div>
<div class="action-buttons">
<button class="btn btn-outline" @click="saveDraft">保存草稿</button>
<button class="btn btn-primary" @click="downloadPaper">下 载</button>
</div>
</div>
</div>
<WarningFooter />
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import WarningFooter from '@/components/common/WarningFooter.vue'
import { all } from 'axios'
import { dayOrDaysToDate, ElMessage } from 'element-plus'
import html2canvas from 'html2canvas'
const currentYear = new Date().getFullYear().toString()
const currentMonth = (new Date().getMonth() + 1).toString()
const yearOptions = computed(() => {
const current = new Date().getFullYear()
const years = []
for (let i = 0; i < 15; i++) {
years.push((current - i).toString())
}
return years
})
const monthOptions = Array.from({ length: 12 }, (_, i) => (i + 1).toString())
const formData = ref({
year: currentYear,
month: currentMonth,
job: '销售',
name: '肖战',
basicSalary: "10000",
performanceSalary: "5000",
fullAttendanceBonus: "1000",
otherBonus: "1000",
socialInsurance: "600",
taxDeduction: "1000",
attendanceDeduction: "500",
otherDeduction: "0",
totalSalary: "13900",
totalDeduction: "1500",
date: `${currentYear}-${currentMonth.padStart(2, '0')}-21`,
netSalary: "12400",
})
watch(
() => [
formData.value.basicSalary,
formData.value.performanceSalary,
formData.value.fullAttendanceBonus,
formData.value.otherBonus,
formData.value.socialInsurance,
formData.value.taxDeduction,
formData.value.attendanceDeduction,
formData.value.otherDeduction
],
([basicVal, perfVal, attBVal, othBVal, socVal, taxVal, attDVal, othDVal]) => {
const basic = parseFloat(basicVal) || 0
const perf = parseFloat(perfVal) || 0
const attendanceBonus = parseFloat(attBVal) || 0
const otherBonus = parseFloat(othBVal) || 0
const social = parseFloat(socVal) || 0
const tax = parseFloat(taxVal) || 0
const attendDeduct = parseFloat(attDVal) || 0
const otherDeduct = parseFloat(othDVal) || 0
const totalSalary = basic + perf + attendanceBonus + otherBonus
const totalDeduction = social + tax + attendDeduct + otherDeduct
const netSalary = totalSalary - totalDeduction
formData.value.totalSalary = totalSalary.toString()
formData.value.totalDeduction = totalDeduction.toString()
formData.value.netSalary = netSalary.toString()
},
{ immediate: true }
)
const randomGenerate = () => {
// TODO: 实现随机生成逻辑
}
const saveDraft = () => {
ElMessage.success('草稿已保存')
}
const downloadPaper = async () => {
const el = document.querySelector('.main-box')
if (!el) {
ElMessage.error('预览未就绪,请稍后重试')
return
}
try {
const canvas = await html2canvas(el, {
scale: 2,
useCORS: true,
onclone: (clonedDoc) => {
const watermark = clonedDoc.querySelector('.watermark-overlay')
if (watermark) {
watermark.remove()
}
}
})
canvas.toBlob((blob) => {
if (!blob) {
ElMessage.error('图片生成失败')
return
}
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.download = `gongzidan-${Date.now()}.png`
link.href = url
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
ElMessage.success('下载成功')
}, 'image/png')
} catch (e) {
ElMessage.error('下载失败:' + e.message)
}
}
</script>
<style scoped lang="scss">
.flex {
display: flex;
}
.flex-col {
display: flex;
flex-direction: column;
}
.align-center {
align-items: center;
}
.flex-1 {
flex: 1;
}
.shrink {
flex-shrink: 0;
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
.page-conatiner {
padding: 47px;
padding-left: 66px;
margin: 17px auto 0;
width: fit-content;
justify-content: center;
.menu-box {
width: 200px;
padding: 16px;
background-color: #FFFFFF;
border-radius: 18px;
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
.menu-name {
background: #ECECFF;
border-radius: 12px 12px 12px 12px;
padding: 12px;
}
.icon {
width: 32px;
height: 32px;
margin-right: 6px;
}
.title {
width: 90px;
height: 20px;
font-family: Source Han Sans, Source Han Sans;
font-weight: 500;
font-size: 18px;
line-height: 20px;
text-align: left;
font-style: normal;
text-transform: none;
background: linear-gradient(88.42188424578676deg, #6825DB 0%, #5066D4 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.menu-item {
margin-top: 16px;
font-size: 18px;
color: #1A1A1A;
height: 37px;
line-height: 37px;
padding-left: 12px;
border-radius: 8px;
&:hover {
background-color: #F7F8FF;
}
&.active {
color: #FFFFFF;
background: linear-gradient(215deg, #A679F4 7.14%, #778CF5 100%);
border-radius: 8px 8px 8px 8px;
border-image: linear-gradient(215deg, rgba(166.00000530481339, 121.00000038743019, 244.0000006556511, 1), rgba(119.00000050663948, 140.00000685453415, 245.00000059604645, 1)) 1 1;
}
}
}
.stay-tuned-img {
width: 200px;
height: 145px;
margin-top: 22px;
background-color: #FFFFFF;
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
border-radius: 18px 18px 18px 18px;
}
.main-box {
position: relative;
margin: 0 54px;
.main-bg {
width: 800px;
height: 600px;
object-fit: contain;
}
.watermark-overlay {
position: absolute;
top: 0;
left: 0;
width: 800px;
height: 600px;
background-image: url('@/public/common/watermaker.png');
background-size: 125px 125px;
background-repeat: repeat;
pointer-events: none;
z-index: 999;
}
.item-info {
position: absolute;
font-size: 11px;
color: #454649;
opacity: 0.9;
transform: translateX(-50%);
text-shadow: 0 0 1px #454649EE;
&.time {
font-size: 16px;
font-weight: 500;
color: #46474C;
}
}
}
.right-box {
width: 300px;
height: 600px;
&.content-box {
overflow-y: hidden;
background-color: #FFFFFF;
border-radius: 18px;
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
padding: 20px 16px;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.right-title {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
line-height: 18px;
font-weight: 600;
color: #1A1A1A;
margin-bottom: 24px;
.touzi-icon {
width: 32px;
height: 32px;
}
}
.from-box {
flex: 1;
height: 100px;
overflow-y: auto;
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.label {
font-size: 14px;
color: #333333;
width: 90px;
flex-shrink: 0;
}
.switch-wrapper {
flex: 1;
display: flex;
justify-content: flex-end;
}
.random-dice {
width: 32px;
height: 32px;
border: 1px dashed #BDBDBD;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 20px;
user-select: none;
transition: all 0.2s;
&:hover {
border-color: #808AFC;
background-color: #F7F8FF;
}
}
}
:deep(.custom-input) {
flex: 1;
.el-input__wrapper {
background-color: #F5F6F9;
box-shadow: none;
border-radius: 8px;
padding: 0 12px;
height: 36px;
transition: all 0.3s;
&.is-focus {
box-shadow: 0 0 0 1px #808AFC inset;
background-color: #FFFFFF;
}
}
.el-input__inner {
color: #333333;
font-size: 14px;
&::placeholder {
color: #BDBDBD;
}
}
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 24px;
.btn {
width: 100%;
height: 36px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
letter-spacing: 2px;
}
.btn-outline {
background-color: transparent;
border: 1px dashed #9F82F7;
color: #9F82F7;
&:hover {
background-color: #F7F8FF;
}
}
.btn-primary {
background: linear-gradient(215deg, #A679F4 7.14%, #778CF5 100%);
border: none;
color: #FFFFFF;
box-shadow: 0px 2px 6px rgba(166, 121, 244, 0.3);
&:hover {
opacity: 0.9;
}
}
}
}
}
</style>