发票项目明细项动态添加行,项目名称自动补全功能,并且自动检索

功能说明

  1. 项目名称检索

    • 在输入框中输入内容时,会实时过滤项目列表

    • 点击搜索图标也会触发过滤并展开下拉列表

  2. 下拉列表

    • 显示匹配的项目名称和分类

    • 匹配文本高亮显示

    • 带图标的项目展示

  3. 底部固定选项

    • "自行选择商品编码"固定在底部

    • 点击后打开商品编码选择对话框

  4. 商品编码选择

    • 以网格形式展示商品编码

    • 选择后自动填充到项目名称字段

  5. 表格功能

    • 使用vxe-table实现高性能表格

    • 金额自动计算

    • 税率下拉选择器

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>发票项目管理系统 - Element UI + vxe-table</title>
    <!-- 引入Element UI样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入vxe-table样式 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vxe-table@3.9.1/lib/style.css">
    <!-- 引入字体图标 -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <!-- 修复VXETable未定义的问题 -->
    <script src="https://cdn.jsdelivr.net/npm/xe-utils"></script>
    <script src="https://cdn.jsdelivr.net/npm/vxe-pc-ui@3.1.27"></script>
    <script src="https://cdn.jsdelivr.net/npm/vxe-table@3.9.1"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }

        body {
            background: linear-gradient(135deg, #f0f5ff 0%, #e6f7ff 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .app-container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 12px;
            box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
            overflow: hidden;
        }

        .header {
            background: linear-gradient(to right, #1a6dcc, #0d4a9e);
            color: white;
            padding: 20px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .header h1 {
            font-size: 24px;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .header-actions {
            display: flex;
            gap: 15px;
        }

        .header-actions .el-button {
            background: rgba(255, 255, 255, 0.15);
            border: none;
            color: white;
            transition: all 0.3s;
        }

        .header-actions .el-button:hover {
            background: rgba(255, 255, 255, 0.25);
        }

        .toolbar {
            padding: 15px 20px;
            background: #f8fafd;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .toolbar-title {
            font-size: 18px;
            font-weight: 600;
            color: #333;
        }

        .toolbar-actions {
            display: flex;
            gap: 10px;
        }

        .vxe-table-container {
            padding: 20px;
        }

        .vxe-table {
            border-radius: 8px;
            overflow: hidden;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
        }

        .vxe-table .vxe-header--column {
            background-color: #f5f9ff;
            color: #1a6dcc;
            font-weight: 600;
        }

        .vxe-table .vxe-body--column {
            padding: 12px 10px;
        }

        .footer {
            padding: 20px;
            background: #f8fafd;
            border-top: 1px solid #eee;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .total-section {
            display: flex;
            align-items: center;
            gap: 20px;
        }

        .total-label {
            font-weight: 600;
            color: #666;
        }

        .total-amount {
            font-size: 22px;
            font-weight: 700;
            color: #e74c3c;
        }

        .tax-section {
            color: #666;
            font-size: 14px;
            line-height: 1.6;
        }

        .add-item-btn {
            background: linear-gradient(to right, #2ecc71, #27ae60);
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            transition: all 0.3s;
        }

        .add-item-btn:hover {
            opacity: 0.9;
            transform: translateY(-2px);
        }

        .custom-autocomplete {
            width: 100%;
        }

        .suggestion-item {
            display: flex;
            align-items: center;
            padding: 8px 12px;
            cursor: pointer;
            transition: all 0.2s;
        }

        .suggestion-item:hover {
            background-color: #f0f7ff;
        }

        .suggestion-icon {
            margin-right: 10px;
            color: #1a6dcc;
            min-width: 20px;
            text-align: center;
        }

        .suggestion-content {
            flex: 1;
            overflow: hidden;
        }

        .suggestion-name {
            font-weight: 500;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .suggestion-category {
            font-size: 12px;
            color: #888;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .fixed-option {
            background: #f8fafd;
            border-top: 1px solid #eee;
            font-weight: 600;
            color: #1a6dcc;
            padding: 10px 12px;
            display: flex;
            align-items: center;
            cursor: pointer;
        }

        .fixed-option:hover {
            background: #e6f0ff;
        }

        .code-modal {
            border-radius: 12px;
            overflow: hidden;
        }

        .code-modal-header {
            background: linear-gradient(to right, #1a6dcc, #0d4a9e);
            color: white;
            padding: 15px 20px;
            font-size: 18px;
            font-weight: 600;
        }

        .code-grid {
            padding: 20px;
            max-height: 400px;
            overflow-y: auto;
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 15px;
        }

        .code-item {
            border: 1px solid #e1e4e8;
            border-radius: 8px;
            padding: 15px;
            cursor: pointer;
            transition: all 0.2s;
            background: white;
        }

        .code-item:hover {
            border-color: #1a6dcc;
            box-shadow: 0 4px 10px rgba(26, 109, 204, 0.15);
            transform: translateY(-3px);
        }

        .code-name {
            font-weight: 600;
            margin-bottom: 5px;
            color: #333;
        }

        .code-id {
            font-size: 13px;
            color: #666;
        }

        .code-category {
            font-size: 12px;
            color: #888;
            margin-top: 5px;
        }

        .highlight {
            color: #e74c3c;
            font-weight: 600;
        }

        .el-popper.custom-popper {
            max-height: 300px;
            overflow-y: auto;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            border: none;
            border-radius: 8px;
            padding: 0;
        }

        .vxe-table .vxe-body--row:hover {
            background-color: #f8fbff;
        }

        .amount-cell {
            font-weight: 500;
            color: #1a6dcc;
        }

        .el-input__inner {
            border-radius: 6px;
        }

        .el-select {
            width: 100%;
        }
    </style>
</head>
<body>
<div id="app">
    <div class="app-container">
        <div class="header">
            <h1>
                <i class="fas fa-file-invoice"></i>
                发票项目管理系统
            </h1>
            <div class="header-actions">
                <el-button icon="el-icon-time">开通时间</el-button>
                <el-button icon="el-icon-document">待发单</el-button>
                <el-button icon="el-icon-tickets">待发单</el-button>
            </div>
        </div>

        <div class="toolbar">
            <div class="toolbar-title">发票项目明细</div>
            <div class="toolbar-actions">
                <el-button type="primary" icon="el-icon-plus" @click="addNewItem">新增行</el-button>
                <el-button type="danger" icon="el-icon-delete" @click="deleteItem">删除</el-button>
                <el-button icon="el-icon-upload2">明细导入</el-button>
                <el-button icon="el-icon-discount">对应折扣</el-button>
            </div>
        </div>

        <div class="vxe-table-container">
            <vxe-table
                    :data="tableData"
                    border="inner"
                    show-overflow
                    auto-resize
                    highlight-hover-row
                    ref="invoiceTable"
            >
                <vxe-column type="seq" width="60" title="序号"></vxe-column>
                <vxe-column field="projectName" title="项目名称" min-width="220">
                    <template v-slot="{ row }">
                        <el-autocomplete
                                class="custom-autocomplete"
                                v-model="row.projectName"
                                :fetch-suggestions="querySearch"
                                placeholder="请输入项目名称"
                                @focus="handleFocus(row)"
                                @select="handleSelect"
                                value-key="name"
                                popper-class="custom-popper"
                                :highlight-first-item="true"
                                :trigger-on-focus="true"
                        >
                            <template v-slot:default="{ item }">
                                <div class="suggestion-item">
                                    <div class="suggestion-content">
                                        <div class="suggestion-name">{{ item.name}}</div>
                                    </div>
                                </div>
                            </template>
                            <template v-slot:footer>
                                <div class="fixed-option" @click="showCodeDialog(row)">
                                    <i class="fas fa-barcode"></i>
                                    <span style="margin-left: 8px;">自行选择商品编码</span>
                                </div>
                            </template>
                        </el-autocomplete>
                    </template>
                </vxe-column>
                <vxe-column field="specification" title="规格型号" width="150">
                    <template v-slot="{ row }">
                        <el-input v-model="row.specification" placeholder="规格型号" size="small"></el-input>
                    </template>
                </vxe-column>
                <vxe-column field="unit" title="单位" width="100">
                    <template v-slot="{ row }">
                        <el-input v-model="row.unit" placeholder="单位" size="small"></el-input>
                    </template>
                </vxe-column>
                <vxe-column field="quantity" title="数量" width="100">
                    <template v-slot="{ row }">
                        <el-input-number
                                v-model="row.quantity"
                                :min="0"
                                :precision="2"
                                controls-position="right"
                                size="small"
                                @change="calculateRowAmount(row)"
                        ></el-input-number>
                    </template>
                </vxe-column>
                <vxe-column field="price" title="单价(含税)" width="150">
                    <template v-slot="{ row }">
                        <el-input
                                v-model="row.price"
                                placeholder="0.00"
                                size="small"
                                @input="calculateRowAmount(row)"
                        >
                            <template slot="prepend">¥</template>
                        </el-input>
                    </template>
                </vxe-column>
                <vxe-column field="amount" title="金额(含税)" width="150">
                    <template v-slot="{ row }">
                        <div class="amount-cell">¥ {{ row.amount }}</div>
                    </template>
                </vxe-column>
                <vxe-column field="taxRate" title="税率/征收率" width="150">
                    <template v-slot="{ row }">
                        <el-select v-model="row.taxRate" placeholder="选择税率" size="small">
                            <el-option label="13%" value="13%"></el-option>
                            <el-option label="9%" value="9%"></el-option>
                            <el-option label="6%" value="6%"></el-option>
                            <el-option label="3%" value="3%"></el-option>
                            <el-option label="0%" value="0%"></el-option>
                        </el-select>
                    </template>
                </vxe-column>
            </vxe-table>
        </div>

        <div class="footer">
            <div class="total-section">
                <div class="total-label">合计:</div>
                <div class="total-amount">¥ {{ totalAmount }}</div>
            </div>
            <div class="tax-section">
                <div>*现代服务*现代服务费</div>
                <div>*交通运输设备*等自行车及</div>
                <div>*交通运输设备*普通通行车</div>
            </div>
            <button class="add-item-btn" @click="addNewItem">
                <i class="fas fa-plus-circle"></i> 添加新项目
            </button>
        </div>
    </div>

    <!-- 商品编码选择对话框 -->
    <el-dialog
            title="选择商品编码"
            :visible.sync="codeDialogVisible"
            width="800px"
            custom-class="code-modal"
            :close-on-click-modal="false"
    >
        <div class="code-modal-header">请选择商品编码</div>
        <div class="code-grid">
            <div
                    v-for="(code, index) in commodityCodes"
                    :key="index"
                    class="code-item"
                    @click="selectCode(code)"
            >
                <div class="code-name">{{ code.name }}</div>
                <div class="code-id">编码: {{ code.id }}</div>
                <div class="code-category">{{ code.category }}</div>
            </div>
        </div>
        <span slot="footer" class="dialog-footer">
                <el-button @click="codeDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="codeDialogVisible = false">确 定</el-button>
            </span>
    </el-dialog>
</div>

<script>
    // 修复VXETable未定义的问题
    Vue.use(VXETable);

    new Vue({
        el: '#app',
        data() {
            return {
                tableData: [
                    {
                        id: 1,
                        projectName: '',
                        specification: '',
                        unit: '',
                        quantity: 1,
                        price: '',
                        amount: '0.00',
                        taxRate: '13%'
                    }
                ],
                projectSuggestions: [
                    { name: "办公用品", category: "办公设备", icon: "fas fa-cube" },
                    { name: "技术服务费", category: "现代服务", icon: "fas fa-laptop-code" },
                    { name: "咨询服务", category: "现代服务", icon: "fas fa-headset" },
                    { name: "软件开发", category: "信息技术", icon: "fas fa-code" },
                    { name: "电脑设备", category: "电子产品", icon: "fas fa-desktop" },
                    { name: "网络服务", category: "信息技术", icon: "fas fa-network-wired" },
                    { name: "广告服务", category: "文化传媒", icon: "fas fa-ad" },
                    { name: "维修服务", category: "技术服务", icon: "fas fa-tools" },
                    { name: "培训服务", category: "教育服务", icon: "fas fa-chalkboard-teacher" },
                    { name: "物流服务", category: "运输服务", icon: "fas fa-truck" },
                    { name: "会议服务", category: "商务服务", icon: "fas fa-conference" },
                    { name: "租赁服务", category: "商业服务", icon: "fas fa-key" }
                ],
                commodityCodes: [
                    { id: "109010101", name: "办公用品", category: "办公设备" },
                    { id: "304020202", name: "技术服务", category: "现代服务" },
                    { id: "304030303", name: "咨询服务", category: "现代服务" },
                    { id: "108050505", name: "软件开发", category: "信息技术" },
                    { id: "109030303", name: "电脑设备", category: "电子产品" },
                    { id: "108040404", name: "网络服务", category: "信息技术" },
                    { id: "306010101", name: "广告服务", category: "文化传媒" },
                    { id: "304040404", name: "维修服务", category: "技术服务" },
                    { id: "307020202", name: "培训服务", category: "教育服务" },
                    { id: "302010101", name: "物流服务", category: "运输服务" },
                    { id: "304050505", name: "会议服务", category: "商务服务" },
                    { id: "304060606", name: "租赁服务", category: "商业服务" }
                ],
                codeDialogVisible: false,
                activeRow: null
            };
        },
        computed: {
            totalAmount() {
                return this.tableData.reduce((total, row) => {
                    return total + parseFloat(row.amount || 0);
                }, 0).toFixed(2);
            }
        },
        methods: {
            calculateRowAmount(row) {
                const quantity = parseFloat(row.quantity) || 0;
                const price = parseFloat(row.price) || 0;
                row.amount = (quantity * price).toFixed(2);
            },
            querySearch(queryString, cb) {
                const results = queryString
                    ? this.projectSuggestions.filter(item =>
                        item.name.toLowerCase().includes(queryString.toLowerCase()) ||
                        item.category.toLowerCase().includes(queryString.toLowerCase())
                    )
                    : this.projectSuggestions;

                // 调用 callback 返回建议列表的数据
                cb(results);
            },
            handleFocus(row) {
                this.activeRow = row;
            },
            handleSelect(item) {
                if (this.activeRow) {
                    this.activeRow.xmmc = item.nsrmc;
                }
            },
            highlightMatch(text, query) {
                if (!query || !text) return text;

                const lowerText = text.toLowerCase();
                const lowerQuery = query.toLowerCase();
                const startIndex = lowerText.indexOf(lowerQuery);

                if (startIndex === -1) return text;

                const endIndex = startIndex + query.length;
                const before = text.substring(0, startIndex);
                const match = text.substring(startIndex, endIndex);
                const after = text.substring(endIndex);

                return `${before}<span class="highlight">${match}</span>${after}`;
            },
            showCodeDialog(row) {
                this.activeRow = row;
                this.codeDialogVisible = true;
            },
            selectCode(code) {
                if (this.activeRow) {
                    this.activeRow.projectName = code.name;
                }
                this.codeDialogVisible = false;
            },
            addNewItem() {
                const newId = this.tableData.length > 0
                    ? Math.max(...this.tableData.map(item => item.id)) + 1
                    : 1;

                this.tableData.push({
                    id: newId,
                    projectName: '',
                    specification: '',
                    unit: '',
                    quantity: 1,
                    price: '',
                    amount: '0.00',
                    taxRate: '13%'
                });
            },
            deleteItem() {
                if (this.tableData.length > 1) {
                    this.tableData.pop();
                } else {
                    this.$message.warning('至少保留一行数据');
                }
            }
        }
    });
</script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战族狼魂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值