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

1465 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 应用:
```typescript
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
应用根组件:
```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
路由权限控制:
```typescript
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. 克隆项目
```bash
git clone https://github.com/your-org/lyzsys.git
cd lyzsys/lyzsys-ui-admin
```
#### 2. 安装依赖
```bash
pnpm install
```
#### 3. 启动开发服务器
```bash
pnpm dev
```
#### 4. 访问应用
- 应用地址: http://localhost:5173
- 用户名: admin
- 密码: admin123
### 开发规范
#### 命名规范
**文件命名**:
- 组件文件:大驼峰 `UserList.vue`
- 工具文件:小驼峰 `formatDate.ts`
- 样式文件:小驼峰 `userList.scss`
**变量命名**:
```typescript
// 常量:全大写
const MAX_SIZE = 100
// 变量:小驼峰
const userName = 'admin'
// 接口:大驼峰
interface UserInfo {
id: number
name: string
}
// 类型:大驼峰
type UserRole = 'admin' | 'user'
// 枚举:大驼峰
enum UserStatus {
Active = 'active',
Inactive = 'inactive'
}
```
**组件命名**:
```vue
<script setup lang="ts">
// 组件名:大驼峰
defineOptions({
name: 'UserList'
})
</script>
```
#### 注释规范
**函数注释**:
```typescript
/**
* 获取用户列表
* @param params 查询参数
* @returns 用户列表
*/
async function getUserList(params: UserPageReqVO) {
return await userApi.getUserPage(params)
}
```
**组件注释**:
```vue
<!--
@description 用户列表组件
@author Lyzsys Team
-->
<template>
<!-- ... -->
</template>
```
#### 代码风格
**使用 Composition API**:
```vue
<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**:
```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'
```
### 组件开发
#### 创建组件
```vue
<!-- 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**:
```vue
<script setup lang="ts">
interface Props {
userId: number
userName?: string
}
const props = withDefaults(defineProps<Props>(), {
userName: ''
})
</script>
```
**Emits**:
```vue
<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**:
```vue
<template>
<div class="card">
<slot name="header">
<h3>默认标题</h3>
</slot>
<slot>
默认内容
</slot>
<slot name="footer">
<el-button>确定</el-button>
</slot>
</div>
</template>
```
### API 开发
#### API 定义
```typescript
// 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 调用
```vue
<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>
```
### 路由开发
#### 路由配置
```typescript
// 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
```
#### 路由跳转
```typescript
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 定义
```typescript
// 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 使用
```vue
<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>
```
---
## 核心功能
### 权限管理
#### 权限指令
```vue
<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>
```
#### 权限函数
```typescript
import { usePermission } from '@/hooks/web/usePermission'
const { hasPermission, hasRole } = usePermission()
// 检查权限
if (hasPermission(['system:user:create'])) {
// 有权限
}
// 检查角色
if (hasRole(['admin'])) {
// 是管理员
}
```
### 国际化
#### 使用翻译
```vue
<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>
```
#### 添加翻译
```typescript
// locales/zh-CN/common.ts
export default {
title: 'Lyzsys 管理系统',
welcome: '欢迎,{name}',
logout: '退出登录'
}
```
```typescript
// locales/en/common.ts
export default {
title: 'Lyzsys Admin',
welcome: 'Welcome, {name}',
logout: 'Logout'
}
```
### 主题定制
#### 修改主题色
```typescript
// styles/variables.scss
$primary-color: #409eff;
$success-color: #67c23a;
$warning-color: #e6a23c;
$danger-color: #f56c6c;
$error-color: #f56c6c;
```
#### 动态切换主题
```vue
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
// 切换暗黑模式
function toggleDarkMode() {
appStore.setDarkMode(!appStore.getDarkMode)
}
</script>
```
### 文件上传
#### 单文件上传
```vue
<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>
```
#### 多文件上传
```vue
<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>
```
### 表单验证
#### 表单定义
```vue
<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>
```
### 表格操作
#### 基础表格
```vue
<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>
```
#### 分页表格
```vue
<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
#### 按钮
```vue
<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>
```
#### 表单
```vue
<el-input v-model="value" placeholder="请输入内容" />
<el-input v-model="value" type="textarea" />
<el-input v-model="value" type="password" />
```
#### 选择器
```vue
<el-select v-model="value" placeholder="请选择">
<el-option label="选项1" value="1" />
<el-option label="选项2" value="2" />
</el-select>
```
#### 日期选择器
```vue
<el-date-picker
v-model="value"
type="date"
placeholder="选择日期"
/>
<el-date-picker
v-model="value"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
```
#### 对话框
```vue
<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>
```
#### 消息提示
```typescript
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)
```vue
<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)
```vue
<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 预处理器:
```scss
// 使用变量
$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
```vue
<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>
```
### 响应式设计
```scss
// 使用媒体查询
.container {
padding: 20px;
@media (max-width: 768px) {
padding: 10px;
}
@media (max-width: 480px) {
padding: 5px;
}
}
```
### 主题定制
```scss
// 自定义主题色
: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;
}
```
---
## 部署指南
### 本地构建
```bash
# 开发环境构建
pnpm build:dev
# 生产环境构建
pnpm build:prod
# 测试环境构建
pnpm build:test
```
### 环境变量配置
```bash
# .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. 构建镜像
```bash
docker build -t lyzsys/lyzsys-ui-admin:latest .
```
#### 2. 运行容器
```bash
docker run -d \
--name lyzsys-ui-admin \
-p 80:80 \
lyzsys/lyzsys-ui-admin:latest
```
### Nginx 配置
```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()` 方法
### 参考资源
- [Vue 3 官方文档](https://cn.vuejs.org/)
- [TypeScript 官方文档](https://www.typescriptlang.org/zh/)
- [Vite 官方文档](https://cn.vitejs.dev/)
- [Element Plus 官方文档](https://element-plus.org/zh-CN/)
- [Pinia 官方文档](https://pinia.vuejs.org/zh/)
- [UnoCSS 官方文档](https://unocss.dev/)
---
**文档版本**: v1.0.0
**最后更新**: 2025-01-19
**维护团队**: Lyzsys 开发团队