1465 lines
28 KiB
Markdown
1465 lines
28 KiB
Markdown
|
|
# 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 开发团队
|