Files
xlxumu/docs/development/管理后台开发文档.md

38 KiB
Raw Permalink Blame History

管理后台开发文档

版本历史

版本 日期 作者 变更说明
1.0 2024-01-20 前端开发团队 初始版本

1. 项目概述

1.1 项目介绍

畜牧养殖管理平台管理后台,为管理员提供全面的系统管理、用户管理、数据统计和业务监控功能。基于现代化的前端技术栈构建,提供高效、易用的管理界面。

1.2 技术栈

  • 开发框架: Vue 3 + TypeScript
  • 构建工具: Vite
  • UI框架: Element Plus
  • 状态管理: Pinia
  • 路由管理: Vue Router 4
  • 网络请求: Axios
  • 图表库: ECharts
  • 表格组件: Element Plus Table
  • 表单验证: Element Plus Form + Async Validator
  • 代码规范: ESLint + Prettier
  • CSS预处理: Sass/SCSS

1.3 项目结构

admin-system/
├── public/                 # 静态资源
├── src/
│   ├── api/               # API接口
│   ├── assets/            # 资源文件
│   ├── components/        # 通用组件
│   ├── composables/       # 组合式函数
│   ├── layouts/           # 布局组件
│   ├── pages/             # 页面组件
│   │   ├── dashboard/     # 仪表盘
│   │   ├── user/          # 用户管理
│   │   ├── farm/          # 养殖场管理
│   │   ├── animal/        # 动物管理
│   │   ├── order/         # 订单管理
│   │   ├── finance/       # 财务管理
│   │   ├── system/        # 系统管理
│   │   └── statistics/    # 数据统计
│   ├── router/            # 路由配置
│   ├── stores/            # 状态管理
│   ├── styles/            # 样式文件
│   ├── utils/             # 工具函数
│   ├── types/             # TypeScript类型定义
│   └── main.ts            # 入口文件
├── tests/                 # 测试文件
├── docs/                  # 文档
├── .env.development       # 开发环境配置
├── .env.production        # 生产环境配置
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md

2. 开发环境搭建

2.1 环境要求

  • Node.js >= 16.0.0
  • npm >= 8.0.0 或 yarn >= 1.22.0
  • VSCode + Volar插件
  • Chrome浏览器推荐

2.2 安装步骤

# 1. 克隆项目
git clone <repository-url>
cd admin-system

# 2. 安装依赖
npm install
# 或
yarn install

# 3. 配置环境变量
cp .env.example .env.development
cp .env.example .env.production
# 编辑环境变量文件

# 4. 启动开发服务器
npm run dev
# 或
yarn dev

# 5. 构建生产版本
npm run build
# 或
yarn build

2.3 开发工具配置

  • 编辑器: VSCode + Volar + TypeScript Vue Plugin
  • 代码规范: ESLint + Prettier
  • Git钩子: Husky + lint-staged
  • 调试工具: Vue DevTools

3. 开发计划

3.1 第一阶段基础框架搭建1周

3.1.1 项目初始化

任务: 搭建基础项目架构 负责人: 前端架构师 工期: 2天 详细任务:

  • 创建Vite + Vue3 + TypeScript项目
  • 配置ESLint、Prettier代码规范
  • 配置路由和状态管理
  • 配置环境变量和构建配置
  • 创建基础的目录结构
  • 配置Git工作流和提交规范

3.1.2 基础布局和组件

任务: 开发基础布局和通用组件 负责人: 前端工程师A 工期: 3天 详细任务:

  • 开发主布局组件(侧边栏、顶栏、内容区)
  • 开发面包屑导航组件
  • 开发页面标题组件
  • 开发加载状态组件
  • 开发空状态组件
  • 开发确认对话框组件

3.1.3 权限系统和路由守卫

任务: 实现权限控制和路由守卫 负责人: 前端工程师B 工期: 2天 详细任务:

  • 实现登录认证机制
  • 实现路由权限控制
  • 实现按钮级权限控制
  • 实现Token自动刷新
  • 实现登录状态持久化
  • 实现权限变更实时更新

3.2 第二阶段核心功能模块开发4-5周

3.2.1 用户认证和个人中心

任务: 实现用户认证和个人信息管理 负责人: 前端工程师A 工期: 3天 详细任务:

  • 登录页面开发
  • 个人信息页面
  • 修改密码页面
  • 头像上传功能
  • 登录日志查看
  • 安全设置页面

页面列表:

pages/auth/login.vue           # 登录页面
pages/profile/index.vue        # 个人信息
pages/profile/password.vue     # 修改密码
pages/profile/security.vue     # 安全设置
pages/profile/logs.vue         # 登录日志

3.2.2 仪表盘和数据概览

任务: 实现数据仪表盘和统计概览 负责人: 前端工程师B 工期: 4天 详细任务:

  • 数据概览卡片组件
  • 实时数据图表展示
  • 业务趋势分析图表
  • 快捷操作入口
  • 系统通知和消息
  • 数据刷新和实时更新

页面列表:

pages/dashboard/index.vue      # 仪表盘首页
pages/dashboard/analytics.vue # 数据分析
pages/dashboard/reports.vue   # 报表中心

3.2.3 用户管理模块

任务: 实现用户管理功能 负责人: 前端工程师C 工期: 5天 详细任务:

  • 用户列表页面(搜索、筛选、分页)
  • 用户详情页面
  • 添加用户页面
  • 编辑用户页面
  • 用户状态管理(启用/禁用)
  • 批量操作功能
  • 用户导入导出功能

页面列表:

pages/user/list.vue            # 用户列表
pages/user/detail.vue          # 用户详情
pages/user/add.vue             # 添加用户
pages/user/edit.vue            # 编辑用户
pages/user/import.vue          # 用户导入

3.2.4 角色权限管理模块

任务: 实现角色和权限管理 负责人: 前端工程师D 工期: 4天 详细任务:

  • 角色列表页面
  • 角色详情和权限配置
  • 添加/编辑角色页面
  • 权限树形选择组件
  • 用户角色分配页面
  • 权限变更日志

页面列表:

pages/role/list.vue            # 角色列表
pages/role/detail.vue          # 角色详情
pages/role/add.vue             # 添加角色
pages/role/edit.vue            # 编辑角色
pages/role/permissions.vue     # 权限配置

3.2.5 养殖场管理模块

任务: 实现养殖场管理功能 负责人: 前端工程师E 工期: 6天 详细任务:

  • 养殖场列表页面
  • 养殖场详情页面
  • 添加/编辑养殖场页面
  • 养殖场审核功能
  • 养殖场统计分析
  • 地图展示功能
  • 养殖场认证管理

页面列表:

pages/farm/list.vue            # 养殖场列表
pages/farm/detail.vue          # 养殖场详情
pages/farm/add.vue             # 添加养殖场
pages/farm/edit.vue            # 编辑养殖场
pages/farm/audit.vue           # 养殖场审核
pages/farm/statistics.vue      # 养殖场统计
pages/farm/map.vue             # 地图展示

3.2.6 动物管理模块

任务: 实现动物档案管理功能 负责人: 前端工程师F 工期: 6天 详细任务:

  • 动物列表页面
  • 动物详情页面
  • 动物健康记录管理
  • 动物生长记录管理
  • 动物统计分析
  • 动物批量操作
  • 动物数据导出

页面列表:

pages/animal/list.vue          # 动物列表
pages/animal/detail.vue        # 动物详情
pages/animal/health.vue        # 健康记录
pages/animal/growth.vue        # 生长记录
pages/animal/statistics.vue    # 动物统计
pages/animal/batch.vue         # 批量操作

3.3 第三阶段交易和财务管理2-3周

3.3.1 订单管理模块

任务: 实现订单管理功能 负责人: 前端工程师A 工期: 5天 详细任务:

  • 订单列表页面
  • 订单详情页面
  • 订单状态管理
  • 订单审核功能
  • 订单统计分析
  • 订单导出功能
  • 退款处理页面

页面列表:

pages/order/list.vue           # 订单列表
pages/order/detail.vue         # 订单详情
pages/order/audit.vue          # 订单审核
pages/order/refund.vue         # 退款处理
pages/order/statistics.vue     # 订单统计

3.3.2 财务管理模块

任务: 实现财务管理功能 负责人: 前端工程师B 工期: 6天 详细任务:

  • 财务概览页面
  • 收入统计页面
  • 支出统计页面
  • 资金流水页面
  • 财务报表生成
  • 对账功能
  • 财务数据导出

页面列表:

pages/finance/overview.vue     # 财务概览
pages/finance/income.vue       # 收入统计
pages/finance/expense.vue      # 支出统计
pages/finance/flow.vue         # 资金流水
pages/finance/reports.vue      # 财务报表
pages/finance/reconcile.vue    # 对账管理

3.3.3 支付管理模块

任务: 实现支付管理功能 负责人: 前端工程师C 工期: 4天 详细任务:

  • 支付记录列表
  • 支付详情页面
  • 支付方式配置
  • 支付异常处理
  • 支付统计分析
  • 支付对账功能

页面列表:

pages/payment/list.vue         # 支付记录
pages/payment/detail.vue       # 支付详情
pages/payment/config.vue       # 支付配置
pages/payment/exception.vue    # 异常处理
pages/payment/statistics.vue   # 支付统计

3.4 第四阶段系统管理和配置1-2周

3.4.1 系统配置模块

任务: 实现系统配置管理 负责人: 前端工程师D 工期: 4天 详细任务:

  • 系统参数配置
  • 字典数据管理
  • 系统公告管理
  • 邮件模板配置
  • 短信模板配置
  • 系统备份恢复

页面列表:

pages/system/config.vue        # 系统配置
pages/system/dict.vue          # 字典管理
pages/system/notice.vue        # 公告管理
pages/system/template.vue      # 模板管理
pages/system/backup.vue        # 备份恢复

3.4.2 日志管理模块

任务: 实现系统日志管理 负责人: 前端工程师E 工期: 3天 详细任务:

  • 操作日志查看
  • 登录日志查看
  • 系统错误日志
  • 日志搜索和筛选
  • 日志导出功能
  • 日志清理配置

页面列表:

pages/log/operation.vue        # 操作日志
pages/log/login.vue            # 登录日志
pages/log/error.vue            # 错误日志
pages/log/search.vue           # 日志搜索

3.4.3 监控管理模块

任务: 实现系统监控功能 负责人: 前端工程师F 工期: 3天 详细任务:

  • 系统性能监控
  • 服务状态监控
  • 数据库监控
  • 接口调用监控
  • 告警规则配置
  • 监控报表生成

页面列表:

pages/monitor/system.vue       # 系统监控
pages/monitor/service.vue      # 服务监控
pages/monitor/database.vue     # 数据库监控
pages/monitor/api.vue          # 接口监控
pages/monitor/alert.vue        # 告警配置

3.5 第五阶段数据统计和报表1周

3.5.1 数据统计模块

任务: 实现数据统计分析功能 负责人: 前端工程师A 工期: 4天 详细任务:

  • 用户统计分析
  • 业务数据统计
  • 交易数据分析
  • 趋势分析图表
  • 自定义统计报表
  • 数据对比分析

页面列表:

pages/statistics/user.vue      # 用户统计
pages/statistics/business.vue  # 业务统计
pages/statistics/trade.vue     # 交易统计
pages/statistics/trend.vue     # 趋势分析
pages/statistics/custom.vue    # 自定义报表

3.5.2 报表管理模块

任务: 实现报表生成和管理 负责人: 前端工程师B 工期: 3天 详细任务:

  • 报表模板管理
  • 报表生成功能
  • 报表预览和下载
  • 定时报表配置
  • 报表分享功能
  • 报表权限控制

页面列表:

pages/report/template.vue      # 报表模板
pages/report/generate.vue      # 报表生成
pages/report/preview.vue       # 报表预览
pages/report/schedule.vue      # 定时报表
pages/report/share.vue         # 报表分享

3.6 第六阶段测试和优化1周

3.6.1 功能测试

任务: 全面功能测试 负责人: 测试工程师 工期: 3天 详细任务:

  • 页面功能测试
  • 权限控制测试
  • 数据操作测试
  • 兼容性测试
  • 性能测试

3.6.2 性能优化

任务: 性能优化和体验提升 负责人: 前端架构师 工期: 2天 详细任务:

  • 页面加载速度优化
  • 大数据量处理优化
  • 内存使用优化
  • 网络请求优化
  • 用户体验优化

4. 开发规范

4.1 代码规范

  • 命名规范: 使用驼峰命名法组件名使用PascalCase
  • 文件命名: 页面文件使用kebab-case组件文件使用PascalCase
  • TypeScript: 严格的类型定义和检查
  • 注释规范: 使用JSDoc格式注释
  • 代码格式: 使用Prettier自动格式化

4.2 组件开发规范

  • 组件设计: 遵循单一职责原则
  • 属性定义: 明确的属性类型和默认值
  • 事件处理: 统一的事件命名和处理方式
  • 样式隔离: 使用scoped样式避免样式冲突
  • 文档说明: 完整的组件使用文档

4.3 API接口规范

  • 接口封装: 统一的接口封装和错误处理
  • 类型定义: 完整的请求和响应类型定义
  • 缓存策略: 合理的接口缓存策略
  • 错误处理: 统一的错误处理和用户提示
  • 加载状态: 明确的加载状态管理

4.4 状态管理规范

  • Store设计: 合理的Store结构设计
  • 模块划分: 按功能模块划分Store
  • 异步处理: 统一的异步操作处理
  • 数据流: 清晰的数据流向和更新机制
  • 持久化: 关键状态的持久化存储

5. 技术实现

5.1 项目配置

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'

export default defineConfig({
  plugins: [vue()],
  
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@/components': resolve(__dirname, 'src/components'),
      '@/utils': resolve(__dirname, 'src/utils'),
      '@/api': resolve(__dirname, 'src/api'),
      '@/stores': resolve(__dirname, 'src/stores'),
      '@/types': resolve(__dirname, 'src/types')
    }
  },
  
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  },
  
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          element: ['element-plus'],
          echarts: ['echarts']
        }
      }
    }
  }
})

5.2 路由配置

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/stores/auth'

const routes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/pages/auth/login.vue'),
    meta: { requiresAuth: false }
  },
  {
    path: '/',
    component: () => import('@/layouts/MainLayout.vue'),
    meta: { requiresAuth: true },
    children: [
      {
        path: '',
        name: 'Dashboard',
        component: () => import('@/pages/dashboard/index.vue'),
        meta: { title: '仪表盘', icon: 'dashboard' }
      },
      {
        path: 'users',
        name: 'UserManagement',
        component: () => import('@/pages/user/list.vue'),
        meta: { 
          title: '用户管理', 
          icon: 'user',
          permissions: ['user:read']
        }
      },
      {
        path: 'farms',
        name: 'FarmManagement',
        component: () => import('@/pages/farm/list.vue'),
        meta: { 
          title: '养殖场管理', 
          icon: 'farm',
          permissions: ['farm:read']
        }
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 路由守卫
router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore()
  
  if (to.meta.requiresAuth && !authStore.isAuthenticated) {
    next('/login')
    return
  }
  
  if (to.meta.permissions) {
    const hasPermission = authStore.hasPermissions(to.meta.permissions)
    if (!hasPermission) {
      next('/403')
      return
    }
  }
  
  next()
})

export default router

5.3 状态管理

// stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { User, LoginCredentials } from '@/types/auth'
import { authApi } from '@/api/auth'

export const useAuthStore = defineStore('auth', () => {
  const user = ref<User | null>(null)
  const token = ref<string | null>(localStorage.getItem('token'))
  const permissions = ref<string[]>([])
  
  const isAuthenticated = computed(() => !!token.value && !!user.value)
  
  const login = async (credentials: LoginCredentials) => {
    try {
      const response = await authApi.login(credentials)
      const { user: userData, token: userToken, permissions: userPermissions } = response.data
      
      user.value = userData
      token.value = userToken
      permissions.value = userPermissions
      
      localStorage.setItem('token', userToken)
      localStorage.setItem('user', JSON.stringify(userData))
      
      return response
    } catch (error) {
      throw error
    }
  }
  
  const logout = async () => {
    try {
      await authApi.logout()
    } catch (error) {
      console.error('Logout error:', error)
    } finally {
      user.value = null
      token.value = null
      permissions.value = []
      
      localStorage.removeItem('token')
      localStorage.removeItem('user')
    }
  }
  
  const hasPermissions = (requiredPermissions: string[]) => {
    return requiredPermissions.every(permission => 
      permissions.value.includes(permission)
    )
  }
  
  const refreshToken = async () => {
    try {
      const response = await authApi.refreshToken()
      token.value = response.data.token
      localStorage.setItem('token', response.data.token)
      return response
    } catch (error) {
      await logout()
      throw error
    }
  }
  
  // 初始化用户信息
  const initAuth = async () => {
    const savedUser = localStorage.getItem('user')
    if (savedUser && token.value) {
      try {
        user.value = JSON.parse(savedUser)
        const response = await authApi.getUserInfo()
        user.value = response.data.user
        permissions.value = response.data.permissions
      } catch (error) {
        await logout()
      }
    }
  }
  
  return {
    user,
    token,
    permissions,
    isAuthenticated,
    login,
    logout,
    hasPermissions,
    refreshToken,
    initAuth
  }
})

5.4 API封装

// utils/request.ts
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
import { useAuthStore } from '@/stores/auth'

class HttpClient {
  private instance: AxiosInstance
  
  constructor() {
    this.instance = axios.create({
      baseURL: import.meta.env.VITE_API_BASE_URL,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json'
      }
    })
    
    this.setupInterceptors()
  }
  
  private setupInterceptors() {
    // 请求拦截器
    this.instance.interceptors.request.use(
      (config) => {
        const authStore = useAuthStore()
        if (authStore.token) {
          config.headers.Authorization = `Bearer ${authStore.token}`
        }
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )
    
    // 响应拦截器
    this.instance.interceptors.response.use(
      (response: AxiosResponse) => {
        const { code, message, data } = response.data
        
        if (code === 200) {
          return { data, message }
        } else {
          ElMessage.error(message || '请求失败')
          return Promise.reject(new Error(message))
        }
      },
      async (error) => {
        const { response } = error
        
        if (response?.status === 401) {
          const authStore = useAuthStore()
          try {
            await authStore.refreshToken()
            // 重新发起原请求
            return this.instance.request(error.config)
          } catch (refreshError) {
            authStore.logout()
            return Promise.reject(refreshError)
          }
        }
        
        if (response?.status === 403) {
          ElMessage.error('没有权限访问')
        } else if (response?.status >= 500) {
          ElMessage.error('服务器错误')
        } else {
          ElMessage.error(response?.data?.message || '请求失败')
        }
        
        return Promise.reject(error)
      }
    )
  }
  
  public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.get(url, config)
  }
  
  public post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.post(url, data, config)
  }
  
  public put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.put(url, data, config)
  }
  
  public delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
    return this.instance.delete(url, config)
  }
}

export const httpClient = new HttpClient()

5.5 通用组件示例

<!-- components/DataTable.vue -->
<template>
  <div class="data-table">
    <!-- 搜索和操作栏 -->
    <div class="table-header">
      <div class="search-section">
        <el-input
          v-model="searchKeyword"
          placeholder="请输入搜索关键词"
          clearable
          @keyup.enter="handleSearch"
          style="width: 300px"
        >
          <template #append>
            <el-button @click="handleSearch" :icon="Search" />
          </template>
        </el-input>
        
        <slot name="filters" :search="handleSearch" />
      </div>
      
      <div class="action-section">
        <slot name="actions" />
      </div>
    </div>
    
    <!-- 表格 -->
    <el-table
      :data="tableData"
      :loading="loading"
      v-bind="$attrs"
      @selection-change="handleSelectionChange"
      @sort-change="handleSortChange"
    >
      <el-table-column
        v-if="showSelection"
        type="selection"
        width="55"
        align="center"
      />
      
      <slot />
      
      <el-table-column
        v-if="showActions"
        label="操作"
        :width="actionWidth"
        align="center"
        fixed="right"
      >
        <template #default="scope">
          <slot name="actions-column" :row="scope.row" :index="scope.$index" />
        </template>
      </el-table-column>
    </el-table>
    
    <!-- 分页 -->
    <div class="table-footer" v-if="showPagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :total="total"
        :page-sizes="pageSizes"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { Search } from '@element-plus/icons-vue'

interface Props {
  data?: any[]
  loading?: boolean
  showSelection?: boolean
  showActions?: boolean
  actionWidth?: number
  showPagination?: boolean
  total?: number
  pageSizes?: number[]
}

const props = withDefaults(defineProps<Props>(), {
  data: () => [],
  loading: false,
  showSelection: false,
  showActions: true,
  actionWidth: 200,
  showPagination: true,
  total: 0,
  pageSizes: () => [10, 20, 50, 100]
})

const emit = defineEmits<{
  search: [keyword: string, filters?: any]
  selectionChange: [selection: any[]]
  sortChange: [sort: { prop: string; order: string }]
  pageChange: [page: number, size: number]
}>()

const searchKeyword = ref('')
const currentPage = ref(1)
const pageSize = ref(20)
const tableData = ref(props.data)

watch(() => props.data, (newData) => {
  tableData.value = newData
})

const handleSearch = () => {
  currentPage.value = 1
  emit('search', searchKeyword.value)
}

const handleSelectionChange = (selection: any[]) => {
  emit('selectionChange', selection)
}

const handleSortChange = (sort: { prop: string; order: string }) => {
  emit('sortChange', sort)
}

const handleSizeChange = (size: number) => {
  pageSize.value = size
  emit('pageChange', currentPage.value, size)
}

const handleCurrentChange = (page: number) => {
  currentPage.value = page
  emit('pageChange', page, pageSize.value)
}
</script>

<style lang="scss" scoped>
.data-table {
  .table-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    
    .search-section {
      display: flex;
      align-items: center;
      gap: 16px;
    }
    
    .action-section {
      display: flex;
      gap: 8px;
    }
  }
  
  .table-footer {
    display: flex;
    justify-content: flex-end;
    margin-top: 16px;
  }
}
</style>

5.6 页面开发示例

<!-- pages/user/list.vue -->
<template>
  <div class="user-list-page">
    <PageHeader title="用户管理" />
    
    <el-card>
      <DataTable
        :data="userList"
        :loading="loading"
        :total="total"
        show-selection
        @search="handleSearch"
        @selection-change="handleSelectionChange"
        @page-change="handlePageChange"
      >
        <template #filters="{ search }">
          <el-select
            v-model="filters.status"
            placeholder="用户状态"
            clearable
            @change="search"
            style="width: 120px"
          >
            <el-option label="启用" value="active" />
            <el-option label="禁用" value="inactive" />
          </el-select>
          
          <el-select
            v-model="filters.role"
            placeholder="用户角色"
            clearable
            @change="search"
            style="width: 120px"
          >
            <el-option
              v-for="role in roleOptions"
              :key="role.id"
              :label="role.name"
              :value="role.id"
            />
          </el-select>
        </template>
        
        <template #actions>
          <el-button
            type="primary"
            @click="handleAdd"
            v-permission="['user:create']"
          >
            添加用户
          </el-button>
          
          <el-button
            type="danger"
            :disabled="!selectedUsers.length"
            @click="handleBatchDelete"
            v-permission="['user:delete']"
          >
            批量删除
          </el-button>
          
          <el-button @click="handleExport">
            导出数据
          </el-button>
        </template>
        
        <el-table-column prop="id" label="ID" width="80" />
        <el-table-column prop="username" label="用户名" />
        <el-table-column prop="email" label="邮箱" />
        <el-table-column prop="phone" label="手机号" />
        <el-table-column prop="role_name" label="角色" />
        <el-table-column prop="status" label="状态" width="100">
          <template #default="{ row }">
            <el-tag :type="row.status === 'active' ? 'success' : 'danger'">
              {{ row.status === 'active' ? '启用' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="created_at" label="创建时间" width="180" />
        
        <template #actions-column="{ row }">
          <el-button
            type="primary"
            size="small"
            @click="handleEdit(row)"
            v-permission="['user:update']"
          >
            编辑
          </el-button>
          
          <el-button
            :type="row.status === 'active' ? 'warning' : 'success'"
            size="small"
            @click="handleToggleStatus(row)"
            v-permission="['user:update']"
          >
            {{ row.status === 'active' ? '禁用' : '启用' }}
          </el-button>
          
          <el-button
            type="danger"
            size="small"
            @click="handleDelete(row)"
            v-permission="['user:delete']"
          >
            删除
          </el-button>
        </template>
      </DataTable>
    </el-card>
    
    <!-- 用户表单对话框 -->
    <UserFormDialog
      v-model="showUserDialog"
      :user="currentUser"
      @success="handleFormSuccess"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { userApi } from '@/api/user'
import { roleApi } from '@/api/role'
import type { User, UserListParams } from '@/types/user'
import DataTable from '@/components/DataTable.vue'
import PageHeader from '@/components/PageHeader.vue'
import UserFormDialog from './components/UserFormDialog.vue'

const loading = ref(false)
const userList = ref<User[]>([])
const total = ref(0)
const selectedUsers = ref<User[]>([])
const roleOptions = ref([])
const showUserDialog = ref(false)
const currentUser = ref<User | null>(null)

const filters = reactive({
  status: '',
  role: ''
})

const pagination = reactive({
  page: 1,
  size: 20
})

const searchParams = reactive<UserListParams>({
  keyword: '',
  ...filters,
  ...pagination
})

onMounted(() => {
  loadUserList()
  loadRoleOptions()
})

const loadUserList = async () => {
  loading.value = true
  try {
    const response = await userApi.getUserList(searchParams)
    userList.value = response.data.list
    total.value = response.data.total
  } catch (error) {
    ElMessage.error('加载用户列表失败')
  } finally {
    loading.value = false
  }
}

const loadRoleOptions = async () => {
  try {
    const response = await roleApi.getRoleList({ page: 1, size: 100 })
    roleOptions.value = response.data.list
  } catch (error) {
    console.error('加载角色选项失败:', error)
  }
}

const handleSearch = (keyword: string) => {
  searchParams.keyword = keyword
  searchParams.status = filters.status
  searchParams.role = filters.role
  searchParams.page = 1
  loadUserList()
}

const handleSelectionChange = (selection: User[]) => {
  selectedUsers.value = selection
}

const handlePageChange = (page: number, size: number) => {
  searchParams.page = page
  searchParams.size = size
  loadUserList()
}

const handleAdd = () => {
  currentUser.value = null
  showUserDialog.value = true
}

const handleEdit = (user: User) => {
  currentUser.value = user
  showUserDialog.value = true
}

const handleDelete = async (user: User) => {
  try {
    await ElMessageBox.confirm(
      `确定要删除用户 "${user.username}" 吗?`,
      '确认删除',
      { type: 'warning' }
    )
    
    await userApi.deleteUser(user.id)
    ElMessage.success('删除成功')
    loadUserList()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}

const handleBatchDelete = async () => {
  try {
    await ElMessageBox.confirm(
      `确定要删除选中的 ${selectedUsers.value.length} 个用户吗?`,
      '确认批量删除',
      { type: 'warning' }
    )
    
    const userIds = selectedUsers.value.map(user => user.id)
    await userApi.batchDeleteUsers(userIds)
    ElMessage.success('批量删除成功')
    loadUserList()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('批量删除失败')
    }
  }
}

const handleToggleStatus = async (user: User) => {
  try {
    const newStatus = user.status === 'active' ? 'inactive' : 'active'
    await userApi.updateUserStatus(user.id, newStatus)
    ElMessage.success('状态更新成功')
    loadUserList()
  } catch (error) {
    ElMessage.error('状态更新失败')
  }
}

const handleExport = async () => {
  try {
    const response = await userApi.exportUsers(searchParams)
    // 处理文件下载
    const blob = new Blob([response.data])
    const url = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = `用户数据_${new Date().toISOString().slice(0, 10)}.xlsx`
    link.click()
    window.URL.revokeObjectURL(url)
    ElMessage.success('导出成功')
  } catch (error) {
    ElMessage.error('导出失败')
  }
}

const handleFormSuccess = () => {
  showUserDialog.value = false
  loadUserList()
}
</script>

<style lang="scss" scoped>
.user-list-page {
  padding: 24px;
}
</style>

6. 性能优化

6.1 构建优化

  • 代码分割: 按路由和功能模块分割代码
  • Tree Shaking: 移除未使用的代码
  • 压缩优化: 代码压缩和资源优化
  • 缓存策略: 合理的缓存策略配置
  • CDN加速: 静态资源CDN加速

6.2 运行时优化

  • 虚拟滚动: 大数据量列表使用虚拟滚动
  • 懒加载: 组件和图片懒加载
  • 防抖节流: 搜索和操作防抖节流
  • 内存管理: 避免内存泄漏
  • 组件缓存: 合理使用keep-alive

6.3 用户体验优化

  • 加载状态: 明确的加载状态提示
  • 骨架屏: 使用骨架屏提升感知性能
  • 错误边界: 错误边界和降级处理
  • 响应式设计: 适配不同屏幕尺寸
  • 无障碍支持: 支持键盘导航和屏幕阅读器

7. 测试策略

7.1 单元测试

// tests/components/DataTable.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import DataTable from '@/components/DataTable.vue'

describe('DataTable', () => {
  it('renders correctly with data', () => {
    const wrapper = mount(DataTable, {
      props: {
        data: [
          { id: 1, name: 'Test User', email: 'test@example.com' }
        ]
      }
    })
    
    expect(wrapper.find('.data-table').exists()).toBe(true)
    expect(wrapper.text()).toContain('Test User')
  })
  
  it('emits search event when search button clicked', async () => {
    const wrapper = mount(DataTable)
    
    await wrapper.find('.search-button').trigger('click')
    
    expect(wrapper.emitted('search')).toBeTruthy()
  })
})

7.2 集成测试

  • 页面测试: 完整页面功能测试
  • API测试: 接口集成测试
  • 权限测试: 权限控制测试
  • 流程测试: 完整业务流程测试
  • 兼容性测试: 浏览器兼容性测试

7.3 E2E测试

// tests/e2e/user-management.spec.ts
import { test, expect } from '@playwright/test'

test('user management flow', async ({ page }) => {
  // 登录
  await page.goto('/login')
  await page.fill('[data-testid="username"]', 'admin')
  await page.fill('[data-testid="password"]', 'password')
  await page.click('[data-testid="login-button"]')
  
  // 导航到用户管理页面
  await page.click('[data-testid="user-menu"]')
  await expect(page).toHaveURL('/users')
  
  // 添加用户
  await page.click('[data-testid="add-user-button"]')
  await page.fill('[data-testid="username-input"]', 'testuser')
  await page.fill('[data-testid="email-input"]', 'test@example.com')
  await page.click('[data-testid="save-button"]')
  
  // 验证用户已添加
  await expect(page.locator('text=testuser')).toBeVisible()
})

8. 部署配置

8.1 环境配置

# .env.production
VITE_API_BASE_URL=https://api.xlxumu.com
VITE_APP_TITLE=畜牧养殖管理平台
VITE_APP_VERSION=1.0.0

8.2 构建脚本

{
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "build:staging": "vue-tsc --noEmit && vite build --mode staging",
    "preview": "vite preview",
    "test": "vitest",
    "test:e2e": "playwright test",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
    "type-check": "vue-tsc --noEmit"
  }
}

8.3 Docker配置

# Dockerfile
FROM node:18-alpine as build-stage

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

9. 监控运维

9.1 性能监控

  • 页面性能: 监控页面加载时间和渲染性能
  • 接口性能: 监控API调用性能和成功率
  • 错误监控: 监控JavaScript错误和异常
  • 用户行为: 监控用户操作行为和路径
  • 资源监控: 监控静态资源加载情况

9.2 业务监控

  • 用户活跃: 监控管理员活跃度和使用情况
  • 功能使用: 监控各功能模块使用频率
  • 操作统计: 监控关键操作的执行情况
  • 数据质量: 监控数据的完整性和准确性
  • 系统健康: 监控系统整体健康状态

9.3 告警配置

  • 错误告警: 错误率超过阈值时告警
  • 性能告警: 性能指标异常时告警
  • 业务告警: 关键业务指标异常时告警
  • 实时通知: 通过邮件、短信等方式实时通知
  • 报表推送: 定期推送监控报表和分析

10. 总结

本开发文档详细规划了畜牧养殖管理平台管理后台的开发计划,包括:

10.1 开发亮点

  • 现代化技术栈: 使用Vue3 + TypeScript + Vite等现代化技术
  • 组件化开发: 完善的组件化开发体系和复用机制
  • 权限控制: 细粒度的权限控制和安全防护
  • 数据可视化: 丰富的图表和数据展示功能
  • 用户体验: 注重用户体验和操作效率

10.2 技术特色

  • TypeScript: 严格的类型检查和代码质量保证
  • 响应式设计: 适配不同屏幕尺寸和设备
  • 性能优化: 全面的性能优化和加载策略
  • 错误处理: 完善的错误处理和用户提示机制
  • 可维护性: 清晰的代码结构和文档说明

10.3 开发保障

  • 规范化: 完善的开发规范和代码标准
  • 自动化: 自动化测试、构建和部署流程
  • 质量控制: 严格的代码审查和测试要求
  • 团队协作: 明确的分工和协作机制
  • 文档完善: 完整的开发和使用文档

10.4 后续优化

  • 功能扩展: 根据业务需求持续扩展功能
  • 性能提升: 持续的性能监控和优化
  • 用户体验: 基于用户反馈优化交互体验
  • 技术升级: 跟进技术发展,适时升级技术栈
  • 安全加固: 持续加强安全防护和风险控制