Vue 学习与实践大纲(后端视角)

Vue 学习与实践大纲(后端视角)

作为一个后端大数据开发人员,对于前端不必深究;重点掌握 Vue 框架的基本用法和常用 UI 组件(Element Plus),并结合大模型的代码生成能力,快速产出可维护的管理后台页面。关键是做好"组件抽象",让生成的代码结构清晰、可复用、易扩展。

学习目标

  • 能独立完成典型 CRUD 列表页(搜索、表格、分页、弹窗表单)。
  • 会用组合式 API(setuprefcomputedwatch)。
  • 掌握 Element Plus 常用组件并能抽象为通用组件。
  • 会封装 Axios 请求与路由守卫,完成登录鉴权与错误处理。
  • 能用 LLM 生成前端骨架,并通过约束让组件结构可复用。

快速入门要点

  • 组件与响应式:用 ref/reactive 管理局部状态,computed 计算派生值。
  • 路由与布局:vue-router 定义页面与主布局,登录后进入主框架。
  • UI 组件:Element Plus 的表格、表单、弹窗、消息、日期、上传。
  • 状态管理:Pinia 最小使用(如 userStore 存 token、用户信息)。
  • 网络请求:Axios 封装,统一注入 Token,统一错误提示与重试策略。
  • 权限控制:路由守卫 + 指令/组件控制按钮级权限。
  • 组件抽象:props/emits/slots 约定,维持单一职责与低耦合。

目录结构建议

复制代码
src/
  api/           接口请求封装与模块
  components/    可复用基础组件(表格、搜索区、分页、上传等)
  router/        路由与守卫
  stores/        Pinia stores
  utils/         工具方法(时间、下载、权限判断等)
  views/         业务页面(列表、编辑、详情)

Element Plus 必会组件

  • 数据展示:ElTableElPaginationElCard
  • 表单:ElFormElInputElSelectElDatePickerElUpload
  • 反馈:ElMessageElMessageBoxElDialogElNotification
  • 布局:ElContainerElHeaderElAsideElMainElRowElCol

网络与权限

  • Axios:创建实例,request 拦截器写入 token;response 统一处理。
  • 路由守卫:在 beforeEach 判断登录状态与权限,未登录跳转登录页。

组件抽象与复用原则

  • 单一职责:展示组件不直接调接口,数据与分页交由外层控制。
  • 事件导向:用 emits 暴露事件,让外层掌控行为与数据。
  • 插槽扩展:复杂单元格、操作列通过 slots 扩展实现。
  • 低耦合:组件避免直接依赖 store 或路由,必要数据用 props 注入。
  • 可组合:通用逻辑封为 composable(如 usePaginationuseSearchForm)。

最小项目初始化与依赖

  • 环境:Node 18+,包管理器 npmpnpm

  • 初始化命令(Windows PowerShell):

    npm create vite@latest my-admin -- --template vue
    cd my-admin
    npm i element-plus axios pinia vue-router

入口示例(src/main.ts):

ts 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from './router'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(router)
app.use(createPinia())
app.use(ElementPlus)
app.mount('#app')

基础路由(src/router/index.ts):

ts 复制代码
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('../views/Home.vue') },
    { path: '/login', component: () => import('../views/Login.vue') }
  ]
})

export default router

用户状态(src/stores/user.ts):

ts 复制代码
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({ token: '' }),
  actions: { setToken(t: string) { this.token = t } }
})

请求封装(src/api/http.ts):

ts 复制代码
import axios from 'axios'

const http = axios.create({ baseURL: '/api', timeout: 10000 })

http.interceptors.request.use((config) => {
  // 注入 token(示例):
  // const token = localStorage.getItem('token')
  // if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

http.interceptors.response.use(
  (res) => res.data,
  (err) => Promise.reject(err)
)

export default http

通用表格组件约定与示例

约定:

  • props:columnsdataloadingpagination({ page, pageSize, total })
  • emits:update:paginationrow-clickselection-changerefresh
  • slots:toolbarcell-[columnKey]

示例组件(src/components/TablePro.vue):

vue 复制代码
<template>
  <div>
    <slot name="toolbar" />
    <el-table
      :data="data"
      :loading="loading"
      @row-click="(row) => emit('row-click', row)"
      @selection-change="(rows) => emit('selection-change', rows)"
    >
      <el-table-column type="selection" width="48" />
      <el-table-column
        v-for="col in columns"
        :key="col.key"
        :prop="col.key"
        :label="col.label"
        :width="col.width"
        :align="col.align || 'left'"
      >
        <template #default="scope">
          <slot :name="`cell-${col.key}`" :row="scope.row">
            {{ scope.row[col.key] }}
          </slot>
        </template>
      </el-table-column>
    </el-table>

    <el-pagination
      v-if="pagination"
      class="mt-12"
      background
      layout="prev, pager, next, sizes, total"
      :current-page="pagination.page"
      :page-size="pagination.pageSize"
      :total="pagination.total"
      @update:current-page="(p) => emit('update:pagination', { page: p, pageSize: pagination.pageSize })"
      @update:page-size="(s) => emit('update:pagination', { page: 1, pageSize: s })"
    />
  </div>
  </template>

<script setup lang="ts">
import type { PropType } from 'vue'

interface Column { key: string; label: string; width?: number; align?: 'left'|'center'|'right' }

const props = defineProps({
  columns: { type: Array as PropType<Column[]>, required: true },
  data: { type: Array as PropType<any[]>, default: () => [] },
  loading: { type: Boolean, default: false },
  pagination: {
    type: Object as PropType<{ page: number; pageSize: number; total: number }>,
    default: undefined
  }
})

const emit = defineEmits<{
  (e: 'update:pagination', v: { page: number; pageSize: number }): void
  (e: 'row-click', v: any): void
  (e: 'selection-change', v: any[]): void
  (e: 'refresh'): void
}>()
</script>

列表页示例(src/views/UserList.vue):

vue 复制代码
<template>
  <el-card>
    <TablePro
      :columns="columns"
      :data="rows"
      :loading="loading"
      :pagination="pagination"
      @update:pagination="onPage"
    >
      <template #toolbar>
        <el-form :inline="true" :model="query">
          <el-form-item label="关键字">
            <el-input v-model="query.keyword" placeholder="用户名称" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="fetchList">查询</el-button>
            <el-button @click="reset">重置</el-button>
          </el-form-item>
        </el-form>
      </template>

      <template #cell-actions="{ row }">
        <el-button size="small" @click="edit(row)">编辑</el-button>
      </template>
    </TablePro>
  </el-card>
  </template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import TablePro from '../components/TablePro.vue'

const columns = [
  { key: 'id', label: 'ID', width: 80 },
  { key: 'name', label: '姓名' },
  { key: 'phone', label: '手机' },
  { key: 'actions', label: '操作', width: 160, align: 'center' }
]

const query = reactive({ keyword: '' })
const rows = ref<any[]>([])
const loading = ref(false)
const pagination = ref({ page: 1, pageSize: 10, total: 0 })

function onPage(v: { page: number; pageSize: number }) {
  pagination.value.page = v.page
  pagination.value.pageSize = v.pageSize
  fetchList()
}

function reset() {
  query.keyword = ''
  pagination.value.page = 1
  fetchList()
}

async function fetchList() {
  loading.value = true
  try {
    // 替换为真实接口
    const total = 42
    const start = (pagination.value.page - 1) * pagination.value.pageSize
    const end = start + pagination.value.pageSize
    const data = Array.from({ length: total }).map((_, i) => ({ id: i + 1, name: `用户${i + 1}`, phone: '13800000000' }))
    rows.value = data.slice(start, end)
    pagination.value.total = total
  } finally {
    loading.value = false
  }
}

function edit(row: any) {
  console.log('edit', row)
}

fetchList()
</script>

LLM 提示词模板(可直接用)

  • 目标:生成「Vue 3 + Element Plus」的可复用表格组件与列表页骨架。
  • 约束:
    • props:columnsdataloadingpagination
    • emits:update:paginationrow-clickselection-changerefresh
    • slots:toolbarcell-[columnKey]
    • 使用组合式 API 与 TypeScript,组件不写死接口调用。
  • 输出:组件源码、使用示例(列表页),以及最小 main.ts 接入说明。

示例提示词:

复制代码
请用 Vue 3 + Element Plus 写一个通用表格组件 TablePro.vue,满足上述 props/emits/slots 约束,并给出一个在列表页中使用它的示例。列表页包含搜索区域(关键字)、分页、刷新按钮,不写死接口,仅演示事件流与数据拼装。

实践路径与常见坑

  • 路线:先定义后端接口契约 → 生成组件骨架 → 接好 Axios → 页面拼装。
  • 常见坑:
    • Vue 3 用 Element Plus(非 Element UI)。
    • 全局样式污染,尽量使用局部样式或 CSS 变量。
    • 表格数据量大时优先服务端分页,需要时做行虚拟化。
    • 表单复杂时拆分子组件,避免巨型组件。
相关推荐
charlie1145141913 小时前
理解C++20的革命特性——协程引用之——利用协程做一个迷你的Echo Server
网络·学习·socket·c++20·协程·epoll·raii
柯南二号3 小时前
【大前端】Vue 和 React 的区别详解 —— 两大前端框架深度对比
前端·vue.js·前端框架
Yupureki3 小时前
从零开始的C++学习生活 4:类和对象(下)
c语言·数据结构·c++·学习
MAR-Sky3 小时前
单片机学习中的一些简单总结
单片机·嵌入式硬件·学习
Dream_言十3 小时前
光全息|OAM-旋转双维度复用全息
神经网络·学习·dnn·论文笔记
IT_陈寒3 小时前
「Redis性能翻倍的5个核心优化策略:从数据结构选择到持久化配置全解析」
前端·人工智能·后端
weixin_446938873 小时前
uniapp vue-i18n如何使用
前端·vue.js·uni-app
知识分享小能手4 小时前
微信小程序入门学习教程,从入门到精通,WXS语法详解(10)
前端·javascript·学习·微信小程序·小程序·vue·团队开发
excel4 小时前
Vue 组件与插件的区别详解
前端