一、项目demo
效果页面
静态页面下载:
链接:https://pan.quark.cn/s/2db47dbca87cvue2+Fastapi:
链接:https://pan.quark.cn/s/1b817baf5fc5
链接里面有说明文档
功能实现:
1.会计科目实现下拉关联选择和过滤关联选择;
2.借方金额和贷方金额实现输入后映射金额的效果;
3.头部日期控件选择后,完成对年月的数值显示;
4.合计功能大写,同时将借、贷金额数值汇总
二、页面效果
三、页面demo
可以直接复制粘贴(完全静态)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>记账凭证管理</title> <!-- 引入 Element-UI 样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <style> body { font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif; padding: 20px; } .container { width: 90%; max-width: 1200px; margin: 0 auto; } .search-group { display: flex; gap: 10px; margin-bottom: 20px; } .el-table { margin-top: 20px; } .el-form-item { margin-bottom: 15px; } .operation-buttons { margin: 20px 0; } .voucher-item { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } .info-row { display: flex; align-items: center; margin-bottom: 10px; } .info-row span { margin-right: 20px; } .voucher-table { width: 100%; border-collapse: collapse; } .voucher-table th, .voucher-table td { border: 1px solid #ccc; padding: 5px; text-align: center; } .total-row { font-weight: bold; } .footer-info { margin-top: 10px; } .footer-info span { margin-right: 20px; } .input-overlay { position: absolute; display: none; background-color: white; border: 1px solid #ccc; z-index: 100; } </style> </head> <body> <div id="app" class="container"> <!-- 操作按钮 --> <div class="operation-buttons"> <el-button type="primary" @click="openDialog('add')">录入凭证</el-button> <el-button type="danger" @click="batchDelete" :disabled="!selectedRows.length">批量删除</el-button> </div> <!-- 表格 --> <el-table :data="filteredVouchers" stripe @selection-change="handleSelectionChange" > <el-table-column type="selection" width="55"></el-table-column> <el-table-column prop="date" label="日期"></el-table-column> <el-table-column prop="voucherNumber" label="凭证字号"></el-table-column> <el-table-column label="摘要" > <template slot-scope="scope"> <div class="summary-container"> <div v-for="(item, index) in scope.row.items" :key="index"> {{ item.summary }} </div> </div> </template> </el-table-column> <el-table-column label="科目"> <template slot-scope="scope"> <div class="summary-container"> <div v-for="(item, index) in scope.row.items" :key="index"> {{ item.account }} </div> </div> </template> </el-table-column> <el-table-column label="借方金额"> <template slot-scope="scope"> {{ scope.row.items.reduce((sum, item) => { const units = [100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1, 0.1, 0.01]; return sum + item.debitDigits.reduce((acc, digit, index) => acc + digit * units[index], 0); }, 0) }} </template> </el-table-column> <el-table-column label="贷方金额"> <template slot-scope="scope"> {{ scope.row.items.reduce((sum, item) => { const units = [100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1, 0.1, 0.01]; return sum + item.creditDigits.reduce((acc, digit, index) => acc + digit * units[index], 0); }, 0) }} </template> </el-table-column> <el-table-column prop="maker" label="制单人"></el-table-column> <el-table-column prop="reviewer" label="审核人"></el-table-column> <el-table-column label="操作" width="200"> <template slot-scope="scope"> <el-button type="primary" size="mini" @click="openDialog('edit', scope.row)" >编辑</el-button> <el-button type="danger" size="mini" @click="deleteVoucher(scope.$index)" >删除</el-button> </template> </el-table-column> </el-table> <!-- 表单对话框 --> <el-dialog title="凭证编辑" :visible.sync="dialogVisible" width="60%" > <!-- 单个凭证 --> <div class="voucher-item" v-for="(voucher, index) in [currentVoucher]" :key="index"> <div class="info-row"> <span>凭证字:记</span> <span>凭证号:{{ voucher.voucherNumber }}</span> <el-date-picker v-model="voucher.date" type="date" placeholder="选择日期"></el-date-picker> </div> <table class="voucher-table"> <thead> <tr> <th style="width: 25%">摘要</th> <th style="width: 25%">会计科目</th> <th style="width: 25%" colspan="12">借方金额</th> <th style="width: 25%" colspan="12">贷方金额</th> </tr> <tr> <th></th> <th></th> <th></th> <th>亿</th> <th>千</th> <th>百</th> <th>十</th> <th>万</th> <th>千</th> <th>百</th> <th>十</th> <th>元</th> <th>角</th> <th>分</th> <th></th> <th>亿</th> <th>千</th> <th>百</th> <th>十</th> <th>万</th> <th>千</th> <th>百</th> <th>十</th> <th>元</th> <th>角</th> <th>分</th> </tr> </thead> <tbody> <tr v-for="(item, itemIndex) in voucher.items" :key="itemIndex"> <td><input v-model="item.summary" style="width: 100%; height: 100%; border: none; outline:none;"></td> <td><input v-model="item.account" style="width: 100%; height: 100%; border: none; outline:none;"></td> <!-- 分割格子用于美观 --> <td style="background-color: #F5F7FA;"></td> <!-- 对应单位:亿 千 百 十 万 千 百 十 元 角 分 --> <td @click="showInput(voucher, item, 'debit')" style="background-color: #f8e4d7;">{{ item.debitDigits[0] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #f8e4d7;">{{ item.debitDigits[1] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #f8e4d7;">{{ item.debitDigits[2] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #f7f8cb;">{{ item.debitDigits[3] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #f7f8cb;">{{ item.debitDigits[4] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #f7f8cb;">{{ item.debitDigits[5] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #e0ebf7;">{{ item.debitDigits[6] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #e0ebf7;">{{ item.debitDigits[7] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #e0ebf7;">{{ item.debitDigits[8] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #cff6ee;">{{ item.debitDigits[9] }}</td> <td @click="showInput(voucher, item, 'debit')" style="background-color: #cff6ee;">{{ item.debitDigits[10] }}</td> <!-- 分割格子用于美观 --> <td style="background-color: #F5F7FA;"></td> <!-- 对应单位:亿 千 百 十 万 千 百 十 元 角 分 --> <td @click="showInput(voucher, item, 'credit')" style="background-color: #f8e4d7;">{{ item.creditDigits[0] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #f8e4d7;">{{ item.creditDigits[1] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #f8e4d7;">{{ item.creditDigits[2] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #f7f8cb;">{{ item.creditDigits[3] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #f7f8cb;">{{ item.creditDigits[4] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #f7f8cb;">{{ item.creditDigits[5] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #e0ebf7;">{{ item.creditDigits[6] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #e0ebf7;">{{ item.creditDigits[7] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #e0ebf7;">{{ item.creditDigits[8] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #cff6ee;">{{ item.creditDigits[9] }}</td> <td @click="showInput(voucher, item, 'credit')" style="background-color: #cff6ee;">{{ item.creditDigits[10] }}</td> </tr> <tr class="total-row"> <td colspan="2">合计</td> <td colspan="12"> <div class="amount-cell"> {{ formatAmount(voucher.totalDebit) }} </div> </td> <td colspan="12"> <div class="amount-cell"> {{ formatAmount(voucher.totalCredit) }} </div> </td> </tr> </tbody> </table> <div class="footer-info"> <span>制单时间:{{ voucher.makeTime }}</span> <span>制单人:{{ voucher.maker }}</span> <span>备注:{{ voucher.remark }}</span> </div> </div> <div class="input-overlay" ref="inputOverlay" @click.away="hideInput"> <!-- // 设置输入框的宽度和高度 --> <input style="border: none; outline: none;" v-model="inputValue" @keydown.enter="submitInput"> </div> <div slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="saveVoucher">保存</el-button> </div> </el-dialog> </div> <!-- 引入 Vue 和 Element-UI 脚本 --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <script src="https://unpkg.com/element-ui/lib/index.js"></script> <script> new Vue({ el: '#app', data() { return { searchDate: null, vouchers: [ { id: 1, date: '2025-03-15', voucherNumber: '记-001', maker: '张三', reviewer: '李四', items: [ { summary: '业务费', account: '管理费_招待费', debitDigits: [0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, { summary: '业务费', account: '管理费_水电费', debitDigits: [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, ], totalDebit: 0, totalCredit: 0, makeTime: '2025-3-16', remark: '无' }, { id: 2, date: '2025-03-16', voucherNumber: '记-002', maker: '王五', reviewer: '王麻子', items: [ { summary: '业务费', account: '管理费_招待费', debitDigits: [0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, { summary: '业务费', account: '管理费_水电费', debitDigits: [0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, ], totalDebit: 0, totalCredit: 0, makeTime: '2025-3-16', remark: '无' } ], dialogVisible: false, currentVoucher: {}, selectedRows: [], inputValue: '', currentItem: null, currentType: null }; }, computed: { filteredVouchers() { if (this.searchDate) { return this.vouchers.filter(v => new Date(v.date).toDateString() === new Date(this.searchDate).toDateString()); } return this.vouchers; } }, methods: { openDialog(type, row = null) { this.currentVoucher = type === 'add' ? { id: Date.now(), date: new Date().toISOString().split('T')[0], voucherNumber: '', maker: '', reviewer: '', items: [ { summary: '', account: '', debitDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, { summary: '', account: '', debitDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, { summary: '', account: '', debitDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, { summary: '', account: '', debitDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], creditDigits: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] } ], totalDebit: 0, totalCredit: 0, makeTime: new Date().toISOString().split('T')[0], remark: '无' } : { ...row }; this.dialogVisible = true; }, saveVoucher() { if (this.currentVoucher.id) { // 更新操作 const index = this.vouchers.findIndex(v => v.id === this.currentVoucher.id); this.vouchers.splice(index, 1, this.currentVoucher); } else { // 新增操作 this.currentVoucher.id = Date.now(); this.vouchers.push(this.currentVoucher); } this.dialogVisible = false; this.$message.success('操作成功'); }, deleteVoucher(index) { this.$confirm('确认删除该凭证吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.vouchers.splice(index, 1); this.$message.success('删除成功'); }); }, batchDelete() { if (this.selectedRows.length === 0) return; this.$confirm(`确认删除${this.selectedRows.length}条记录吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.vouchers = this.vouchers.filter(v => !this.selectedRows.includes(v)); this.$message.success(`成功删除${this.selectedRows.length}条记录`); this.selectedRows = []; }); }, searchVouchers() { // 这里可以添加实际的搜索逻辑(如调用接口) this.$message.info('搜索功能已触发'); }, handleSelectionChange(rows) { this.selectedRows = rows; }, // 统计账单金额 calculateTotal(voucher) { let totalDebit = 0; let totalCredit = 0; const units = [100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1, 0.1, 0.01]; voucher.items.forEach(item => { totalDebit += item.debitDigits.reduce((sum, digit, index) => { return sum + digit * units[index]; }, 0); totalCredit += item.creditDigits.reduce((sum, digit, index) => { return sum + digit * units[index]; }, 0); }); voucher.totalDebit = totalDebit; voucher.totalCredit = totalCredit; }, // 这里统计账单金额 把开头为0的全部去掉,在数组的倒数第二位加上小数点 /** * 格式化金额 * @param {number} amount - 需要格式化的金额数字 * @returns {string} - 格式化后的金额字符串 */ formatAmount(amount) { if (amount === undefined || amount === null) { return '0.00'; } const [integer, decimal] = amount.toFixed(2).split('.'); return `${integer.replace(/^0+/, '') || '0'}.${decimal}`; }, // 显示输入框 showInput(voucher, item, type) { this.currentVoucher = voucher; this.currentItem = item; this.currentType = type; this.inputValue = ''; // 用个按钮,并把按钮变成input const inputOverlay = this.$refs.inputOverlay; const inputElement = inputOverlay.querySelector('input'); let targetCell; if (type == 'debit') { console.log('showInput', type); targetCell = event.currentTarget.parentElement.querySelectorAll('td')[3]; } else { console.log('showInput', type); targetCell = event.currentTarget.parentElement.querySelectorAll('td')[15]; } inputOverlay.style.display = 'block'; const rect = targetCell.getBoundingClientRect(); inputOverlay.style.left = rect.left + 'px'; inputOverlay.style.top = rect.top + 'px'; // 自动聚焦输入框,进入输入状态 inputElement.focus(); inputElement.style.outline = 'none'; // 获取 单元格的长度和宽度,动态设置宽高 宽度 = 单元格的宽度*11,长度 = 单元格的高度 inputElement.style.width = rect.width * 11 + 17 + 'px'; inputElement.style.height = rect.height + 'px'; }, hideInput() { this.$refs.inputOverlay.style.display = 'none'; }, submitInput() { const amount = parseFloat(this.inputValue); // 转换为数字类型 if (!isNaN(amount)) { if (amount === undefined || amount === null) { return '0.00'; } let digits = amount.toFixed(2).toString().replace('.', '').padStart(11, '0').split('').map(Number); if (this.currentType === 'debit') { this.currentItem.debitDigits = digits; } else { this.currentItem.creditDigits = digits; } this.calculateTotal(this.currentVoucher); } this.hideInput(); // 隐藏输入框 } } }); </script> </body> </html>