motorvehicles
v1.0.0
Published
机动车发票展示、motorvehiclesinvoice、vue发票模板
Maintainers
Readme
🚗 机动车销售统一发票组件
一个专业的机动车销售统一发票 Vue 组件,支持自定义校验、主题配置、响应式布局
问题反馈: 微信 zkhh6666(请备注好来意)
📦 安装 (Installation)
npm install motorvehicles --save⚠️ 重要提示: 建议为组件设置唯一的
key属性,避免数据复用导致的渲染问题
🚀 快速开始 (Quick Start)
1. 引入组件
import MotorVehiclesIvoice from 'motorvehicles'
import 'motorvehicles/motorvehicles.css' // 可以通过 class 名来覆盖其中属性
export default {
components: {
MotorVehiclesIvoice
}
}2. 使用组件
<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. 正则表达式校验:
{
index: 7,
key: '购买方名称',
name: 'purchaserName',
disabled: false,
required: true,
validateFields: {
rule: /^[\u4e00-\u9fa5a-zA-Z0-9]{2,50}$/,
message: '购买方名称格式不正确,需2-50个字符'
}
}2. 自定义函数校验:
{
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 表示校验通过
}
}完整配置示例
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 字段对应
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 配置说明
自定义税率下拉选项
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' }
]传入组件:
<MotorVehiclesIvoice
:targetLocation="locationConfig"
:targetContent="invoiceData"
:taxRateList="taxRateList"
/>🎨 主题自定义
通过 props 自定义组件样式:
<!-- 默认主题(棕色) -->
<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 覆盖样式:
/* 覆盖默认样式 */
.custom-invoice .MotorVehiclesIvoice_AllBox_Font {
font-size: 16px !important;
color: #1890ff !important;
}
.custom-invoice .MotorVehiclesIvoice_AllBox_border {
border-color: #1890ff !important;
}🔧 方法 (Methods)
getFieldsValue()
获取组件当前的所有字段值
返回值: Object - 包含所有字段的数据对象
// 通过 ref 调用
const invoiceData = this.$refs.motorInvoice.getFieldsValue()
console.log(invoiceData)
// 返回:
// {
// purchaserName: '张三',
// taxNo: '91110000MA01XXXXX',
// vehicleType: '小型轿车',
// brandModel: '特斯拉 Model 3',
// totalAmountSmall: '350,000.00',
// ...
// }使用示例:
<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: 校验成功时的所有字段值
校验规则优先级:
- 必填校验: 检查
required: true的字段 - 正则校验: 检查配置了
validateFields.rule的字段 - 自定义校验: 执行配置的
validateFields函数
this.$refs.motorInvoice.validateFields((error, values) => {
if (error) {
// 校验失败
console.error('校验失败:', error.message)
this.$message.error(error.message)
} else {
// 校验通过
console.log('校验通过,数据:', values)
this.submitInvoice(values)
}
})完整使用示例:
<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 复用导致的数据错乱:
<MotorVehiclesIvoice
v-for="invoice in invoiceList"
:key="invoice.id"
:targetContent="invoice.data"
/>2. 动态禁用字段
根据业务逻辑动态控制字段是否可编辑:
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. 自定义复杂校验
实现多字段联动校验:
{
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. 打印功能
添加打印样式:
<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. 数据初始化
从接口获取数据并初始化组件:
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('数据加载失败')
}
}📸 效果预览

📝 注意事项
⚠️ key 的重要性: 在列表渲染时务必设置唯一的
key,避免数据复用问题⚠️ 校验函数返回值: 自定义校验函数必须返回
true(通过)或错误信息字符串(失败)⚠️ 必填字段标识: 配置
required: true的字段会在标签后显示红色*号⚠️ 字段索引顺序:
targetLocation的index必须按顺序从 0 开始递增⚠️ 下拉框配置:
type: 'select'的字段需要配置对应的taxRateList⚠️ 样式覆盖: 引入 CSS 后可以通过 class 名覆盖默认样式
⚠️ mode 模式:
look模式下所有字段只读,normal模式根据disabled配置决定
🆚 与增值税发票组件的区别
| 特性 | 机动车发票 | 增值税发票 | |:---:|:---|:---| | 数据结构 | 固定字段位置 | 灵活的 table 结构 | | 适用场景 | 汽车销售 | 通用商品/服务 | | 校验方式 | 单字段独立校验 | 支持行级校验 | | 明细行数 | 固定格式 | 动态多行 |
📅 版本记录
v1.0.0 (2025-11-25)
- 🎉 插件首次发布
- ✨ 支持机动车销售统一发票渲染
- ✨ 支持字段校验(必填、正则、自定义函数)
- ✨ 支持主题自定义(颜色、字体、边框)
- ✨ 支持只读/编辑模式切换
📄 License
MIT License
🤝 贡献与反馈
欢迎提交 Issue 和 Pull Request!
联系方式: 微信 zkhh6666(请备注好来意)
🔗 相关链接
- GitHub 仓库: [待补充]
- 在线示例: [待补充]
