mi-assessment/server/MiAssessment/src/MiAssessment.Admin/admin-web
2026-03-19 06:52:57 +08:00
..
src 21 2026-03-19 06:52:57 +08:00
.gitignore 前端 2026-02-04 10:04:50 +08:00
Dockerfile 2121 2026-02-03 19:17:48 +08:00
index.html 清楚历史文件 2026-02-04 18:57:04 +08:00
nginx.conf 2121 2026-02-03 19:17:48 +08:00
package-lock.json 1534 2026-02-21 23:42:15 +08:00
package.json 1534 2026-02-21 23:42:15 +08:00
README.md 2121 2026-02-03 19:17:48 +08:00
tsconfig.json 2121 2026-02-03 19:17:48 +08:00
tsconfig.tsbuildinfo 前端 2026-02-04 10:04:50 +08:00
vite.config.ts 21 2026-03-17 10:45:51 +08:00

后台管理系统前端开发文档

基于 Vue 3 + Element Plus + TypeScript 的后台管理系统前端模板。

技术栈

  • Vue 3.5 + Composition API
  • TypeScript 5.6
  • Element Plus 2.9
  • Pinia 状态管理
  • Vue Router 4
  • Vite 6 构建工具

目录结构

src/
├── api/                    # API 接口
│   ├── auth.ts             # 认证接口
│   ├── adminUser.ts        # 管理员接口
│   ├── role.ts             # 角色接口
│   ├── menu.ts             # 菜单接口
│   ├── department.ts       # 部门接口
│   ├── permission.ts       # 权限接口
│   ├── dict.ts             # 字典接口
│   ├── operationLog.ts     # 操作日志接口
│   └── upload.ts           # 上传接口
├── components/             # 公共组件
│   ├── ImageUpload/        # 图片上传组件
│   ├── DictSelect/         # 字典下拉组件
│   ├── DictRadio/          # 字典单选组件
│   └── DictCheckbox/       # 字典多选组件
├── directives/             # 自定义指令
│   └── permission.ts       # 权限指令
├── layout/                 # 布局组件
├── router/                 # 路由配置
├── store/                  # 状态管理
│   └── modules/
│       ├── user.ts         # 用户状态
│       ├── permission.ts   # 权限状态
│       └── theme.ts        # 主题状态
├── styles/                 # 全局样式
├── utils/                  # 工具函数
│   ├── request.ts          # 请求封装
│   ├── auth.ts             # Token 管理
│   └── format.ts           # 格式化工具
└── views/                  # 页面
    ├── dashboard/          # 首页
    ├── login/              # 登录
    ├── password/           # 修改密码
    ├── profile/            # 个人中心
    ├── error/              # 错误页
    └── system/             # 系统管理
        ├── user/           # 管理员管理
        ├── role/           # 角色管理
        ├── menu/           # 菜单管理
        ├── department/     # 部门管理
        ├── permission/     # 权限管理
        ├── dict/           # 字典管理
        └── log/            # 操作日志

快速开始

# 安装依赖
npm install

# 开发模式
npm run dev

# 构建生产版本
npm run build

开发指南

1. 新增页面

  1. src/views/ 下创建页面目录和 Vue 文件
  2. 在后台「菜单管理」中配置菜单,设置 component 为页面路径(不含 .vue 后缀)

示例:创建商品列表页面

src/views/business/goods/index.vue

菜单配置:

  • 路径:/business/goods/list
  • 组件:business/goods/index
  • 权限标识:goods:list

2. 新增 API

src/api/ 下创建接口文件:

// src/api/goods.ts
import request from '@/utils/request'

export interface Goods {
  id: number
  name: string
  price: number
  status: number
}

// 获取商品列表
export function getGoodsList(params: { page: number; pageSize: number }) {
  return request.get<Goods[]>('/api/admin/goods', { params })
}

// 创建商品
export function createGoods(data: Partial<Goods>) {
  return request.post<Goods>('/api/admin/goods', data)
}

// 更新商品
export function updateGoods(id: number, data: Partial<Goods>) {
  return request.put(`/api/admin/goods/${id}`, data)
}

// 删除商品
export function deleteGoods(id: number) {
  return request.delete(`/api/admin/goods/${id}`)
}

3. 使用字典组件

字典组件用于动态加载下拉选项,避免在代码中硬编码。

步骤:

  1. 在「字典管理」中创建字典类型,如 goods_status
  2. 添加字典数据项,如 上架(1)下架(0)
  3. 在页面中使用字典组件

DictSelect - 下拉选择

<template>
  <el-form-item label="状态">
    <DictSelect v-model="form.status" type="goods_status" />
  </el-form-item>
</template>

Props:

属性 类型 默认值 说明
type string - 字典类型编码(必填)
placeholder string '请选择' 占位文本
disabled boolean false 是否禁用
clearable boolean true 是否可清空
filterable boolean false 是否可搜索

DictRadio - 单选

<template>
  <el-form-item label="状态">
    <DictRadio v-model="form.status" type="goods_status" />
    <!-- 按钮样式 -->
    <DictRadio v-model="form.status" type="goods_status" button />
  </el-form-item>
</template>

DictCheckbox - 多选

<template>
  <el-form-item label="标签">
    <DictCheckbox v-model="form.tags" type="goods_tags" />
    <!-- 按钮样式 -->
    <DictCheckbox v-model="form.tags" type="goods_tags" button />
  </el-form-item>
</template>

4. 权限控制

按钮级权限指令

使用 v-permission 指令控制按钮显示:

<template>
  <el-button v-permission="'goods:create'" type="primary">新增</el-button>
  <el-button v-permission="'goods:edit'" type="warning">编辑</el-button>
  <el-button v-permission="'goods:delete'" type="danger">删除</el-button>
  
  <!-- 多个权限(满足其一即可) -->
  <el-button v-permission="['goods:edit', 'goods:delete']">操作</el-button>
</template>

代码中判断权限

import { usePermissionStore } from '@/store/modules/permission'

const permissionStore = usePermissionStore()

// 判断是否有权限
if (permissionStore.hasPermission('goods:create')) {
  // 有权限
}

// 判断是否有任一权限
if (permissionStore.hasAnyPermission(['goods:edit', 'goods:delete'])) {
  // 有权限
}

5. 图片上传

<template>
  <el-form-item label="商品图片">
    <!-- 单图上传 -->
    <ImageUpload v-model="form.image" />
    
    <!-- 多图上传 -->
    <ImageUpload v-model="form.images" :limit="5" multiple />
  </el-form-item>
</template>

Props:

属性 类型 默认值 说明
limit number 1 最大上传数量
multiple boolean false 是否多选
accept string 'image/*' 接受的文件类型
maxSize number 5 最大文件大小(MB)

6. 请求封装

src/utils/request.ts 已封装:

  • 自动携带 Token
  • 401 自动刷新 Token
  • 统一错误处理
  • 响应数据解构
import request from '@/utils/request'

// GET 请求
const res = await request.get('/api/admin/goods', { params: { page: 1 } })

// POST 请求
const res = await request.post('/api/admin/goods', { name: '商品1' })

// PUT 请求
const res = await request.put('/api/admin/goods/1', { name: '商品2' })

// DELETE 请求
const res = await request.delete('/api/admin/goods/1')

7. 页面模板

标准 CRUD 页面模板:

<template>
  <div class="page-container">
    <!-- 搜索区域 -->
    <el-card class="search-card">
      <el-form :model="queryParams" inline>
        <el-form-item label="名称">
          <el-input v-model="queryParams.keyword" placeholder="请输入" clearable />
        </el-form-item>
        <el-form-item label="状态">
          <DictSelect v-model="queryParams.status" type="goods_status" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">搜索</el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 表格区域 -->
    <el-card>
      <template #header>
        <el-button v-permission="'goods:create'" type="primary" @click="handleAdd">
          新增
        </el-button>
      </template>

      <el-table v-loading="loading" :data="tableData">
        <el-table-column prop="name" label="名称" />
        <el-table-column prop="status" label="状态">
          <template #default="{ row }">
            <el-tag :type="row.status === 1 ? 'success' : 'danger'">
              {{ row.status === 1 ? '启用' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200">
          <template #default="{ row }">
            <el-button v-permission="'goods:edit'" type="primary" link @click="handleEdit(row)">
              编辑
            </el-button>
            <el-button v-permission="'goods:delete'" type="danger" link @click="handleDelete(row)">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页 -->
      <el-pagination
        v-model:current-page="queryParams.page"
        v-model:page-size="queryParams.pageSize"
        :total="total"
        layout="total, sizes, prev, pager, next"
        @change="loadData"
      />
    </el-card>

    <!-- 表单弹窗 -->
    <el-dialog v-model="dialogVisible" :title="form.id ? '编辑' : '新增'" width="500px">
      <el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="名称" prop="name">
          <el-input v-model="form.name" placeholder="请输入名称" />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <DictRadio v-model="form.status" type="goods_status" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { getGoodsList, createGoods, updateGoods, deleteGoods, type Goods } from '@/api/goods'

// 查询参数
const queryParams = reactive({
  keyword: '',
  status: '',
  page: 1,
  pageSize: 20
})

// 表格数据
const loading = ref(false)
const tableData = ref<Goods[]>([])
const total = ref(0)

// 加载数据
async function loadData() {
  loading.value = true
  try {
    const res = await getGoodsList(queryParams)
    tableData.value = res.data?.items || []
    total.value = res.data?.total || 0
  } finally {
    loading.value = false
  }
}

function handleSearch() {
  queryParams.page = 1
  loadData()
}

function handleReset() {
  Object.assign(queryParams, { keyword: '', status: '', page: 1 })
  loadData()
}

// 表单
const dialogVisible = ref(false)
const submitting = ref(false)
const formRef = ref<FormInstance>()
const form = reactive({ id: 0, name: '', status: 1 })
const rules: FormRules = {
  name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
}

function handleAdd() {
  Object.assign(form, { id: 0, name: '', status: 1 })
  dialogVisible.value = true
}

function handleEdit(row: Goods) {
  Object.assign(form, row)
  dialogVisible.value = true
}

async function handleSubmit() {
  await formRef.value?.validate()
  submitting.value = true
  try {
    if (form.id) {
      await updateGoods(form.id, form)
      ElMessage.success('更新成功')
    } else {
      await createGoods(form)
      ElMessage.success('创建成功')
    }
    dialogVisible.value = false
    loadData()
  } finally {
    submitting.value = false
  }
}

async function handleDelete(row: Goods) {
  await ElMessageBox.confirm('确定删除吗?', '提示', { type: 'warning' })
  await deleteGoods(row.id)
  ElMessage.success('删除成功')
  loadData()
}

onMounted(() => loadData())
</script>

<style scoped>
.page-container {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
</style>

构建部署

# 构建
npm run build

# 输出目录:../wwwroot
# 构建产物会自动输出到后端的 wwwroot 目录

注意事项

  1. 菜单配置的 component 路径对应 src/views/ 下的文件路径
  2. 权限标识建议使用 模块:操作 格式,如 goods:listgoods:create
  3. 字典类型编码建议使用小写字母和下划线,如 order_status
  4. API 接口统一使用 /api/admin/ 前缀