Vue3 el-table实现 将子表字段动态显示在主表行尾
1. 主表(订单)的el-table
2. 一个关联展示的el-table,其中一列显示子表信息(商品)
3. 子表管理的el-table(商品项)
同时,我们使用Element Plus的表单组件来添加子表数据。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue3 + Element Plus - 子表字段动态显示在主表行尾</title><!-- 引入Element Plus样式 --><link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css"><!-- 引入Vue3和Element Plus --><script src="https://unpkg.com/vue@3/dist/vue.global.js"></script><script src="https://unpkg.com/element-plus"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"><style>* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background: linear-gradient(135deg, #1a2980, #26d0ce);min-height: 100vh;display: flex;justify-content: center;align-items: center;padding: 20px;color: #333;}.container {width: 100%;max-width: 1400px;background: rgba(255, 255, 255, 0.97);border-radius: 18px;box-shadow: 0 20px 50px rgba(0, 0, 0, 0.25);overflow: hidden;margin: 30px 0;}header {background: linear-gradient(90deg, #0c3483, #2a5298);color: white;padding: 30px 40px;text-align: center;position: relative;overflow: hidden;}header::before {content: "";position: absolute;top: -50%;left: -50%;width: 200%;height: 200%;background: radial-gradient(circle, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0) 70%);transform: rotate(30deg);}header h1 {font-size: 2.8rem;margin-bottom: 15px;display: flex;align-items: center;justify-content: center;gap: 20px;position: relative;text-shadow: 0 2px 8px rgba(0,0,0,0.3);}header p {font-size: 1.25rem;opacity: 0.92;max-width: 900px;margin: 0 auto;line-height: 1.7;position: relative;}.content {padding: 40px;display: flex;flex-direction: column;gap: 40px;}.panel {background: white;border-radius: 15px;padding: 30px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);transition: all 0.4s ease;}.panel:hover {box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);}.master-panel {border: 1px solid #e0e7ff;background: linear-gradient(to bottom, #f7f9ff, #ffffff);}h2 {color: #2c3e50;margin-bottom: 25px;padding-bottom: 15px;border-bottom: 3px solid #4361ee;display: flex;align-items: center;gap: 15px;font-size: 1.8rem;}.instructions {background: #eef5ff;border-left: 5px solid #4361ee;padding: 20px;margin: 25px 0;border-radius: 0 12px 12px 0;}.instructions h3 {margin-bottom: 15px;color: #2c3e50;display: flex;align-items: center;gap: 12px;font-size: 1.3rem;}.instructions ul {padding-left: 25px;}.instructions li {margin-bottom: 12px;line-height: 1.6;}.actions {display: flex;gap: 12px;}.btn {padding: 10px 18px;border: none;border-radius: 8px;cursor: pointer;font-weight: 600;transition: all 0.3s ease;display: inline-flex;align-items: center;gap: 8px;font-size: 1rem;}.btn-primary {background: #4361ee;color: white;}.btn-danger {background: #f44336;color: white;}.btn-success {background: #4caf50;color: white;}.btn-info {background: #2196f3;color: white;}.btn:hover {opacity: 0.92;transform: translateY(-3px);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);}.btn:active {transform: translateY(1px);}.form-row {display: grid;grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: 20px;margin-bottom: 25px;align-items: end;}.form-group {display: flex;flex-direction: column;}label {margin-bottom: 10px;font-weight: 600;color: #2c3e50;display: flex;align-items: center;gap: 8px;}input, select {width: 100%;padding: 14px;border: 1px solid #c5d1ff;border-radius: 10px;font-size: 1.05rem;transition: all 0.3s;background: #f8fafc;}input:focus, select:focus {border-color: #4361ee;outline: none;box-shadow: 0 0 0 4px rgba(67, 97, 238, 0.25);background: white;}.footer {text-align: center;padding: 30px;color: white;background: rgba(12, 52, 131, 0.85);font-size: 1.1rem;}.sub-table-info {padding: 12px 16px;background: #eef5ff;border-radius: 8px;margin-top: 8px;border-left: 3px solid #4361ee;}.sub-table-info h4 {color: #2c3e50;margin-bottom: 8px;display: flex;align-items: center;gap: 8px;}.sub-table-items {display: flex;flex-wrap: wrap;gap: 12px;margin-top: 10px;}.sub-table-item {background: white;border: 1px solid #c5d1ff;border-radius: 6px;padding: 10px 15px;font-size: 0.95rem;box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);display: flex;align-items: center;gap: 8px;}.sub-table-item i {color: #4361ee;}.highlight {color: #4361ee;font-weight: 700;}.empty-state {text-align: center;padding: 50px 30px;color: #6c757d;background: #f8fafc;border-radius: 12px;margin-top: 20px;border: 2px dashed #dee2e6;}.empty-state i {font-size: 4rem;margin-bottom: 25px;color: #adb5bd;opacity: 0.7;}.empty-state h3 {font-size: 1.8rem;margin-bottom: 15px;color: #495057;}.validation-error {color: #f44336;font-size: 0.9rem;margin-top: 6px;display: flex;align-items: center;gap: 5px;}.action-header {display: flex;justify-content: space-between;align-items: center;margin-bottom: 20px;}.master-row td {position: relative;}.sub-data-container {display: flex;flex-wrap: wrap;gap: 10px;margin-top: 15px;padding-top: 15px;border-top: 1px dashed #e0e7ff;}.sub-data-item {background: #f0f5ff;padding: 8px 15px;border-radius: 20px;font-size: 0.95rem;display: flex;align-items: center;gap: 8px;}.sub-data-header {font-weight: 600;color: #4361ee;margin-right: 5px;}.el-table .cell {padding: 8px 12px;}.status-completed {color: #4caf50;font-weight: 600;}.status-processing {color: #2196f3;font-weight: 600;}.status-shipped {color: #ff9800;font-weight: 600;}.status-pending {color: #9e9e9e;font-weight: 600;}@media (max-width: 768px) {.content {padding: 20px;gap: 25px;}header h1 {font-size: 2.2rem;}header p {font-size: 1rem;}.form-row {grid-template-columns: 1fr;}}</style>
</head>
<body><div id="app" class="container"><header><h1><i class="fas fa-table-columns"></i> Vue3 + Element Plus - 子表字段动态显示在主表行尾</h1><p>使用Element Plus的el-table组件实现子表字段在主表行尾的动态显示</p></header><div class="content"><!-- 主表数据面板 --><section class="panel master-panel"><div class="action-header"><h2><i class="fas fa-table"></i> 订单主表数据 (el-table)</h2><button class="btn btn-success" @click="addSampleData"><i class="fas fa-plus"></i> 添加示例数据</button></div><div class="instructions"><h3><i class="fas fa-lightbulb"></i> 实现原理</h3><ul><li>使用Element Plus的<strong>el-table</strong>组件展示主表数据</li><li>主表每行显示订单核心信息(ID、客户、日期、状态)</li><li>通过<strong>计算属性</strong>为每个主表行关联子表数据</li><li>子表特定字段(商品信息)动态显示在主表行尾</li></ul></div><el-table :data="masterData" style="width: 100%" border><el-table-column prop="id" label="订单ID" width="120" sortable></el-table-column><el-table-column prop="customer" label="客户名称" width="180"></el-table-column><el-table-column prop="date" label="订单日期" width="150" :formatter="formatDate"></el-table-column><el-table-column prop="status" label="状态" width="120"><template #default="scope"><span :class="['status', scope.row.status]">{{ scope.row.status }}</span></template></el-table-column><el-table-column label="操作" width="180"><template #default="scope"><button class="btn btn-info" @click="viewDetails(scope.row.id)"><i class="fas fa-eye"></i> 详情</button><button class="btn btn-danger" @click="removeOrder(scope.row.id)"><i class="fas fa-trash"></i></button></template></el-table-column></el-table><div v-if="masterData.length === 0" class="empty-state"><i class="fas fa-inbox"></i><h3>暂无订单数据</h3><p>请添加订单数据,子表信息将动态显示在主表行尾</p></div></section><!-- 主表与子表关联展示 --><section class="panel"><h2><i class="fas fa-link"></i> 主表与子表关联展示 (el-table)</h2><div class="instructions"><h3><i class="fas fa-info-circle"></i> 功能说明</h3><p>在此表格中,主表每行显示核心订单信息,<span class="highlight">子表相关字段动态显示在主表行尾</span>,而非合并主表数据。</p></div><el-table :data="masterData" style="width: 100%" border><el-table-column prop="id" label="订单ID" width="120" sortable></el-table-column><el-table-column prop="customer" label="客户名称" width="180"></el-table-column><el-table-column prop="date" label="订单日期" width="150" :formatter="formatDate"></el-table-column><el-table-column prop="status" label="状态" width="120"><template #default="scope"><span :class="['status', scope.row.status]">{{ scope.row.status }}</span></template></el-table-column><!-- 子表字段动态显示在主表行尾 --><el-table-column label="关联子表信息" min-width="300"><template #default="scope"><div v-if="getSubTableItems(scope.row.id).length > 0"><div class="sub-data-container"><div v-for="item in getSubTableItems(scope.row.id)" :key="item.id" class="sub-data-item"><span class="sub-data-header">商品:</span> {{ item.product }}<span class="sub-data-header">数量:</span> {{ item.quantity }}<span class="sub-data-header">单价:</span> ¥{{ formatCurrency(item.price) }}</div></div></div><div v-else class="sub-data-item"><i class="fas fa-info-circle"></i> 该订单暂无商品信息</div></template></el-table-column><el-table-column label="操作" width="150"><template #default="scope"><button class="btn btn-info" @click="viewDetails(scope.row.id)"><i class="fas fa-eye"></i></button><button class="btn btn-danger" @click="removeOrder(scope.row.id)"><i class="fas fa-trash"></i></button></template></el-table-column></el-table><div v-if="masterData.length === 0" class="empty-state"><i class="fas fa-shopping-cart"></i><h3>暂无订单数据</h3><p>请添加订单数据以查看子表信息在主表行尾的显示效果</p></div></section><!-- 子表管理面板 --><section class="panel"><h2><i class="fas fa-list"></i> 订单子表管理 (el-table)</h2><div class="form-row"><div class="form-group"><label for="orderSelect"><i class="fas fa-shopping-cart"></i> 选择订单</label><select id="orderSelect" v-model="newSubItem.orderId"><option v-for="order in masterData" :value="order.id" :key="order.id">{{ order.id }} - {{ order.customer }}</option></select></div><div class="form-group"><label for="productName"><i class="fas fa-box"></i> 商品名称</label><input type="text" id="productName" v-model="newSubItem.product" placeholder="输入商品名称"><div v-if="validationErrors.product" class="validation-error"><i class="fas fa-exclamation-circle"></i> {{ validationErrors.product }}</div></div><div class="form-group"><label for="productPrice"><i class="fas fa-tag"></i> 单价 (¥)</label><input type="number" id="productPrice" v-model.number="newSubItem.price" placeholder="输入价格" min="0" step="0.01"><div v-if="validationErrors.price" class="validation-error"><i class="fas fa-exclamation-circle"></i> {{ validationErrors.price }}</div></div><div class="form-group"><label for="productQuantity"><i class="fas fa-cubes"></i> 数量</label><input type="number" id="productQuantity" v-model.number="newSubItem.quantity" placeholder="输入数量" min="1"><div v-if="validationErrors.quantity" class="validation-error"><i class="fas fa-exclamation-circle"></i> {{ validationErrors.quantity }}</div></div><div class="form-group"><button class="btn btn-primary" @click="addSubItem"><i class="fas fa-plus-circle"></i> 添加商品</button></div></div><div v-if="subTableData.length > 0"><el-table :data="subTableData" style="width: 100%" border><el-table-column prop="orderId" label="订单ID" width="120" sortable></el-table-column><el-table-column prop="product" label="商品名称" width="200"></el-table-column><el-table-column prop="price" label="单价 (¥)" width="120" :formatter="formatCurrency"></el-table-column><el-table-column prop="quantity" label="数量" width="100"></el-table-column><el-table-column label="小计 (¥)" width="150"><template #default="scope">{{ formatCurrency(scope.row.price * scope.row.quantity) }}</template></el-table-column><el-table-column label="操作" width="120"><template #default="scope"><button class="btn btn-danger" @click="removeSubItem(scope.$index)"><i class="fas fa-trash"></i></button></template></el-table-column></el-table></div><div v-else class="empty-state"><i class="fas fa-box-open"></i><h3>暂无商品数据</h3><p>请添加商品数据,这些数据将动态显示在主表行尾</p></div></section></div><div class="footer"><p>Vue3 + Element Plus - 子表字段动态显示在主表行尾 | 资深前端解决方案</p></div></div><script>const { createApp, ref, reactive, computed } = Vueconst ElementPlus = window.ElementPluscreateApp({setup() {// 主表数据 - 订单列表const masterData = ref([{ id: 1001, customer: "张三", date: new Date(2023, 5, 15), status: "completed" },{ id: 1002, customer: "李四", date: new Date(2023, 5, 18), status: "processing" },{ id: 1003, customer: "王五", date: new Date(2023, 5, 20), status: "shipped" }])// 子表数据 - 订单项列表const subTableData = ref([{ id: 1, orderId: 1001, product: "笔记本电脑", price: 8999.00, quantity: 1 },{ id: 2, orderId: 1001, product: "无线鼠标", price: 259.00, quantity: 2 },{ id: 3, orderId: 1002, product: "智能手机", price: 4999.00, quantity: 1 },{ id: 4, orderId: 1002, product: "蓝牙耳机", price: 899.00, quantity: 1 },{ id: 5, orderId: 1003, product: "平板电脑", price: 3299.00, quantity: 2 },{ id: 6, orderId: 1003, product: "智能手表", price: 1299.00, quantity: 1 }])// 新订单项表单const newSubItem = reactive({orderId: null,product: '',price: '',quantity: 1})// 表单验证错误const validationErrors = reactive({orderId: '',product: '',price: '',quantity: ''})// 获取指定订单的子表项const getSubTableItems = (orderId) => {return subTableData.value.filter(item => item.orderId === orderId)}// 格式化货币const formatCurrency = (value) => {if (typeof value !== 'number') return value;return value.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')}// 格式化日期const formatDate = (row, column, cellValue) => {return new Date(cellValue).toLocaleDateString('zh-CN', {year: 'numeric',month: '2-digit',day: '2-digit'})}// 验证表单const validateSubForm = () => {let isValid = truevalidationErrors.orderId = ''validationErrors.product = ''validationErrors.price = ''validationErrors.quantity = ''if (!newSubItem.orderId) {validationErrors.orderId = '请选择订单'isValid = false}if (!newSubItem.product || newSubItem.product.trim().length < 2) {validationErrors.product = '商品名称至少需要2个字符'isValid = false}if (!newSubItem.price || newSubItem.price <= 0) {validationErrors.price = '请输入有效的价格'isValid = false}if (!newSubItem.quantity || newSubItem.quantity < 1) {validationErrors.quantity = '数量至少为1'isValid = false}return isValid}// 添加新订单项const addSubItem = () => {if (!validateSubForm()) returnsubTableData.value.push({id: Date.now(),orderId: newSubItem.orderId,product: newSubItem.product.trim(),price: parseFloat(newSubItem.price),quantity: parseInt(newSubItem.quantity)})// 重置表单newSubItem.product = ''newSubItem.price = ''newSubItem.quantity = 1}// 删除订单项const removeSubItem = (index) => {subTableData.value.splice(index, 1)}// 添加示例数据const addSampleData = () => {masterData.value = [{ id: 1001, customer: "张三", date: new Date(2023, 5, 15), status: "completed" },{ id: 1002, customer: "李四", date: new Date(2023, 5, 18), status: "processing" },{ id: 1003, customer: "王五", date: new Date(2023, 5, 20), status: "shipped" },{ id: 1004, customer: "赵六", date: new Date(2023, 5, 22), status: "pending" }]subTableData.value = [{ id: 1, orderId: 1001, product: "笔记本电脑", price: 8999.00, quantity: 1 },{ id: 2, orderId: 1001, product: "无线鼠标", price: 259.00, quantity: 2 },{ id: 3, orderId: 1002, product: "智能手机", price: 4999.00, quantity: 1 },{ id: 4, orderId: 1002, product: "蓝牙耳机", price: 899.00, quantity: 1 },{ id: 5, orderId: 1003, product: "平板电脑", price: 3299.00, quantity: 2 },{ id: 6, orderId: 1003, product: "智能手表", price: 1299.00, quantity: 1 },{ id: 7, orderId: 1004, product: "数码相机", price: 4599.00, quantity: 1 }]}// 删除订单const removeOrder = (orderId) => {masterData.value = masterData.value.filter(order => order.id !== orderId)subTableData.value = subTableData.value.filter(item => item.orderId !== orderId)}// 查看详情const viewDetails = (orderId) => {const order = masterData.value.find(o => o.id === orderId)if (order) {const items = getSubTableItems(orderId)let message = `订单 ${orderId} 详情\n客户: ${order.customer}\n日期: ${formatDate(null, null, order.date)}\n状态: ${order.status}\n\n商品列表:\n`items.forEach(item => {message += `- ${item.product} (¥${formatCurrency(item.price)} × ${item.quantity}) = ¥${formatCurrency(item.price * item.quantity)}\n`})message += `\n总计: ¥${formatCurrency(items.reduce((sum, item) => sum + (item.price * item.quantity), 0))}`alert(message)}}// 设置默认订单if (masterData.value.length > 0) {newSubItem.orderId = masterData.value[0].id}return {masterData,subTableData,newSubItem,validationErrors,getSubTableItems,addSubItem,removeSubItem,addSampleData,removeOrder,viewDetails,formatCurrency,formatDate}}}).use(ElementPlus).mount('#app')</script>
</body>
</html>