Files
tjt_czjs_backend/doc/前端开发文档.md
2026-01-19 18:54:03 +08:00

28 KiB
Raw Blame History

Lyzsys 前端开发文档

目录


项目概述

Lyzsys 前端是基于 Vue 3 + TypeScript + Element Plus 构建的现代化管理系统,采用 Composition API 和模块化架构,支持多租户、权限管理、国际化等功能。

核心特性

  • Vue 3 Composition API: 充分利用 Vue 3 的新特性
  • TypeScript: 完整的类型支持
  • Vite 构建: 极速的开发体验
  • Element Plus: 企业级 UI 组件库
  • 权限管理: 基于 RBAC 的权限控制
  • 国际化: 支持多语言切换
  • 主题定制: 灵活的主题配置

技术栈

核心框架

技术 版本 说明
Vue 3.2+ 渐进式 JavaScript 框架
TypeScript 4.x JavaScript 的超集
Vite 4.x 下一代前端构建工具
Vue Router 4.x Vue.js 官方路由管理器
Pinia 2.x Vue 官方状态管理库

UI 框架

技术 版本 说明
Element Plus 2.x 基于 Vue 3 的组件库
UnoCSS - 原子化 CSS 引擎
Animate.css - CSS 动画库
Iconify - 统一的图标框架

工具库

技术 版本 说明
Axios - HTTP 客户端
Day.js - 轻量级日期处理库
Lodash - JavaScript 实用工具库
Crypto-js - JavaScript 加密库
DOMPurify - XSS 过滤器
WangEditor - 富文本编辑器
ECharts - 数据可视化库

开发工具

技术 版本 说明
ESLint - 代码检查工具
Prettier - 代码格式化工具
Stylelint - CSS 代码检查工具
Commitlint - Git 提交信息检查
unplugin-auto-import - 自动导入 API
unplugin-vue-components - 自动导入组件

项目结构

目录结构

lyzsys-ui-admin/
├── .github/              # GitHub Actions 配置
├── .husky/              # Git Hooks 配置
├── .vscode/             # VSCode 配置
├── public/              # 静态资源
├── src/                 # 源代码
│   ├── api/             # API 接口管理
│   │   ├── ai/          # AI 相关接口
│   │   ├── bpm/         # 工作流接口
│   │   ├── crm/         # CRM 接口
│   │   ├── erp/         # ERP 接口
│   │   ├── system/      # 系统管理接口
│   │   └── login/       # 登录接口
│   ├── assets/          # 静态资源
│   │   ├── images/      # 图片
│   │   ├── styles/      # 样式文件
│   │   └── svg/         # SVG 图标
│   ├── components/      # 全局组件
│   ├── config/          # 全局配置
│   ├── directives/      # 自定义指令
│   ├── hooks/           # 组合式函数
│   ├── layout/          # 布局组件
│   ├── locales/         # 国际化文件
│   │   ├── en/          # 英文
│   │   └── zh-CN/       # 简体中文
│   ├── plugins/         # 插件配置
│   ├── router/          # 路由配置
│   ├── store/           # 状态管理
│   │   ├── modules/     # 模块化 Store
│   │   └── index.ts     # Store 入口
│   ├── styles/          # 全局样式
│   ├── types/           # TypeScript 类型定义
│   ├── utils/           # 工具函数
│   ├── views/           # 页面组件
│   ├── App.vue          # 应用根组件
│   ├── main.ts          # 应用入口
│   └── permission.ts    # 路由权限控制
├── types/               # 全局类型定义
├── .env.base            # 基础环境变量
├── .env.dev             # 开发环境变量
├── .env.pro             # 生产环境变量
├── .env.test            # 测试环境变量
├── .eslintrc.js         # ESLint 配置
├── .gitignore           # Git 忽略文件
├── .prettierrc.js       # Prettier 配置
├── index.html           # HTML 模板
├── package.json         # 项目依赖
├── tsconfig.json        # TypeScript 配置
└── vite.config.ts       # Vite 配置

核心文件说明

main.ts

应用入口文件,负责初始化 Vue 应用:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { setupStore } from './store'
import { setupRouter } from './router'
import { setupElPlus, setupIcons } from './plugins'

async function bootstrap() {
  const app = createApp(App)

  // 配置 Store
  setupStore(app)

  // 配置 Router
  await setupRouter(app)

  // 配置插件
  setupElPlus(app)
  setupIcons(app)

  // 挂载应用
  app.mount('#app')
}

bootstrap()

App.vue

应用根组件:

<template>
  <ConfigProvider :locale="getLocale">
    <router-view />
  </ConfigProvider>
</template>

<script setup lang="ts">
import { ConfigProvider } from 'element-plus'
import { useLocale } from './locales/useLocale'
import { getLocale } from './locales/getLocale'
</script>

permission.ts

路由权限控制:

import router from './router'
import { useUserStore } from './store/modules/user'
import { usePermissionStore } from './store/modules/permission'

router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  const permissionStore = usePermissionStore()

  // 检查是否登录
  if (userStore.getToken) {
    // 已登录
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      // 检查是否已获取权限
      if (permissionStore.getIsAddRouters) {
        next()
      } else {
        // 获取权限和路由
        await permissionStore.generateRoutes()
        next({ ...to, replace: true })
      }
    }
  } else {
    // 未登录
    if (to.path === '/login') {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
    }
  }
})

开发指南

环境准备

必需软件

软件 版本要求 下载地址
Node.js 16+ https://nodejs.org/
pnpm 8+ https://pnpm.io/
VSCode 最新版 https://code.visualstudio.com/

推荐插件

  • Vue - Official
  • TypeScript Vue Plugin (Volar)
  • ESLint
  • Prettier - Code formatter
  • Stylelint
  • UnoCSS

快速开始

1. 克隆项目

git clone https://github.com/your-org/lyzsys.git
cd lyzsys/lyzsys-ui-admin

2. 安装依赖

pnpm install

3. 启动开发服务器

pnpm dev

4. 访问应用

开发规范

命名规范

文件命名:

  • 组件文件:大驼峰 UserList.vue
  • 工具文件:小驼峰 formatDate.ts
  • 样式文件:小驼峰 userList.scss

变量命名:

// 常量:全大写
const MAX_SIZE = 100

// 变量:小驼峰
const userName = 'admin'

// 接口:大驼峰
interface UserInfo {
  id: number
  name: string
}

// 类型:大驼峰
type UserRole = 'admin' | 'user'

// 枚举:大驼峰
enum UserStatus {
  Active = 'active',
  Inactive = 'inactive'
}

组件命名:

<script setup lang="ts">
// 组件名:大驼峰
defineOptions({
  name: 'UserList'
})
</script>

注释规范

函数注释:

/**
 * 获取用户列表
 * @param params 查询参数
 * @returns 用户列表
 */
async function getUserList(params: UserPageReqVO) {
  return await userApi.getUserPage(params)
}

组件注释:

<!--
  @description 用户列表组件
  @author Lyzsys Team
-->
<template>
  <!-- ... -->
</template>

代码风格

使用 Composition API:

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'

// 响应式数据
const loading = ref(false)
const userList = ref<User[]>([])

// 计算属性
const totalCount = computed(() => userList.value.length)

// 方法
async function fetchUsers() {
  loading.value = true
  try {
    userList.value = await userApi.getUserList()
  } finally {
    loading.value = false
  }
}

// 生命周期
onMounted(() => {
  fetchUsers()
})
</script>

使用 TypeScript:

interface User {
  id: number
  name: string
  email: string
}

type UserRole = 'admin' | 'user' | 'guest'

const user: User = {
  id: 1,
  name: 'Admin',
  email: 'admin@example.com'
}

const role: UserRole = 'admin'

组件开发

创建组件

<!-- components/UserList.vue -->
<template>
  <div class="user-list">
    <el-table :data="userList" :loading="loading">
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="email" label="邮箱" />
      <el-table-column label="操作">
        <template #default="{ row }">
          <el-button link @click="handleEdit(row)">编辑</el-button>
          <el-button link @click="handleDelete(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'

interface User {
  id: number
  name: string
  email: string
}

defineOptions({
  name: 'UserList'
})

const loading = ref(false)
const userList = ref<User[]>([])

async function fetchUsers() {
  loading.value = true
  try {
    const { data } = await userApi.getUserList()
    userList.value = data
  } finally {
    loading.value = false
  }
}

function handleEdit(user: User) {
  console.log('编辑用户', user)
}

function handleDelete(user: User) {
  console.log('删除用户', user)
}

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

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

组件通信

Props:

<script setup lang="ts">
interface Props {
  userId: number
  userName?: string
}

const props = withDefaults(defineProps<Props>(), {
  userName: ''
})
</script>

Emits:

<script setup lang="ts">
interface Emits {
  (e: 'update:modelValue', value: string): void
  (e: 'change', value: string): void
}

const emit = defineEmits<Emits>()

function handleChange(value: string) {
  emit('update:modelValue', value)
  emit('change', value)
}
</script>

Slots:

<template>
  <div class="card">
    <slot name="header">
      <h3>默认标题</h3>
    </slot>
    <slot>
      默认内容
    </slot>
    <slot name="footer">
      <el-button>确定</el-button>
    </slot>
  </div>
</template>

API 开发

API 定义

// api/system/user/index.ts
import request from '@/utils/request'

// 获取用户分页
export function getUserPage(params: UserPageReqVO) {
  return request.get({ url: '/system/user/page', params })
}

// 获取用户详情
export function getUser(id: number) {
  return request.get({ url: `/system/user/get?id=${id}` })
}

// 创建用户
export function createUser(data: UserCreateReqVO) {
  return request.post({ url: '/system/user/create', data })
}

// 更新用户
export function updateUser(data: UserUpdateReqVO) {
  return request.put({ url: '/system/user/update', data })
}

// 删除用户
export function deleteUser(id: number) {
  return request.delete({ url: `/system/user/delete?id=${id}` })
}

API 调用

<script setup lang="ts">
import { ref } from 'vue'
import { getUserPage } from '@/api/system/user'

const loading = ref(false)
const userList = ref([])

async function fetchUsers() {
  loading.value = true
  try {
    const { data } = await getUserPage({
      pageNo: 1,
      pageSize: 10
    })
    userList.value = data.list
  } finally {
    loading.value = false
  }
}
</script>

路由开发

路由配置

// router/modules/system.ts
import type { AppRouteModule } from '@/router/types'

const system: AppRouteModule = {
  path: '/system',
  name: 'System',
  component: 'LAYOUT',
  redirect: '/system/user',
  meta: {
    title: '系统管理',
    icon: 'system',
    orderNo: 1000
  },
  children: [
    {
      path: 'user',
      name: 'SystemUser',
      component: () => import('@/views/system/user/index.vue'),
      meta: {
        title: '用户管理',
        icon: 'user',
        noCache: true
      }
    },
    {
      path: 'role',
      name: 'SystemRole',
      component: () => import('@/views/system/role/index.vue'),
      meta: {
        title: '角色管理',
        icon: 'role',
        noCache: true
      }
    }
  ]
}

export default system

路由跳转

import { useRouter } from 'vue-router'

const router = useRouter()

// 路径跳转
router.push('/system/user')

// 命名路由跳转
router.push({ name: 'SystemUser' })

// 带参数跳转
router.push({
  path: '/system/user',
  query: { id: 1 }
})

// 替换当前路由
router.replace('/system/user')

// 后退
router.back()

// 前进
router.forward()

状态管理

Store 定义

// store/modules/user.ts
import { defineStore } from 'pinia'
import { getUserInfo, login } from '@/api/system/user'

interface UserState {
  token: string
  userInfo: UserInfo | null
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    token: '',
    userInfo: null
  }),

  getters: {
    getToken: (state) => state.token,
    getUserInfo: (state) => state.userInfo
  },

  actions: {
    setToken(token: string) {
      this.token = token
    },

    async login(params: LoginReqVO) {
      const { data } = await login(params)
      this.setToken(data.token)
    },

    async getUserInfoAction() {
      const { data } = await getUserInfo()
      this.userInfo = data
    },

    async logout() {
      this.token = ''
      this.userInfo = null
    }
  },

  persist: {
    key: 'user-store',
    storage: localStorage
  }
})

Store 使用

<script setup lang="ts">
import { useUserStore } from '@/store/modules/user'

const userStore = useUserStore()

// 访问 state
console.log(userStore.token)

// 访问 getters
console.log(userStore.getToken)

// 调用 actions
await userStore.login({
  username: 'admin',
  password: 'admin123'
})

// 重置 state
userStore.$reset()
</script>

核心功能

权限管理

权限指令

<template>
  <!-- 有权限时显示 -->
  <el-button v-hasPermi="['system:user:create']">创建用户</el-button>

  <!-- 有角色时显示 -->
  <el-button v-hasRole="['admin']">管理员操作</el-button>

  <!-- 同时满足多个权限 -->
  <el-button v-hasPermi="['system:user:update', 'system:user:delete']">
    操作
  </el-button>
</template>

权限函数

import { usePermission } from '@/hooks/web/usePermission'

const { hasPermission, hasRole } = usePermission()

// 检查权限
if (hasPermission(['system:user:create'])) {
  // 有权限
}

// 检查角色
if (hasRole(['admin'])) {
  // 是管理员
}

国际化

使用翻译

<template>
  <!-- 使用翻译 -->
  <h1>{{ $t('common.title') }}</h1>

  <!-- 带参数的翻译 -->
  <p>{{ $t('common.welcome', { name: 'Admin' }) }}</p>
</template>

<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'

const { t } = useI18n()

// 使用翻译
const message = t('common.title')
const welcome = t('common.welcome', { name: 'Admin' })
</script>

添加翻译

// locales/zh-CN/common.ts
export default {
  title: 'Lyzsys 管理系统',
  welcome: '欢迎,{name}',
  logout: '退出登录'
}
// locales/en/common.ts
export default {
  title: 'Lyzsys Admin',
  welcome: 'Welcome, {name}',
  logout: 'Logout'
}

主题定制

修改主题色

// styles/variables.scss
$primary-color: #409eff;
$success-color: #67c23a;
$warning-color: #e6a23c;
$danger-color: #f56c6c;
$error-color: #f56c6c;

动态切换主题

<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'

const appStore = useAppStore()

// 切换暗黑模式
function toggleDarkMode() {
  appStore.setDarkMode(!appStore.getDarkMode)
}
</script>

文件上传

单文件上传

<template>
  <el-upload
    :action="uploadUrl"
    :headers="uploadHeaders"
    :on-success="handleSuccess"
    :before-upload="beforeUpload"
  >
    <el-button type="primary">上传文件</el-button>
  </el-upload>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getAccessToken } from '@/utils/auth'

const uploadUrl = import.meta.env.VITE_BASE_URL + '/admin-api/infra/file/upload'
const uploadHeaders = ref({
  Authorization: 'Bearer ' + getAccessToken()
})

function beforeUpload(file: File) {
  const isLt2M = file.size / 1024 / 1024 < 2
  if (!isLt2M) {
    ElMessage.error('上传文件大小不能超过 2MB!')
  }
  return isLt2M
}

function handleSuccess(response: any) {
  if (response.code === 0) {
    ElMessage.success('上传成功')
    console.log('文件 URL:', response.data)
  } else {
    ElMessage.error(response.msg)
  }
}
</script>

多文件上传

<template>
  <el-upload
    :action="uploadUrl"
    :headers="uploadHeaders"
    multiple
    :limit="3"
    :on-exceed="handleExceed"
    :on-success="handleSuccess"
  >
    <el-button type="primary">多文件上传</el-button>
  </el-upload>
</template>

<script setup lang="ts">
function handleExceed() {
  ElMessage.warning('最多只能上传 3 个文件')
}
</script>

表单验证

表单定义

<template>
  <el-form
    ref="formRef"
    :model="form"
    :rules="rules"
    label-width="100px"
  >
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username" placeholder="请输入用户名" />
    </el-form-item>

    <el-form-item label="邮箱" prop="email">
      <el-input v-model="form.email" placeholder="请输入邮箱" />
    </el-form-item>

    <el-form-item label="密码" prop="password">
      <el-input
        v-model="form.password"
        type="password"
        placeholder="请输入密码"
      />
    </el-form-item>

    <el-form-item>
      <el-button type="primary" @click="handleSubmit">提交</el-button>
      <el-button @click="handleReset">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'

const formRef = ref<FormInstance>()

const form = reactive({
  username: '',
  email: '',
  password: ''
})

const rules: FormRules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
  ],
  email: [
    { required: true, message: '请输入邮箱', trigger: 'blur' },
    { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' },
    { min: 6, max: 20, message: '长度在 6 到 20 个字符', trigger: 'blur' }
  ]
}

async function handleSubmit() {
  if (!formRef.value) return

  await formRef.value.validate((valid) => {
    if (valid) {
      console.log('表单数据:', form)
      // 提交表单
    }
  })
}

function handleReset() {
  formRef.value?.resetFields()
}
</script>

表格操作

基础表格

<template>
  <el-table :data="tableData" :loading="loading" border>
    <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="status" label="状态">
      <template #default="{ row }">
        <el-tag :type="row.status === 'active' ? 'success' : 'danger'">
          {{ row.status === 'active' ? '激活' : '禁用' }}
        </el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作" width="200" fixed="right">
      <template #default="{ row }">
        <el-button link @click="handleEdit(row)">编辑</el-button>
        <el-button link @click="handleDelete(row)">删除</el-button>
      </template>
    </el-table-column>
  </el-table>
</template>

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

interface User {
  id: number
  username: string
  email: string
  status: string
}

const loading = ref(false)
const tableData = ref<User[]>([])

function handleEdit(user: User) {
  console.log('编辑用户', user)
}

function handleDelete(user: User) {
  console.log('删除用户', user)
}
</script>

分页表格

<template>
  <div>
    <el-table :data="tableData" :loading="loading" border>
      <!-- 表格列 -->
    </el-table>

    <el-pagination
      v-model:current-page="queryParams.pageNo"
      v-model:page-size="queryParams.pageSize"
      :total="total"
      :page-sizes="[10, 20, 50, 100]"
      layout="total, sizes, prev, pager, next, jumper"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>

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

const loading = ref(false)
const tableData = ref([])
const total = ref(0)

const queryParams = reactive({
  pageNo: 1,
  pageSize: 10
})

async function fetchTableData() {
  loading.value = true
  try {
    const { data } = await userApi.getUserPage(queryParams)
    tableData.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}

function handleSizeChange() {
  fetchTableData()
}

function handleCurrentChange() {
  fetchTableData()
}

// 初始加载
fetchTableData()
</script>

组件库

Element Plus

按钮

<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
<el-button link>文字按钮</el-button>

表单

<el-input v-model="value" placeholder="请输入内容" />
<el-input v-model="value" type="textarea" />
<el-input v-model="value" type="password" />

选择器

<el-select v-model="value" placeholder="请选择">
  <el-option label="选项1" value="1" />
  <el-option label="选项2" value="2" />
</el-select>

日期选择器

<el-date-picker
  v-model="value"
  type="date"
  placeholder="选择日期"
/>

<el-date-picker
  v-model="value"
  type="daterange"
  start-placeholder="开始日期"
  end-placeholder="结束日期"
/>

对话框

<el-dialog v-model="visible" title="提示" width="30%">
  <span>这是一段内容</span>
  <template #footer>
    <el-button @click="visible = false">取消</el-button>
    <el-button type="primary" @click="visible = false">确定</el-button>
  </template>
</el-dialog>

消息提示

import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'

// 消息提示
ElMessage.success('操作成功')
ElMessage.warning('警告消息')
ElMessage.error('错误消息')
ElMessage.info('提示消息')

// 确认框
ElMessageBox.confirm('确定要删除吗?', '提示', {
  confirmButtonText: '确定',
  cancelButtonText: '取消',
  type: 'warning'
})
  .then(() => {
    ElMessage.success('删除成功')
  })
  .catch(() => {
    ElMessage.info('已取消删除')
  })

// 通知
ElNotification({
  title: '通知',
  message: '这是一条通知消息',
  type: 'success'
})

自定义组件

图表组件 (ECharts)

<template>
  <div ref="chartRef" style="width: 100%; height: 400px"></div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
import type { EChartsOption } from 'echarts'

const chartRef = ref<HTMLDivElement>()
let chartInstance: echarts.ECharts | null = null

onMounted(() => {
  if (chartRef.value) {
    chartInstance = echarts.init(chartRef.value)

    const option: EChartsOption = {
      title: {
        text: '示例图表'
      },
      xAxis: {
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {},
      series: [
        {
          type: 'line',
          data: [150, 230, 224, 218, 135, 147, 260]
        }
      ]
    }

    chartInstance.setOption(option)
  }
})

onUnmounted(() => {
  chartInstance?.dispose()
})
</script>

富文本编辑器 (WangEditor)

<template>
  <div ref="editorRef" style="height: 500px"></div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { createEditor, createToolbar } from '@wangeditor/editor-for-vue'

const editorRef = ref<HTMLDivElement>()
let editor: any = null
let toolbar: any = null

onMounted(() => {
  if (editorRef.value) {
    editor = createEditor({
      selector: editorRef.value,
      html: '<p>默认内容</p>',
      config: {}
    })

    toolbar = createToolbar({
      editor,
      config: {}
    })
  }
})

onUnmounted(() => {
  editor?.destroy()
  toolbar?.destroy()
})
</script>

样式指南

CSS 预处理器

使用 SCSS 作为 CSS 预处理器:

// 使用变量
$primary-color: #409eff;
$border-radius: 4px;

// 使用嵌套
.button {
  background: $primary-color;
  border-radius: $border-radius;

  &:hover {
    background: darken($primary-color, 10%);
  }

  &.active {
    background: lighten($primary-color, 10%);
  }
}

UnoCSS

使用原子化 CSS

<template>
  <div class="flex items-center justify-center h-screen bg-gray-100">
    <div class="p-6 bg-white rounded-lg shadow-lg">
      <h1 class="text-2xl font-bold text-gray-800">标题</h1>
      <p class="mt-4 text-gray-600">内容</p>
    </div>
  </div>
</template>

响应式设计

// 使用媒体查询
.container {
  padding: 20px;

  @media (max-width: 768px) {
    padding: 10px;
  }

  @media (max-width: 480px) {
    padding: 5px;
  }
}

主题定制

// 自定义主题色
:root {
  --el-color-primary: #409eff;
  --el-color-success: #67c23a;
  --el-color-warning: #e6a23c;
  --el-color-danger: #f56c6c;
}

// 暗黑模式
.dark {
  --el-bg-color: #1a1a1a;
  --el-text-color-primary: #ffffff;
}

部署指南

本地构建

# 开发环境构建
pnpm build:dev

# 生产环境构建
pnpm build:prod

# 测试环境构建
pnpm build:test

环境变量配置

# .env.base
VITE_BASE_URL=/api

# .env.dev
VITE_BASE_URL=http://localhost:48080

# .env.prod
VITE_BASE_URL=https://api.example.com

Docker 部署

1. 构建镜像

docker build -t lyzsys/lyzsys-ui-admin:latest .

2. 运行容器

docker run -d \
  --name lyzsys-ui-admin \
  -p 80:80 \
  lyzsys/lyzsys-ui-admin:latest

Nginx 配置

server {
  listen 80;
  server_name example.com;

  root /usr/share/nginx/html;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }

  location /api {
    proxy_pass http://backend:48080;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  gzip on;
  gzip_types text/plain text/css application/json application/javascript;
}

附录

常见问题

Q1: 如何添加新页面?

  1. views/ 目录下创建页面组件
  2. router/modules/ 中配置路由
  3. 添加国际化翻译(如需要)
  4. 在后台系统中配置菜单权限

Q2: 如何调用后端 API

  1. api/ 目录下创建 API 文件
  2. 定义 API 接口函数
  3. 在组件中导入并调用
  4. 处理响应数据

Q3: 如何实现权限控制?

  1. 使用 v-hasPermi 指令控制按钮显示
  2. 使用 v-hasRole 指令控制角色访问
  3. 在路由配置中设置权限 meta
  4. 后端接口进行权限验证

Q4: 如何切换语言?

  1. locales/ 目录下添加语言文件
  2. 使用 $t() 函数进行翻译
  3. 在语言切换器中调用 setLocale() 方法

参考资源


文档版本: v1.0.0 最后更新: 2025-01-19 维护团队: Lyzsys 开发团队