UNPKG

motorvehicles

Version:

机动车发票展示、motorvehiclesinvoice、vue发票模板

900 lines (760 loc) 19.9 kB
# 🚗 机动车销售统一发票组件 > 一个专业的机动车销售统一发票 Vue 组件,支持自定义校验、主题配置、响应式布局 **问题反馈:** 微信 `zkhh6666`(请备注好来意) --- ## 📦 安装 (Installation) ```bash npm install motorvehicles --save ``` > ⚠️ **重要提示:** 建议为组件设置唯一的 `key` 属性,避免数据复用导致的渲染问题 --- ## 🚀 快速开始 (Quick Start) ### 1. 引入组件 ```javascript import MotorVehiclesIvoice from 'motorvehicles' import 'motorvehicles/motorvehicles.css' // 可以通过 class 名来覆盖其中属性 export default { components: { MotorVehiclesIvoice } } ``` ### 2. 使用组件 ```vue <template> <div> <!-- 基础使用 --> <MotorVehiclesIvoice :key="invoice.id" :targetLocation="locationConfig" :targetContent="invoiceData" /> <!-- 自定义主题 --> <MotorVehiclesIvoice :key="invoice.id" :targetLocation="locationConfig" :targetContent="invoiceData" themeColor="#1890ff" fontSize="16px" taxRateList="customRateList" /> </div> </template> ``` --- ## 📖 配置详解 ### 📌 参数配置 (Props) | 参数名称 | 说明 | 类型 | 默认值 | 必填 | |:---:|:---|:---:|:---:|:---:| | `targetLocation` | 字段位置和校验规则配置 | `Array<Object>` | `[]` | | | `targetContent` | 发票数据内容 | `Object` | `{}` | | | `mode` | 显示模式 | `String` | `'normal'` | | | `themeColor` | 主题颜色(边框和文字颜色) | `String` | `'#964300'` | | | `fontSize` | 字体大小 | `String` | `'14px'` | | | `borderWidth` | 边框宽度 | `String` | `'1px'` | | | `taxRateList` | 税率下拉选项列表 | `Array<Object>` | 默认税率 | | | `key` | 组件唯一标识 | `String/Number` | - | 推荐 | #### mode 模式说明 | 模式值 | 说明 | |:---:|:---| | `'normal'` | 正常模式,字段可编辑(根据 disabled 配置) | | `'look'` | 查看模式,所有字段只读 | --- ### 📋 targetLocation 配置说明 定义发票各字段的位置、状态和校验规则 #### 字段说明 | 字段 | 说明 | 类型 | 示例 | 必填 | |:---:|:---|:---:|:---|:---:| | `index` | 字段位置索引(从 0 开始) | `Number` | `0` | | | `key` | 字段显示名称(用户可见) | `String` | `'开票日期'` | | | `name` | 字段数据名称(对应 targetContent) | `String` | `'kaipiaoriqi'` | | | `disabled` | 是否禁用编辑 | `Boolean` | `false` | | | `required` | 是否必填 | `Boolean` | `false` | | | `type` | 字段类型 | `String` | `'input'` / `'select'` | | | `validateFields` | 校验规则 | `Object/Function` | 见下方说明 | | #### 校验规则配置 **1. 正则表达式校验:** ```javascript { index: 7, key: '购买方名称', name: 'purchaserName', disabled: false, required: true, validateFields: { rule: /^[\u4e00-\u9fa5a-zA-Z0-9]{2,50}$/, message: '购买方名称格式不正确,需2-50个字符' } } ``` **2. 自定义函数校验:** ```javascript { index: 8, key: '纳税人识别号', name: 'taxNo', disabled: false, required: true, validateFields: (value) => { if (!value) return '纳税人识别号不能为空' if (!/^[A-Z0-9]{15,20}$/.test(value)) { return '纳税人识别号格式不正确' } return true // 返回 true 表示校验通过 } } ``` #### 完整配置示例 ```javascript const targetLocation = [ // 顶部信息区(只读) { index: 0, key: '发票名称', name: 'invoiceTitle', disabled: true, type: 'input' }, { index: 1, key: '发票类型', name: 'invoiceType', disabled: true, type: 'input' }, { index: 2, key: '发票联次', name: 'invoiceNum', disabled: true, type: 'input' }, // 机打信息(只读) { index: 3, key: '机打代码', name: 'machineCode', disabled: true, type: 'input' }, { index: 4, key: '机打号码', name: 'machineNum', disabled: true, type: 'input' }, { index: 5, key: '机器编号', name: 'machineSerialNum', disabled: true, type: 'input' }, { index: 6, key: '税控码', name: 'taxControlCode', disabled: true, type: 'input' }, // 购买方信息(可编辑+必填+校验) { index: 7, key: '购买方名称', name: 'purchaserName', disabled: false, required: true, type: 'input', validateFields: { rule: /^[\u4e00-\u9fa5a-zA-Z0-9]{2,50}$/, message: '购买方名称格式不正确' } }, { index: 8, key: '纳税人识别号', name: 'taxNo', disabled: false, required: true, type: 'select', validateFields: (value) => { if (!value) return '纳税人识别号不能为空' if (!/^[A-Z0-9]{15,20}$/.test(value)) { return '纳税人识别号格式不正确' } return true } }, // 车辆信息 { index: 9, key: '车辆类型', name: 'vehicleType', disabled: false, required: true, type: 'input' }, { index: 10, key: '厂牌型号', name: 'brandModel', disabled: false, required: true, type: 'input' }, { index: 11, key: '产地', name: 'productionPlace', disabled: false, type: 'input' }, { index: 12, key: '合格证号', name: 'certificateNo', disabled: false, type: 'input' }, { index: 13, key: '进口证明书号', name: 'importCertNo', disabled: false, type: 'input' }, { index: 14, key: '商检单号', name: 'inspectionNo', disabled: false, type: 'input' }, { index: 15, key: '发动机号码', name: 'engineNo', disabled: false, type: 'input' }, { index: 16, key: '车辆识别号/车架号码', name: 'vin', disabled: false, required: true, type: 'input', validateFields: { rule: /^[A-Z0-9]{17}$/, message: '车辆识别号必须为17位' } }, // 价格信息 { index: 17, key: '价税合计(大写)', name: 'totalAmount', disabled: true, type: 'input' }, { index: 18, key: '价税合计(小写)', name: 'totalAmountSmall', disabled: false, required: true, type: 'input', validateFields: { rule: /^\d+(\.\d{1,2})?$/, message: '请输入正确的金额格式' } }, // 销售方信息 { index: 19, key: '销货单位名称', name: 'sellerName', disabled: false, type: 'input' }, { index: 20, key: '电话', name: 'sellerPhone', disabled: false, type: 'input' }, { index: 21, key: '纳税人识别号', name: 'sellerTaxNo', disabled: false, type: 'input' }, { index: 22, key: '账号', name: 'sellerAccount', disabled: false, type: 'input' }, { index: 23, key: '地址', name: 'sellerAddress', disabled: false, type: 'input' }, { index: 24, key: '开户银行', name: 'sellerBank', disabled: false, type: 'input' }, // 税务信息 { index: 25, key: '增值税税率或征收率', name: 'taxRate', disabled: false, type: 'select' }, { index: 26, key: '增值税税额', name: 'taxAmount', disabled: true, type: 'input' }, { index: 27, key: '主管税务机关及代码', name: 'taxAuthority', disabled: true, type: 'input' }, { index: 28, key: '不含税价', name: 'amountExcludingTax', disabled: true, type: 'input' }, { index: 29, key: '完税凭证号码', name: 'taxReceiptNo', disabled: true, type: 'input' }, // 车辆参数 { index: 30, key: '吨位', name: 'tonnage', disabled: false, type: 'input' }, { index: 31, key: '限乘人数', name: 'passengerCapacity', disabled: false, type: 'input' } ] ``` --- ### 📝 targetContent 配置说明 发票的实际数据内容,字段名与 `targetLocation` 中的 `name` 字段对应 ```javascript const targetContent = { // 顶部信息 invoiceTitle: '机动车销售统一发票', invoiceType: '增值税专用发票', invoiceNum: '第一联:发票联', // 机打信息 machineCode: '1100204130', machineNum: '01245896', machineSerialNum: 'M12345678', taxControlCode: '12345678901234567890123456789012', // 购买方信息 purchaserName: '张三', taxNo: '91110000MA01XXXXX', // 车辆信息 vehicleType: '小型轿车', brandModel: '特斯拉 Model 3 标准续航后驱升级版', productionPlace: '中国上海', certificateNo: 'CERT2024123456', importCertNo: '', inspectionNo: '', engineNo: 'ENG20240001', vin: 'LRWXXXXXXXXXXX123', // 价格信息 totalAmount: '叁拾伍万元整', totalAmountSmall: '350,000.00', // 销售方信息 sellerName: '某某汽车销售有限公司', sellerPhone: '010-12345678', sellerTaxNo: '91110000MA01YYYYY', sellerAccount: '1234567890123456789', sellerAddress: '北京市朝阳区某某街道100号', sellerBank: '中国工商银行北京某某支行', // 税务信息 taxRate: '0.13', taxAmount: '40,265.49', taxAuthority: '国家税务总局北京市税务局 11000000', amountExcludingTax: '309,734.51', taxReceiptNo: '', // 车辆参数 tonnage: '', passengerCapacity: '5' } ``` --- ### 📊 taxRateList 配置说明 自定义税率下拉选项 ```javascript const taxRateList = [ { label: '13%', value: '0.13' }, { label: '9%', value: '0.09' }, { label: '6%', value: '0.06' }, { label: '3%', value: '0.03' }, { label: '0%', value: '0' } ] ``` 传入组件: ```vue <MotorVehiclesIvoice :targetLocation="locationConfig" :targetContent="invoiceData" :taxRateList="taxRateList" /> ``` --- ### 🎨 主题自定义 通过 props 自定义组件样式: ```vue <!-- 默认主题(棕色) --> <MotorVehiclesIvoice :targetLocation="locationConfig" :targetContent="invoiceData" /> <!-- 蓝色主题 --> <MotorVehiclesIvoice :targetLocation="locationConfig" :targetContent="invoiceData" themeColor="#1890ff" fontSize="16px" borderWidth="2px" /> <!-- 红色主题 --> <MotorVehiclesIvoice :targetLocation="locationConfig" :targetContent="invoiceData" themeColor="#ff4d4f" fontSize="12px" borderWidth="1px" /> <!-- 自定义 CSS 覆盖 --> <MotorVehiclesIvoice class="custom-invoice" :targetLocation="locationConfig" :targetContent="invoiceData" /> ``` 也可以通过 CSS 覆盖样式: ```css /* 覆盖默认样式 */ .custom-invoice .MotorVehiclesIvoice_AllBox_Font { font-size: 16px !important; color: #1890ff !important; } .custom-invoice .MotorVehiclesIvoice_AllBox_border { border-color: #1890ff !important; } ``` --- ## 🔧 方法 (Methods) ### getFieldsValue() 获取组件当前的所有字段值 **返回值:** `Object` - 包含所有字段的数据对象 ```javascript // 通过 ref 调用 const invoiceData = this.$refs.motorInvoice.getFieldsValue() console.log(invoiceData) // 返回: // { // purchaserName: '张三', // taxNo: '91110000MA01XXXXX', // vehicleType: '小型轿车', // brandModel: '特斯拉 Model 3', // totalAmountSmall: '350,000.00', // ... // } ``` **使用示例:** ```vue <template> <div> <MotorVehiclesIvoice ref="motorInvoice" :targetLocation="locationConfig" :targetContent="invoiceData" /> <button @click="handleSave">保存</button> </div> </template> <script> export default { methods: { handleSave() { const data = this.$refs.motorInvoice.getFieldsValue() console.log('当前发票数据:', data) // 调用接口保存... } } } </script> ``` --- ### validateFields(callback) 触发表单校验,校验 `targetLocation` 中配置的所有规则 **参数:** - `callback: Function(error, values)` - 校验完成后的回调函数 - `error`: 校验失败时的错误对象,格式:`{ code: 500, message: '错误信息' }` - `values`: 校验成功时的所有字段值 **校验规则优先级:** 1. **必填校验:** 检查 `required: true` 的字段 2. **正则校验:** 检查配置了 `validateFields.rule` 的字段 3. **自定义校验:** 执行配置的 `validateFields` 函数 ```javascript this.$refs.motorInvoice.validateFields((error, values) => { if (error) { // 校验失败 console.error('校验失败:', error.message) this.$message.error(error.message) } else { // 校验通过 console.log('校验通过,数据:', values) this.submitInvoice(values) } }) ``` **完整使用示例:** ```vue <template> <div> <MotorVehiclesIvoice ref="motorInvoice" :targetLocation="locationConfig" :targetContent="formData" /> <div class="button-group"> <button @click="handleValidate">校验</button> <button @click="handleSubmit">提交</button> <button @click="handleReset">重置</button> </div> </div> </template> <script> export default { data() { return { locationConfig: [...], // targetLocation 配置 formData: {...} // targetContent 数据 } }, methods: { // 仅校验 handleValidate() { this.$refs.motorInvoice.validateFields((error, values) => { if (error) { this.$message.error(error.message) } else { this.$message.success('校验通过') } }) }, // 校验并提交 handleSubmit() { this.$refs.motorInvoice.validateFields(async (error, values) => { if (error) { this.$message.error(error.message) return } try { const res = await this.$api.saveInvoice(values) this.$message.success('保存成功') } catch (err) { this.$message.error('保存失败:' + err.message) } }) }, // 重置表单 handleReset() { this.formData = { purchaserName: '', taxNo: '', vehicleType: '', // ... 重置所有字段 } } } } </script> ``` --- ## 💡 使用技巧 ### 1. 避免数据复用问题 为组件设置唯一的 `key` 避免 Vue 复用导致的数据错乱: ```vue <MotorVehiclesIvoice v-for="invoice in invoiceList" :key="invoice.id" :targetContent="invoice.data" /> ``` ### 2. 动态禁用字段 根据业务逻辑动态控制字段是否可编辑: ```javascript computed: { locationConfig() { return this.baseLocation.map(item => { // 已提交的发票,所有字段禁用 if (this.invoiceStatus === 'submitted') { return { ...item, disabled: true } } // 审核中的发票,仅部分字段可编辑 if (this.invoiceStatus === 'reviewing') { const editableFields = ['sellerPhone', 'sellerAddress'] return { ...item, disabled: !editableFields.includes(item.name) } } return item }) } } ``` ### 3. 自定义复杂校验 实现多字段联动校验: ```javascript { index: 18, key: '价税合计(小写)', name: 'totalAmountSmall', required: true, validateFields: (value) => { const amount = parseFloat(value.replace(/,/g, '')) const taxRate = parseFloat(this.formData.taxRate) const taxAmount = parseFloat(this.formData.taxAmount.replace(/,/g, '')) // 校验:价税合计 不含税价 + 税额 const expectedTotal = amount / (1 + taxRate) const actualTotal = amount - taxAmount if (Math.abs(expectedTotal - actualTotal) > 0.01) { return '价税合计与税额不匹配,请检查' } return true } } ``` ### 4. 打印功能 添加打印样式: ```vue <template> <div> <MotorVehiclesIvoice ref="invoice" class="print-invoice" :targetLocation="locationConfig" :targetContent="invoiceData" /> <button @click="handlePrint">打印</button> </div> </template> <script> export default { methods: { handlePrint() { window.print() } } } </script> <style> @media print { /* 打印时隐藏按钮等元素 */ button { display: none; } /* 调整发票样式 */ .print-invoice { width: 210mm; height: 297mm; page-break-after: always; } } </style> ``` ### 5. 数据初始化 从接口获取数据并初始化组件: ```javascript async mounted() { try { // 获取发票配置 const config = await this.$api.getInvoiceConfig() this.locationConfig = config.fields // 获取发票数据 const invoiceId = this.$route.params.id if (invoiceId) { const data = await this.$api.getInvoiceDetail(invoiceId) this.invoiceData = data } } catch (err) { this.$message.error('数据加载失败') } } ``` --- ## 📸 效果预览 ![机动车发票组件效果图](https://img-blog.csdnimg.cn/d91289ff903b4958aa2721f466078aab.png) --- ## 📝 注意事项 1. ⚠️ **key 的重要性:** 在列表渲染时务必设置唯一的 `key`,避免数据复用问题 2. ⚠️ **校验函数返回值:** 自定义校验函数必须返回 `true`(通过)或错误信息字符串(失败) 3. ⚠️ **必填字段标识:** 配置 `required: true` 的字段会在标签后显示红色 `*` 4. ⚠️ **字段索引顺序:** `targetLocation` `index` 必须按顺序从 0 开始递增 5. ⚠️ **下拉框配置:** `type: 'select'` 的字段需要配置对应的 `taxRateList` 6. ⚠️ **样式覆盖:** 引入 CSS 后可以通过 class 名覆盖默认样式 7. ⚠️ **mode 模式:** `look` 模式下所有字段只读,`normal` 模式根据 `disabled` 配置决定 --- ## 🆚 与增值税发票组件的区别 | 特性 | 机动车发票 | 增值税发票 | |:---:|:---|:---| | **数据结构** | 固定字段位置 | 灵活的 table 结构 | | **适用场景** | 汽车销售 | 通用商品/服务 | | **校验方式** | 单字段独立校验 | 支持行级校验 | | **明细行数** | 固定格式 | 动态多行 | --- ## 📅 版本记录 ### v1.0.0 (2025-11-25) - 🎉 插件首次发布 - 支持机动车销售统一发票渲染 - 支持字段校验(必填、正则、自定义函数) - 支持主题自定义(颜色、字体、边框) - 支持只读/编辑模式切换 --- ## 📄 License MIT License --- ## 🤝 贡献与反馈 欢迎提交 Issue Pull Request! **联系方式:** 微信 `zkhh6666`(请备注好来意) --- ## 🔗 相关链接 - **GitHub 仓库:** [待补充] - **在线示例:** [待补充]