第10章 UI组件库与工具

第28节 Element Plus使用

10.28.1 安装与按需导入

什么是Element Plus?

Element Plus是一套基于Vue 3的桌面端组件库,是Element UI的Vue 3版本。它提供了丰富的组件,包括表单、表格、弹窗、导航等,能够帮助开发者快速构建美观、功能完善的Web应用。

安装Element Plus

使用npm安装:

npm install element-plus

使用yarn安装:

yarn add element-plus

使用pnpm安装:

pnpm add element-plus

完整导入

完整导入是最简单的使用方式,但是会导入所有组件,增加打包体积:

// main.js
import { createApp } from 'vue'
import ElementPlus from 'element-plus' // 导入Element Plus
import 'element-plus/dist/index.css' // 导入样式
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus) // 使用Element Plus
app.mount('#app')

按需导入

按需导入可以只导入需要使用的组件,减小打包体积。Element Plus支持使用Vite或Webpack的自动导入插件。

使用Vite插件
  1. 安装自动导入插件:
npm install -D unplugin-vue-components unplugin-auto-import
  1. 配置Vite:
// vite.config.js
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})
  1. 在组件中直接使用组件,无需导入:
<template>
  <div>
    <el-button type="primary">主要按钮</el-button>
    <el-input v-model="input" placeholder="请输入内容"></el-input>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const input = ref('')
</script>
手动按需导入

如果不使用自动导入插件,也可以手动按需导入组件:

// main.js
import { createApp } from 'vue'
import { ElButton, ElInput } from 'element-plus' // 手动导入需要的组件
import 'element-plus/es/components/button/style/css' // 导入按钮样式
import 'element-plus/es/components/input/style/css' // 导入输入框样式
import App from './App.vue'

const app = createApp(App)
app.use(ElButton)
app.use(ElInput)
app.mount('#app')

10.28.2 常用组件学习

布局容器

Element Plus提供了el-containerel-headerel-asideel-mainel-footer组件来实现常见的页面布局:

<template>
  <el-container style="height: 500px; border: 1px solid #eee;">
    <el-aside width="200px" style="background-color: rgb(238, 241, 246);">
      <el-menu :default-openeds="['1', '3']">
        <el-sub-menu index="1">
          <template #title>
            <el-icon><el-icon-menu /></el-icon>
            <span>导航一</span>
          </template>
          <el-menu-item-group>
            <template #title>分组一</template>
            <el-menu-item index="1-1">选项1</el-menu-item>
            <el-menu-item index="1-2">选项2</el-menu-item>
          </el-menu-item-group>
        </el-sub-menu>
        <el-sub-menu index="2">
          <template #title>
            <el-icon><el-icon-document /></el-icon>
            <span>导航二</span>
          </template>
          <el-menu-item index="2-1">选项1</el-menu-item>
        </el-sub-menu>
      </el-menu>
    </el-aside>

    <el-container>
      <el-header style="text-align: center; line-height: 60px; background-color: #b3c0d1;">
        头部区域
      </el-header>

      <el-main>
        <el-card>
          <template #header>
            <div class="card-header">
              <span>卡片标题</span>
              <el-button type="primary" size="small">操作按钮</el-button>
            </div>
          </template>
          <div>卡片内容区域</div>
        </el-card>
      </el-main>

      <el-footer style="text-align: center; background-color: #b3c0d1;">
        底部区域
      </el-footer>
    </el-container>
  </el-container>
</template>

<style scoped>
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

表单组件

Element Plus提供了丰富的表单组件,如el-inputel-selectel-radioel-checkbox等:

<template>
  <el-form :model="ruleForm" :rules="rules" ref="ruleFormRef" label-width="100px" class="demo-ruleForm">
    <el-form-item label="用户名" prop="name">
      <el-input v-model="ruleForm.name" placeholder="请输入用户名"></el-input>
    </el-form-item>
    <el-form-item label="密码" prop="pass">
      <el-input v-model="ruleForm.pass" type="password" placeholder="请输入密码" show-password></el-input>
    </el-form-item>
    <el-form-item label="确认密码" prop="checkPass">
      <el-input v-model="ruleForm.checkPass" type="password" placeholder="请确认密码" show-password></el-input>
    </el-form-item>
    <el-form-item label="性别" prop="gender">
      <el-radio-group v-model="ruleForm.gender">
        <el-radio label="male">男</el-radio>
        <el-radio label="female">女</el-radio>
      </el-radio-group>
    </el-form-item>
    <el-form-item label="爱好" prop="hobbies">
      <el-checkbox-group v-model="ruleForm.hobbies">
        <el-checkbox label="读书"></el-checkbox>
        <el-checkbox label="运动"></el-checkbox>
        <el-checkbox label="游戏"></el-checkbox>
        <el-checkbox label="音乐"></el-checkbox>
      </el-checkbox-group>
    </el-form-item>
    <el-form-item label="职业" prop="occupation">
      <el-select v-model="ruleForm.occupation" placeholder="请选择职业">
        <el-option label="学生" value="student"></el-option>
        <el-option label="教师" value="teacher"></el-option>
        <el-option label="工程师" value="engineer"></el-option>
        <el-option label="其他" value="other"></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="个人简介" prop="introduction">
      <el-input
        v-model="ruleForm.introduction"
        type="textarea"
        :rows="3"
        placeholder="请输入个人简介"
      ></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)">提交</el-button>
      <el-button @click="resetForm(ruleFormRef)">重置</el-button>
    </el-form-item>
  </el-form>
</template>

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

const ruleFormRef = ref()
const ruleForm = reactive({
  name: '',
  pass: '',
  checkPass: '',
  gender: 'male',
  hobbies: [],
  occupation: '',
  introduction: ''
})

const rules = reactive({
  name: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
  ],
  pass: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
  ],
  checkPass: [
    { required: true, message: '请确认密码', trigger: 'blur' },
    {
      validator: (rule, value, callback) => {
        if (value !== ruleForm.pass) {
          callback(new Error('两次输入密码不一致'))
        } else {
          callback()
        }
      },
      trigger: 'blur'
    }
  ],
  hobbies: [
    { required: true, message: '请至少选择一个爱好', trigger: 'change' }
  ],
  occupation: [
    { required: true, message: '请选择职业', trigger: 'change' }
  ]
})

const submitForm = (formEl) => {
  if (!formEl) return
  formEl.validate((valid) => {
    if (valid) {
      console.log('表单验证通过', ruleForm)
    } else {
      console.log('表单验证失败')
      return false
    }
  })
}

const resetForm = (formEl) => {
  if (!formEl) return
  formEl.resetFields()
}
</script>

<style scoped>
.demo-ruleForm {
  max-width: 600px;
}
</style>

数据展示组件

Element Plus提供了表格、卡片、标签页等数据展示组件:

<template>
  <el-card>
    <template #header>
      <div class="card-header">
        <span>用户列表</span>
        <el-button type="primary" size="small" @click="dialogVisible = true">添加用户</el-button>
      </div>
    </template>
    
    <el-table :data="tableData" style="width: 100%" border>
      <el-table-column prop="date" label="日期" width="180"></el-table-column>
      <el-table-column prop="name" label="姓名" width="180"></el-table-column>
      <el-table-column prop="address" label="地址"></el-table-column>
      <el-table-column label="操作" width="200" fixed="right">
        <template #default="scope">
          <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
          <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    
    <div class="pagination-container">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 50, 100]"
        layout="total, sizes, prev, pager, next, jumper"
        :total="tableData.length"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      ></el-pagination>
    </div>
  </el-card>
  
  <!-- 对话框 -->
  <el-dialog
    v-model="dialogVisible"
    title="添加用户"
    width="500px"
  >
    <el-form :model="form" label-width="80px">
      <el-form-item label="姓名">
        <el-input v-model="form.name"></el-input>
      </el-form-item>
      <el-form-item label="日期">
        <el-date-picker
          v-model="form.date"
          type="date"
          placeholder="选择日期"
          style="width: 100%"
        ></el-date-picker>
      </el-form-item>
      <el-form-item label="地址">
        <el-input v-model="form.address" type="textarea"></el-input>
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="dialogVisible = false">确定</el-button>
      </span>
    </template>
  </el-dialog>
</template>

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

const dialogVisible = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)

const form = reactive({
  name: '',
  date: '',
  address: ''
})

const tableData = reactive([
  {
    date: '2023-01-01',
    name: '张三',
    address: '北京市朝阳区'
  },
  {
    date: '2023-01-02',
    name: '李四',
    address: '上海市浦东新区'
  },
  {
    date: '2023-01-03',
    name: '王五',
    address: '广州市天河区'
  },
  {
    date: '2023-01-04',
    name: '赵六',
    address: '深圳市南山区'
  }
])

const handleEdit = (row) => {
  console.log('编辑', row)
}

const handleDelete = (row) => {
  console.log('删除', row)
}

const handleSizeChange = (size) => {
  console.log('每页条数变化:', size)
  pageSize.value = size
}

const handleCurrentChange = (current) => {
  console.log('当前页码变化:', current)
  currentPage.value = current
}
</script>

<style scoped>
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
</style>

反馈组件

Element Plus提供了消息提示、对话框、加载动画等反馈组件:

<template>
  <div class="demo-feedback">
    <el-button type="primary" @click="showMessage">消息提示</el-button>
    <el-button type="success" @click="showSuccess">成功提示</el-button>
    <el-button type="warning" @click="showWarning">警告提示</el-button>
    <el-button type="danger" @click="showError">错误提示</el-button>
    
    <el-button @click="showLoading">显示加载</el-button>
    <el-button @click="showConfirm">确认对话框</el-button>
    <el-button @click="showPrompt">输入对话框</el-button>
  </div>
</template>

<script setup>
import { ElMessage, ElLoading, ElMessageBox } from 'element-plus'

const showMessage = () => {
  ElMessage('这是一条消息提示')
}

const showSuccess = () => {
  ElMessage.success('操作成功')
}

const showWarning = () => {
  ElMessage.warning('警告提示')
}

const showError = () => {
  ElMessage.error('操作失败')
}

const showLoading = () => {
  const loading = ElLoading.service({
    lock: true,
    text: '加载中...',
    background: 'rgba(0, 0, 0, 0.7)'
  })
  
  // 模拟异步操作
  setTimeout(() => {
    loading.close()
  }, 2000)
}

const showConfirm = () => {
  ElMessageBox.confirm('确定要删除这条数据吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  })
    .then(() => {
      ElMessage.success('删除成功')
    })
    .catch(() => {
      ElMessage.info('已取消删除')
    })
}

const showPrompt = () => {
  ElMessageBox.prompt('请输入您的邮箱', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    inputPattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/,
    inputErrorMessage: '邮箱格式不正确'
  })
    .then(({ value }) => {
      ElMessage.success(`您输入的邮箱是: ${value}`)
    })
    .catch(() => {
      ElMessage.info('已取消输入')
    })
}
</script>

<style scoped>
.demo-feedback {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
</style>

10.28.3 主题定制与国际化

主题定制

Element Plus支持通过CSS变量或Sass变量来定制主题。

使用CSS变量
  1. 在项目中创建主题文件:
/* src/styles/element-plus-theme.css */
:root {
  /* 主色 */
  --el-color-primary: #409eff;
  --el-color-primary-light-3: #79bbff;
  --el-color-primary-light-5: #a0cfff;
  --el-color-primary-light-7: #c6e2ff;
  --el-color-primary-light-8: #d9ecff;
  --el-color-primary-light-9: #ecf5ff;
  --el-color-primary-dark-2: #337ecc;
  
  /* 成功色 */
  --el-color-success: #67c23a;
  
  /* 警告色 */
  --el-color-warning: #e6a23c;
  
  /* 危险色 */
  --el-color-danger: #f56c6c;
  
  /* 信息色 */
  --el-color-info: #909399;
}
  1. main.js中引入主题文件:
import './styles/element-plus-theme.css'
import ElementPlus from 'element-plus'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
使用Sass变量
  1. 安装Sass:
npm install -D sass
  1. 创建主题文件:
/* src/styles/element-plus-variables.scss */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      'base': #409eff,
    ),
    'success': (
      'base': #67c23a,
    ),
    'warning': (
      'base': #e6a23c,
    ),
    'danger': (
      'base': #f56c6c,
    ),
    'info': (
      'base': #909399,
    ),
  )
);
  1. 配置Vite:
// vite.config.js
import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "${path.resolve(__dirname, 'src/styles/element-plus-variables.scss')}" as *;`,
      },
    },
  },
})

国际化

Element Plus支持多语言,默认使用英文。我们可以配置为中文或其他语言。

全局配置
// main.js
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' // 导入中文语言包
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus, {
  locale: zhCn // 配置中文语言
})
app.mount('#app')
组件内配置
<template>
  <div>
    <el-pagination
      v-model:current-page="currentPage"
      v-model:page-size="pageSize"
      :page-sizes="[10, 20, 50, 100]"
      layout="total, sizes, prev, pager, next, jumper"
      :total="1000"
      :prev-text="$t('pagination.prev')"
      :next-text="$t('pagination.next')"
      :sizes-text="$t('pagination.sizes')"
      :total-text="$t('pagination.total')"
      :jump-text="$t('pagination.jump-to')"
    ></el-pagination>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'

const { t } = useI18n()
const currentPage = ref(1)
const pageSize = ref(10)
</script>

10.28.4 自定义组件开发

我们可以基于Element Plus的组件来开发自定义组件。

自定义搜索组件

<template>
  <div class="custom-search">
    <el-input
      v-model="searchValue"
      placeholder="请输入搜索内容"
      clearable
      @keyup.enter="handleSearch"
      @clear="handleClear"
    >
      <template #append>
        <el-button type="primary" @click="handleSearch">
          <el-icon><el-icon-search /></el-icon>
          搜索
        </el-button>
      </template>
    </el-input>
  </div>
</template>

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

const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  placeholder: {
    type: String,
    default: '请输入搜索内容'
  }
})

const emit = defineEmits(['update:modelValue', 'search', 'clear'])

const searchValue = ref(props.modelValue)

// 监听外部modelValue变化
watch(
  () => props.modelValue,
  (newVal) => {
    searchValue.value = newVal
  }
)

// 监听内部搜索值变化
watch(
  searchValue,
  (newVal) => {
    emit('update:modelValue', newVal)
  }
)

const handleSearch = () => {
  emit('search', searchValue.value)
}

const handleClear = () => {
  emit('clear')
}
</script>

<style scoped>
.custom-search {
  width: 100%;
  max-width: 400px;
}
</style>

在父组件中使用:

<template>
  <div>
    <custom-search
      v-model="searchText"
      placeholder="搜索用户"
      @search="onSearch"
      @clear="onClear"
    ></custom-search>
    
    <div class="search-result">
      <p>搜索内容: {{ searchText }}</p>
      <p>搜索结果: {{ searchResult }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import CustomSearch from './CustomSearch.vue'

const searchText = ref('')
const searchResult = ref('')

const onSearch = (value) => {
  searchResult.value = `搜索了: ${value}`
  console.log('搜索:', value)
}

const onClear = () => {
  searchResult.value = ''
  console.log('清除搜索')
}
</script>

<style scoped>
.search-result {
  margin-top: 20px;
  padding: 10px;
  border: 1px solid #eee;
  border-radius: 4px;
}
</style>

最佳实践与注意事项

  1. 按需导入

    • 使用自动导入插件可以简化开发,同时减小打包体积
    • 对于小型项目,可以考虑完整导入以简化配置
  2. 主题定制

    • 使用CSS变量进行主题定制更简单,适合大多数项目
    • 对于复杂的主题定制,可以使用Sass变量
  3. 组件使用

    • 熟悉Element Plus的组件API,合理选择组件
    • 注意组件的事件和属性,避免不必要的性能开销
    • 合理使用插槽来自定义组件内容
  4. 表单验证

    • 使用Element Plus的表单验证规则,提高表单的易用性
    • 对于复杂的表单验证,使用自定义验证函数
  5. 国际化

    • 对于需要支持多语言的项目,使用国际化配置
    • 合理使用i18n插件来管理翻译
  6. 性能优化

    • 对于大型列表,使用虚拟滚动
    • 合理使用缓存和懒加载
    • 避免在模板中使用复杂的计算逻辑

小结

本节我们学习了Element Plus的使用,包括:

  • Element Plus的安装与按需导入
  • 常用组件的使用:布局、表单、数据展示、反馈组件
  • 主题定制与国际化
  • 自定义组件开发

Element Plus是一个功能丰富、易于使用的Vue 3组件库,能够帮助我们快速构建美观、功能完善的Web应用。通过合理使用Element Plus的组件和功能,我们可以提高开发效率,同时保证代码的质量和可维护性。

思考与练习

  1. 安装Element Plus并配置按需导入。
  2. 使用Element Plus的布局组件创建一个页面布局。
  3. 创建一个包含表单验证的表单组件。
  4. 使用表格组件展示数据,并实现分页功能。
  5. 尝试定制Element Plus的主题颜色。
  6. 基于Element Plus组件开发一个自定义组件。
« 上一篇 26-typescript-advanced 下一篇 » 28-vueuse-utils