Element Plus电商平台:商品管理界面开发指南

【免费下载链接】element-plus element-plus/element-plus: Element Plus 是一个基于 Vue 3 的组件库,提供了丰富且易于使用的 UI 组件,用于快速搭建企业级桌面和移动端的前端应用。 【免费下载链接】element-plus 项目地址: https://gitcode.com/GitHub_Trending/el/element-plus

前言:为什么选择Element Plus构建电商后台?

在当今数字化时代,电商平台的后台管理系统需要处理海量商品数据、复杂的业务流程和多样化的用户需求。Element Plus作为基于Vue 3的企业级UI组件库,提供了丰富的组件和优雅的设计,能够帮助开发者快速构建专业、高效的电商商品管理界面。

通过本文,您将掌握:

  • 商品列表页的表格设计与高级功能实现
  • 商品表单的完整验证与数据管理方案
  • 图片上传与管理的专业解决方案
  • 搜索、筛选与批量操作的最佳实践
  • 响应式设计与用户体验优化技巧

商品列表页:数据展示与操作中心

基础表格结构设计

商品列表是电商后台的核心界面,需要展示大量数据并提供丰富的操作功能。Element Plus的Table组件为此提供了完美的解决方案。

<template>
  <div class="product-management">
    <!-- 搜索和筛选区域 -->
    <el-card class="search-filter-card">
      <el-form :model="searchForm" inline>
        <el-form-item label="商品名称">
          <el-input v-model="searchForm.name" placeholder="请输入商品名称" />
        </el-form-item>
        <el-form-item label="商品分类">
          <el-cascader
            v-model="searchForm.category"
            :options="categoryOptions"
            placeholder="请选择商品分类"
          />
        </el-form-item>
        <el-form-item label="商品状态">
          <el-select v-model="searchForm.status" placeholder="请选择状态">
            <el-option label="全部" value="" />
            <el-option label="上架" value="1" />
            <el-option label="下架" value="0" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 操作按钮区域 -->
    <el-card class="operation-card">
      <el-button type="primary" @click="handleAdd">新增商品</el-button>
      <el-button :disabled="selectedProducts.length === 0" @click="handleBatchDelete">
        批量删除
      </el-button>
      <el-button :disabled="selectedProducts.length === 0" @click="handleBatchUpdateStatus(1)">
        批量上架
      </el-button>
      <el-button :disabled="selectedProducts.length === 0" @click="handleBatchUpdateStatus(0)">
        批量下架
      </el-button>
    </el-card>

    <!-- 商品表格 -->
    <el-card>
      <el-table
        v-loading="loading"
        :data="productList"
        style="width: 100%"
        @selection-change="handleSelectionChange"
        :row-key="row => row.id"
      >
        <el-table-column type="selection" width="55" />
        <el-table-column type="index" label="序号" width="60" />
        
        <el-table-column label="商品信息" min-width="200">
          <template #default="{ row }">
            <div class="product-info">
              <el-image
                :src="row.thumbnail"
                :preview-src-list="[row.thumbnail]"
                fit="cover"
                class="product-thumbnail"
              />
              <div class="product-details">
                <div class="product-name">{{ row.name }}</div>
                <div class="product-sku">SKU: {{ row.sku }}</div>
              </div>
            </div>
          </template>
        </el-table-column>

        <el-table-column prop="categoryName" label="分类" width="120" />
        
        <el-table-column label="价格" width="120">
          <template #default="{ row }">
            <span class="price">¥{{ row.price }}</span>
          </template>
        </el-table-column>

        <el-table-column prop="stock" label="库存" width="80" />
        
        <el-table-column label="状态" width="100">
          <template #default="{ row }">
            <el-tag :type="row.status ? 'success' : 'danger'">
              {{ row.status ? '上架' : '下架' }}
            </el-tag>
          </template>
        </el-table-column>

        <el-table-column prop="sales" label="销量" width="80" />
        
        <el-table-column prop="createTime" label="创建时间" width="150">
          <template #default="{ row }">
            {{ formatDate(row.createTime) }}
          </template>
        </el-table-column>

        <el-table-column label="操作" width="200" fixed="right">
          <template #default="{ row }">
            <el-button size="small" @click="handleEdit(row)">编辑</el-button>
            <el-button
              size="small"
              :type="row.status ? 'warning' : 'success'"
              @click="handleToggleStatus(row)"
            >
              {{ row.status ? '下架' : '上架' }}
            </el-button>
            <el-button size="small" type="danger" @click="handleDelete(row)">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页组件 -->
      <el-pagination
        v-model:current-page="pagination.current"
        v-model:page-size="pagination.size"
        :total="pagination.total"
        :page-sizes="[10, 20, 50, 100]"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </el-card>
  </div>
</template>

高级表格功能实现

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'

// 商品数据
const productList = ref([])
const loading = ref(false)
const selectedProducts = ref([])
const searchForm = reactive({
  name: '',
  category: [],
  status: ''
})

const pagination = reactive({
  current: 1,
  size: 10,
  total: 0
})

// 分类选项
const categoryOptions = [
  {
    value: 'electronics',
    label: '电子产品',
    children: [
      { value: 'phones', label: '手机' },
      { value: 'laptops', label: '笔记本电脑' },
      { value: 'accessories', label: '配件' }
    ]
  },
  {
    value: 'clothing',
    label: '服装',
    children: [
      { value: 'men', label: '男装' },
      { value: 'women', label: '女装' },
      { value: 'kids', label: '童装' }
    ]
  }
]

// 加载商品数据
const loadProducts = async () => {
  loading.value = true
  try {
    // 模拟API调用
    const response = await mockApiGetProducts()
    productList.value = response.data
    pagination.total = response.total
  } catch (error) {
    ElMessage.error('加载商品数据失败')
  } finally {
    loading.value = false
  }
}

// 搜索处理
const handleSearch = () => {
  pagination.current = 1
  loadProducts()
}

const resetSearch = () => {
  Object.assign(searchForm, {
    name: '',
    category: [],
    status: ''
  })
  handleSearch()
}

// 选择处理
const handleSelectionChange = (selection) => {
  selectedProducts.value = selection
}

// 分页处理
const handleSizeChange = (size) => {
  pagination.size = size
  pagination.current = 1
  loadProducts()
}

const handleCurrentChange = (current) => {
  pagination.current = current
  loadProducts()
}

// 商品操作
const handleAdd = () => {
  // 跳转到新增页面或打开对话框
  console.log('新增商品')
}

const handleEdit = (product) => {
  // 跳转到编辑页面或打开对话框
  console.log('编辑商品:', product)
}

const handleDelete = async (product) => {
  try {
    await ElMessageBox.confirm(
      `确定要删除商品"${product.name}"吗?`,
      '删除确认',
      { type: 'warning' }
    )
    // 调用删除API
    await mockApiDeleteProduct(product.id)
    ElMessage.success('删除成功')
    loadProducts()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}

const handleBatchDelete = async () => {
  if (selectedProducts.value.length === 0) return
  
  try {
    await ElMessageBox.confirm(
      `确定要删除选中的${selectedProducts.value.length}个商品吗?`,
      '批量删除确认',
      { type: 'warning' }
    )
    // 调用批量删除API
    const ids = selectedProducts.value.map(item => item.id)
    await mockApiBatchDeleteProducts(ids)
    ElMessage.success('批量删除成功')
    selectedProducts.value = []
    loadProducts()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('批量删除失败')
    }
  }
}

const handleToggleStatus = async (product) => {
  try {
    const newStatus = !product.status
    // 调用API更新状态
    await mockApiUpdateProductStatus(product.id, newStatus)
    ElMessage.success(`${newStatus ? '上架' : '下架'}成功`)
    loadProducts()
  } catch (error) {
    ElMessage.error('操作失败')
  }
}

const handleBatchUpdateStatus = async (status) => {
  if (selectedProducts.value.length === 0) return
  
  try {
    const action = status ? '上架' : '下架'
    await ElMessageBox.confirm(
      `确定要${action}选中的${selectedProducts.value.length}个商品吗?`,
      `批量${action}确认`,
      { type: 'warning' }
    )
    // 调用批量更新API
    const ids = selectedProducts.value.map(item => item.id)
    await mockApiBatchUpdateProductStatus(ids, status)
    ElMessage.success(`批量${action}成功`)
    selectedProducts.value = []
    loadProducts()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(`批量${action}失败`)
    }
  }
}

// 工具函数
const formatDate = (dateString) => {
  return new Date(dateString).toLocaleString('zh-CN')
}

// 模拟API函数
const mockApiGetProducts = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = Array.from({ length: 50 }, (_, i) => ({
        id: i + 1,
        name: `商品${i + 1}`,
        sku: `SKU${String(i + 1).padStart(6, '0')}`,
        thumbnail: 'https://via.placeholder.com/60x60',
        categoryName: i % 3 === 0 ? '手机' : i % 3 === 1 ? '笔记本电脑' : '配件',
        price: (Math.random() * 1000 + 100).toFixed(2),
        stock: Math.floor(Math.random() * 1000),
        status: Math.random() > 0.3,
        sales: Math.floor(Math.random() * 500),
        createTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString()
      }))
      
      const start = (pagination.current - 1) * pagination.size
      const end = start + pagination.size
      resolve({
        data: data.slice(start, end),
        total: data.length
      })
    }, 500)
  })
}

const mockApiDeleteProduct = (id) => {
  return new Promise((resolve) => setTimeout(resolve, 500))
}

const mockApiBatchDeleteProducts = (ids) => {
  return new Promise((resolve) => setTimeout(resolve, 500))
}

const mockApiUpdateProductStatus = (id, status) => {
  return new Promise((resolve) => setTimeout(resolve, 500))
}

const mockApiBatchUpdateProductStatus = (ids, status) => {
  return new Promise((resolve) => setTimeout(resolve, 500))
}

onMounted(() => {
  loadProducts()
})
</script>

<style scoped>
.product-management {
  padding: 20px;
}

.search-filter-card {
  margin-bottom: 20px;
}

.operation-card {
  margin-bottom: 20px;
}

.product-info {
  display: flex;
  align-items: center;
}

.product-thumbnail {
  width: 60px;
  height: 60px;
  border-radius: 4px;
  margin-right: 12px;
}

.product-details {
  flex: 1;
}

.product-name {
  font-weight: 500;
  margin-bottom: 4px;
  line-height: 1.4;
}

.product-sku {
  font-size: 12px;
  color: #909399;
}

.price {
  color: #f56c6c;
  font-weight: 500;
}

:deep(.el-table .cell) {
  line-height: 1.6;
}

:deep(.el-pagination) {
  margin-top: 20px;
  justify-content: flex-end;
}
</style>

商品表单:完整的数据管理与验证

表单结构设计与验证规则

<template>
  <el-dialog
    :title="formTitle"
    v-model="dialogVisible"
    width="800px"
    :before-close="handleClose"
  >
    <el-form
      ref="productFormRef"
      :model="productForm"
      :rules="formRules"
      label-width="100px"
      label-position="right"
    >
      <el-form-item label="商品名称" prop="name">
        <el-input
          v-model="productForm.name"
          placeholder="请输入商品名称"
          maxlength="100"
          show-word-limit
        />
      </el-form-item>

      <el-form-item label="商品分类" prop="category">
        <el-cascader
          v-model="productForm.category"
          :options="categoryOptions"
          placeholder="请选择商品分类"
          style="width: 100%"
        />
      </el-form-item>

      <el-form-item label="商品价格" prop="price">
        <el-input-number
          v-model="productForm.price"
          :min="0"
          :precision="2"
          :step="0.01"
          controls-position="right"
          style="width: 200px"
        />
        <span style="margin-left: 8px">元</span>
      </el-form-item>

      <el-form-item label="商品库存" prop="stock">
        <el-input-number
          v-model="productForm.stock"
          :min="0"
          :step="1"
          controls-position="right"
          style="width: 200px"
        />
        <span style="margin-left: 8px">件</span>
      </el-form-item>

      <el-form-item label="商品重量" prop="weight">
        <el-input-number
          v-model="productForm.weight"
          :min="0"
          :precision="2"
          :step="0.01"
          controls-position="right"
          style="width: 200px"
        />
        <span style="margin-left: 8px">kg</span>
      </el-form-item>

      <el-form-item label="商品状态" prop="status">
        <el-radio-group v-model="productForm.status">
          <el-radio :label="1">上架</el-radio>
          <el-radio :label="0">下架</el-radio>
        </el-radio-group>
      </el-form-item>

      <el-form-item label="商品主图" prop="thumbnail">
        <el-upload
          action="/api/upload"
          :file-list="thumbnailFileList"
          :on-success="handleThumbnailSuccess"
          :before-upload="beforeThumbnailUpload"
          :on-remove="handleThumbnailRemove"
          list-type="picture-card"
          :limit="1"
        >
          <el-icon><Plus /></el-icon>
        </el-upload>
      </el-form-item>

      <el-form-item label="商品相册" prop="images">
        <el-upload
          action="/api/upload"
          :file-list="imageFileList"
          :on-success="handleImageSuccess"
          :before-upload="beforeImageUpload"
          :on-remove="handleImageRemove"
          list-type="picture-card"
          :limit="10"
          multiple
        >
          <el-icon><Plus /></el-icon>
        </el-upload>
      </el-form-item>

      <el-form-item label="商品描述" prop="description">
        <el-input
          v-model="productForm.description"
          type="textarea"
          :rows="4"
          placeholder="请输入商品详细描述"
          maxlength="500"
          show-word-limit
        />
      </el-form-item>

      <el-form-item label="商品规格">
        <el-button type="primary" @click="addSpecification">添加规格</el-button>
        <div v-for="(spec, index) in productForm.specifications" :key="index" class="spec-item">
          <el-input
            v-model="spec.name"
            placeholder="规格名称"
            style="width: 150px; margin-right: 10px"
          />
          <el-input
            v-model="spec.value"
            placeholder="规格值"
            style="width: 150px; margin-right: 10px"
          />
          <el-button
            type="danger"
            icon="Delete"
            @click="removeSpecification(index)"
          />
        </div>
      </el-form-item>
    </el-form>

    <template #footer>
      <span class="dialog-footer">
        <el-button @click="handleClose">取消</el-button>
        <el-button type="primary" @click="handleSubmit">确认</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive, computed, watch } from 'vue'
import { ElMessage

【免费下载链接】element-plus element-plus/element-plus: Element Plus 是一个基于 Vue 3 的组件库,提供了丰富且易于使用的 UI 组件,用于快速搭建企业级桌面和移动端的前端应用。 【免费下载链接】element-plus 项目地址: https://gitcode.com/GitHub_Trending/el/element-plus

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐