# acp-admin-web
**Repository Path**: zhangbinhub/acp-admin-web
## Basic Information
- **Project Name**: acp-admin-web
- **Description**: 使用vue全家桶(Vue+Vue-router+axios)、Element-Plus 样式库构建的前端项目。该项目是前后端分离架构中的“前端部分”
- **Primary Language**: JavaScript
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 25
- **Forks**: 6
- **Created**: 2021-08-27
- **Last Updated**: 2026-09-28
## Categories & Tags
**Categories**: backend
**Tags**: vue3, element-plus, vite, file-viewer
## README
# acp-admin-cloud-web





###### v8.4.1 [版本更新日志](doc/version_history.md)
## 简介
该工程是 acp-admin 的前端,基于 Vue 3 全家桶(Vue + Vue Router + Pinia + Axios)与 Element Plus 组件库构建,是前后端分离架构中的"前端部分"。
## 功能特性
* 认证安全:OAuth2 认证,登录密码国密 SM3 加密传输,密码过期策略,密码复杂度策略
* 系统配置:应用、菜单、模块功能、机构、角色、用户、权限、字典、运行参数在线配置
* 工作流:工作流部署与流程管理,内置 BPMN 流程在线设计器(solon-flow)
* 监控审计:登录日志、操作日志、路由日志,在线用户统计与登录统计图表(ECharts)
* 通用能力:文件在线预览(Flyfish Viewer)、富文本编辑(Fluent Editor / Quill)、头像裁剪、JSON 编辑、代码高亮
* 界面体验:多标签页导航、页面缓存(keep-alive)、国际化(简体中文 / 繁体中文 / 英文)、显示模式(浅色 / 深色 / 跟随系统)、响应式布局
## 技术栈
| 分类 | 技术 | 说明 |
|------|------|------|
| 核心框架 | [Vue 3.5](https://cn.vuejs.org/) | Composition API |
| 构建工具 | [Vite 8](https://cn.vite.dev/) | 基于 Rolldown 的构建引擎 |
| 路由 | [Vue Router](https://router.vuejs.org/zh/) | Hash 模式,支持 views 目录自动扫描注册 |
| 状态管理 | [Pinia](https://pinia.vuejs.org/zh/) | 结合 localStorage 持久化 |
| UI 组件库 | [Element Plus](https://element-plus.org/zh-CN/) | 全量引入 |
| 国际化 | [Vue I18n](https://vue-i18n.intlify.dev/) | Composition API 模式 |
| HTTP 请求 | [Axios](https://axios-http.com/zh/) | 统一拦截器封装 |
| 图表 | [ECharts](https://echarts.apache.org/zh/) | 按需引入 |
| 日期处理 | [Day.js](https://day.js.org/zh-CN/) | 2KB 不可变日期库,替代已停止维护的 Moment.js |
| 富文本 | [Fluent Editor](https://opentiny.github.io/fluent-editor/) / Quill | 基于 Quill 2.0 的富文本编辑器 |
| 图片裁剪 | [Cropper.js](https://cropperjs.github.io/) | v2 |
| 文件预览 | [Flyfish Viewer](https://doc.flyfish.dev/) | 文档、表格、音视频等在线预览 |
| 组合式工具 | [@vueuse/core](https://vueuse.org/) | 提供 useStorage、usePreferredDark 等组合式工具 |
| 模拟数据 | [MSW](https://mswjs.io/) | Mock Service Worker,仅开发环境自动启用 |
## 浏览器兼容性
支持的浏览器及最低版本:
| 浏览器 | 最低版本 | 说明 |
|--------|---------|------|
| Chrome | 119+ | 推荐 |
| Firefox | 121+ | 完全支持 |
| Edge | 119+ | 完全支持 |
| Safari | 17.2+ | 完全支持 |
## 环境要求
* Node.js:`^20.19.0 || >=22.12.0`(建议使用 LTS 版本)
* 包管理器:npm(随 Node.js 附带)
可选:设置国内 npm 镜像加速依赖安装
```bash
npm config set registry https://mirrors.tencent.com/npm/
```
## 快速开始
```bash
# 安装依赖
npm install
# 启动本地开发环境(默认 http://127.0.0.1:5173)
npm run start
```
开发环境下会自动启用 MSW(Mock Service Worker)拦截 `src/mock` 中注册的接口(浏览器控制台会输出 `[MSW] Mocking enabled`);未注册 mock 的请求通过 Vite 代理转发至 `http://127.0.0.1:8771` (可在 [vite.config.mjs](vite.config.mjs) 的 `server.proxy` 中修改)。
### 常用命令
| 命令 | 说明 |
|------|------|
| `npm run start` | 启动本地开发环境 |
| `npm run build-test` | 编译打包 test 环境 |
| `npm run build-prod` | 编译打包 prod 环境 |
| `npm run preview` | 本地预览生产构建产物 |
| `npm run lint` | ESLint 检查并自动修复 `src` |
| `npm run format` | Prettier 格式化 `src` 与 `index.html` |
### 依赖更新
推荐使用 [npm-check-updates](https://github.com/raineorshine/npm-check-updates) 批量检查并更新依赖版本:
```bash
npm install -g npm-check-updates
ncu -u
npm install
```
## 工程配置
### 目录结构
```text
root -------工程根目录
├─ doc -------公共文档
├─ environment -------分环境变量配置
├─ public -------静态资源(不参与编译)
├─ src -------源代码
│ ├─ api -------远程 api 封装
│ ├─ assets -------静态资源(参与编译的资源文件)
│ ├─ components -------组件
│ │ ├─ layout -------框架布局
│ │ ├─ libs -------工具库
│ │ ├─ pages -------独立页面
│ │ └─ plugins -------插件
│ ├─ composables -------组合式函数(useCrudTable 等)
│ ├─ lang -------国际化
│ ├─ mock -------mock 模拟数据(仅开发环境加载)
│ ├─ router -------路由配置
│ ├─ stores -------状态管理
│ ├─ views -------自定义页面(自动路由扫描)
│ ├─ App.vue -------根组件
│ └─ main.js -------应用入口
├─ .prettierrc.json -------prettier 配置文件
├─ eslint.config.js -------eslint 配置文件
├─ index.html -------主页面,入口文件
├─ jsconfig.json -------jsconfig 配置文件
├─ package.json -------package.json
└─ vite.config.mjs -------vite 配置文件
```
### 环境变量
分环境配置文件位于 `environment` 目录:
| 环境 | 配置文件名 |
|------|-----------|
| 开发环境 | [environment/.env.development](environment/.env.development) |
| test 环境 | [environment/.env.test](environment/.env.test) |
| prod 环境 | [environment/.env.prod](environment/.env.prod) |
主要变量说明:
| 变量 | 说明 |
|------|------|
| `VITE_APP_NAME` | 应用名称 |
| `VITE_BACKEND_PREFIX` | 后端接口地址前缀(开发环境走 Vite 代理) |
| `VITE_BACKEND_TIME_OUT` | 请求超时时间(毫秒) |
| `VITE_APP_ID` / `VITE_APP_SECRET` | OAuth2 客户端标识;生产部署时建议通过构建环境注入,避免提交真实密钥 |
### 全局工程配置文件
[vite.config.mjs](vite.config.mjs),[配置参考](https://cn.vite.dev/config/)。
### 项目配置文件
* 状态管理组件:Pinia,路径:`src/stores`,入口:[src/stores/index.js](src/stores/index.js)
* 系统信息配置文件(应用名称、主题、首页路径、后端地址等):[src/stores/config/appInfo.js](src/stores/config/appInfo.js)
* token、语言、标签页等状态通过 localStorage 持久化
* 显示模式偏好持久化(key: `vueuse-color-scheme`),应用启动时由 [src/stores/app.js](src/stores/app.js) 中的 `applyTheme()` 同步读取并应用,避免页面闪烁
### 主题与样式
* 样式变量体系:[src/assets/styles/variable.less](src/assets/styles/variable.less)
- 所有业务样式统一使用 CSS 自定义属性 `var(--xxx)`,不使用 Less 变量
- `:root` 定义浅色主题变量,`.dark` 选择器覆盖为深色主题(配合 Element Plus dark CSS vars)
- 深色模式采用蓝灰底调,4 层背景纵深(L0 `#141619` → L1 `#1a1d24` → L2 `#22262e` → L3 `#282d38`)
* 显示模式切换:[src/composables/useDarkMode.js](src/composables/useDarkMode.js)
- 支持三种模式:`light`(浅色)、`dark`(深色)、`system`(跟随系统,默认)
- 基于 `@vueuse/core` 的 `useStorage` + `usePreferredDark` 实现
- 导出 `applyTheme()` 纯函数(供 app 启动时调用)和 `useDarkMode()` composable(供组件响应式使用)
- 切换入口在个人信息页面(`PersonalInformation.vue`)的 `el-radio-group`
### 路由
* 组件:vue-router,Hash 模式
* 固定路由配置:[src/router/routers.js](src/router/routers.js)
* 自动扫描路由:[src/router/autoscan.js](src/router/autoscan.js),自动扫描 `src/views` 下所有 `.vue` 文件,读取组件导出的 `name` 与 `pageMeta` 属性生成路由
* `pageMeta` 属性说明:
| 属性 | 说明 |
|------|------|
| `title` | 页面标题(i18n 表达式),用于浏览器标题、面包屑、标签导航 |
| `icon` | 图标样式,用于面包屑、标签导航 |
| `keepAlive` | 主界面是否缓存该页面,默认 `true` |
| `requireAuth` | 访问该页面是否需要登录,默认 `false` |
| `withInput` | 页面是否包含用户输入项,默认 `false` ;为 `true` 且 `keepAlive=false` 时,关闭标签前会进行确认 |
### HTTP 请求与 API
* 组件:axios,实例配置:[src/components/plugins/axios/plugin-axios.js](src/components/plugins/axios/plugin-axios.js)
* 请求与响应拦截器(token 注入、全局错误处理、页面跳转):[src/api/ApiComm.js](src/api/ApiComm.js)
* API 定义:`src/api` 目录,按业务域拆分文件,并在 [src/api/ApiLists.js](src/api/ApiLists.js) 中注册
* 页面中调用方式:`import api from '@/api/ApiComm'`,通过 `api.request.<模块>.<方法>()` 发起请求
#### 前后端交互 HttpStatus Code 说明
| HttpStatus | 描述 |
|------------|-------------|
| 200 | 请求成功 |
| 201 | 资源创建成功 |
| 400 | 业务错误 |
| 401 | token(登录)失效 |
| 403 | 权限不足 |
| 404 | 找不到资源 |
| 500 | 系统异常 |
### 国际化
* 语言包路径:`src/lang`(内置简体中文 `zh-CN`、繁体中文 `zh-TW`、英文 `en-US`)
* 新增或修改语言属性后,在 [src/lang/index.js](src/lang/index.js) 中注册新的语言包
### Mock
基于 [MSW](https://mswjs.io/)(Mock Service Worker)在网络层拦截请求,DevTools Network 面板中可见 mock 请求:
* 开发环境下自动启动 mock(见 [src/main.js](src/main.js) 的 `import.meta.env.DEV` 分支),无需手动引入;生产构建不含 mock 相关代码
* 接口拦截规则统一在 [src/mock/handlers.js](src/mock/handlers.js) 中注册,路径须带 `VITE_BACKEND_PREFIX` 前缀(如 `/v1/api/main/user`),与 `src/api` 中的请求路径一一对应
* 模拟数据按业务域放在 `src/mock` 的子目录中(如 `sysconfig`、`workflow`、`log`),以纯函数形式导出,由 handlers 引用
* 同方法下具体路径(如 `role-code-list`)须注册在路径参数通配(如 `:roleId`)之前,否则会被通配抢先匹配
* 未注册 mock 的请求直接放行(`onUnhandledRequest: 'bypass'`),经 Vite 代理转发至真实后端
* `public/mockServiceWorker.js` 由 `npx msw init public/ --save` 生成(CLI 工具脚本,已记录在 `package.json` 的 `msw.workerDirectory`),请勿手动修改
### 插件
项目内置插件位于 `src/components/plugins` 目录,每个插件独立子目录并提供 `index.js` 入口文件:
#### Axios 请求实例
* 路径:[src/components/plugins/axios/plugin-axios.js](src/components/plugins/axios/plugin-axios.js)
* 作用:创建并导出 Axios 实例,统一配置 `baseURL`(取自环境变量 `VITE_BACKEND_PREFIX`)、`timeout` 和默认请求头
* 使用方式:在 [src/main.js](src/main.js) 中通过 `vue-axios` 注册为全局 `$http`,并在 [src/api/ApiComm.js](src/api/ApiComm.js) 中挂载请求/响应拦截器(token 注入、错误处理、登录失效跳转)
#### AnimatedNumber 数字动画
* 路径:[src/components/plugins/animated-number/](src/components/plugins/animated-number/)
* 作用:基于 `requestAnimationFrame` + easeOutCubic 缓动函数的数字滚动动画组件,包裹 `el-statistic` 实现数值从旧值平滑过渡到新值
* Props:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `value` | Number | `0` | 目标数值 |
| `duration` | Number | `1000` | 动画持续时间(毫秒) |
* 插槽:`title`、`prefix`、`suffix`(透传至 `el-statistic`)
* 使用示例:
```vue
在线用户数
```
#### Cropper 头像裁剪
* 路径:[src/components/plugins/cropper/](src/components/plugins/cropper/)
* 作用:基于 [Cropper.js v2](https://cropperjs.github.io/) 的 Web Components 头像裁剪组件,支持拖拽、缩放、旋转、翻转、比例裁剪,并提供多种尺寸的实时预览
* Props:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `src` | String | — | 图片 URL,变化时自动重置裁剪区域 |
| `cropButtonText` | String | — | 裁剪按钮文字 |
* 事件:`on-crop` — 裁剪完成后触发,参数为 Base64 DataURL
* 功能按钮:重置、移动/裁剪模式切换、缩放、旋转、水平/垂直翻转、方向键微调、比例预设(16:9 / 4:3 / 1:1 / 2:3 / 自由)
* 使用示例:
```vue
```
#### PropertiesField 属性字段渲染器
* 路径:[src/components/plugins/properties-field/](src/components/plugins/properties-field/)
* 作用:根据 Properties 配置动态渲染对应的表单字段,支持 string / textarea / number / date / datetime / select / radio / picture / file 等多种字段类型,内置文件/图片上传与预览功能
* Props:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `modelValue` | Object | `{ type: 'string' }` | 字段配置对象(含 type/value/options 等) |
| `loading` | Boolean | `false` | 加载状态 |
| `viewOnly` | Boolean | `true` | 仅查看模式 |
| `size` | String | `'default'` | 组件尺寸 |
| `edit` | String | `'auto'` | 编辑控制 |
| `fileKeyPrefix` | String | `'files'` | 文件上传目录前缀 |
| `imageKeyPrefix` | String | `'images'` | 图片上传目录前缀 |
* 数据模式:select/radio 支持 `dataMode=1`(本地 options)和 `dataMode=2`(远程 URL 加载)
* 使用示例:
```vue
```
#### SolonFlowDesigner BPMN 流程设计器
* 路径:[src/components/plugins/solon-flow-designer/](src/components/plugins/solon-flow-designer/)
* 作用:封装 [solon-flow-bpmn-designer](https://www.npmjs.com/package/solon-flow-bpmn-designer) BPMN 流程在线设计器,支持可视化拖拽设计工作流
* Props:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `modelValue` | String | `''` | 设计器值(v-model) |
| `theme` | String | `'light'` | 主题(`light` / `dark`) |
| `lang` | String | `'zh'` | 语言 |
| `type` | String | `'yaml'` | 输出格式 |
| `height` | Number | `60` | 设计器高度(vh) |
* 暴露方法(通过 ref):
| 方法 | 说明 |
|------|------|
| `panelShow()` | 属性面板显隐控制 |
| `clear()` | 清除设计器内容 |
| `getValue()` | 获取当前设计器值 |
* 使用示例:
```vue
```
#### TinyEdit 富文本编辑器
* 路径:[src/components/plugins/tiny-edit/](src/components/plugins/tiny-edit/)
* 作用:基于 [Fluent Editor](https://opentiny.github.io/fluent-editor/)(Quill 2.0)的富文本编辑器组件,支持代码高亮、KaTeX 数学公式、表格增强、截图粘贴、Markdown 快捷输入、工具栏提示等
* Props:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `modelValue` | String | `''` | 编辑器 HTML 内容(v-model) |
| `disabled` | Boolean | `false` | 只读模式 |
| `count` | Boolean | `false` | 是否启用字数统计 |
| `maxCount` | Number | `2000` | 最大字数 |
* 使用示例:
```vue
```
#### UserSelect 人员选择器
* 路径:[src/components/plugins/user-select/](src/components/plugins/user-select/)
* 作用:人员选择组件,支持两种模式:① 后端查询模式(默认):左侧机构树 + 右侧分页人员列表,可按机构/角色/账号/姓名筛选;② 候选人员模式:传入 `candidateUsers` 后仅在候选列表内本地过滤。支持单选和多选
* Props:
| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `modelValue` | Array | `[]` | 已选人员数组(v-model),元素含 `id/loginNo/name` |
| `candidateUsers` | Array | `null` | 候选人员清单,传入后切换为本地过滤模式;可选含 `roleSet`、`organizationSet` |
| `multiple` | Boolean | `false` | 是否多选 |
* 使用示例:
```vue
```
### 工具库
项目工具方法位于 `src/components/libs` 目录,按职责拆分为 `tools.js`(通用工具)和 `propertiesTools.js`(属性配置工具)。
#### tools.js — 通用工具方法
| 方法 | 说明 | 用法 |
|------|------|------|
| `copy(srcObj)` | 对象深拷贝(基于 JSON 序列化),支持 null/undefined | `const clone = copy(obj)` |
| `deepEqual(x, y)` | 递归比较两个对象/数组是否相等 | `if (deepEqual(a, b)) ...` |
| `dateFormat(time)` | 格式化日期为 `YYYY-MM-DD` | `dateFormat(Date.now()) // '2025-01-01'` |
| `dateTimeFormat(time)` | 格式化日期时间为 `YYYY-MM-DD HH:mm:ss` | `dateTimeFormat(time)` |
| `dateTimeMisFormat(time)` | 格式化日期时间为 `YYYY-MM-DD HH:mm:ss.SSS`(精确到毫秒) | `dateTimeMisFormat(time)` |
| `findFullMenuByPath(path, menuList)` | 根据 path 获取菜单全路径数组(含父级) | `const chain = findFullMenuByPath('/user', menus)` |
| `findMenuByPath(path, menuList, isFullPath)` | 根据 path 查找单个菜单对象 | `const menu = findMenuByPath(path, menus)` |
| `getOpenedNamesByActiveName(name, menuList)` | 获取菜单全路径展开名称数组,用于侧边菜单展开 | `const names = getOpenedNamesByActiveName(path, menus)` |
| `findNodeUpperByClasses(ele, classes)` | 向上查找包含指定 class 的父 DOM 节点 | `findNodeUpperByClasses(el, ['wrapper'])` |
| `updateTagNavList(store, tagNavList, menuList, route)` | 更新标签导航列表(路由切换时调用) | `updateTagNavList(store, tags, menus, route)` |
| `getRouteState(store)` | 获取当前路由参数 | `const state = getRouteState(store)` |
| `getRedirect(store, route)` | 获取重定向来源路径(优先 routeState > query > redirectedFrom) | `const from = getRedirect(store, route)` |
| `processTreeNode(nodeList, showCode)` | 将后台返回的树数据转换为 el-tree 可用格式(设置 label) | `processTreeNode(treeData)` |
| `findCheckedTreeNode(nodeList, idList)` | 区分全选和半选节点 ID,用于树形权限配置 | `const { checkedIdList, halfCheckedIdList } = findCheckedTreeNode(nodes, ids)` |
| `arrayIsFullContainOtherArray(src, search)` | 判断数组是否完全包含另一数组的所有元素 | `arrayIsFullContainOtherArray([1,2,3], [1,2]) // true` |
| `arrayIsContainOtherArray(src, search)` | 判断数组是否包含另一数组的至少一个元素 | `arrayIsContainOtherArray([1,2], [2,3]) // true` |
| `sortTreeNodes(nodeList, property)` | 树节点按指定字段递归排序(默认 `sort`) | `sortTreeNodes(menus)` |
| `getTreeFullPathTitle(treeData, targetId, property, separate)` | 获取树节点全路径 label 字符串(如 `A > B > C`) | `getTreeFullPathTitle(tree, id)` |
| `getTreeFullPathArray(treeData, target, property)` | 获取树节点全路径节点数组 | `getTreeFullPathArray(tree, id)` |
| `filterTreeNode(treeData, targetIds)` | 过滤树中指定 id 的节点 | `filterTreeNode(tree, removeIds)` |
| `doDownLoadFile(action, method, params, token)` | 通过动态创建表单的方式下载文件(支持携带 token) | `doDownLoadFile('/api/export', 'get', {}, token)` |
| `isMobileDevice()` | 通过 UserAgent 判断当前是否为移动设备 | `if (isMobileDevice()) ...` |
| `isProd()` | 判断当前是否为生产环境 | `if (isProd()) ...` |
| `getPublicPath(originPath)` | 获取 public 目录下的静态资源绝对路径 | `getPublicPath('logo/logo.png')` |
#### propertiesTools.js — 属性配置工具方法
| 方法 | 说明 | 用法 |
|------|------|------|
| `optionText(options, value)` | 根据 value 在选项列表中查找对应的 text | `optionText([{value:'1',text:'男'}], '1') // '男'` |
| `getInfoValue(infoList, name)` | 从 info 列表中按 name 获取 value | `getInfoValue(infoList, 'email')` |
| `getInfo(fullInfoList)` | 提取列表中的 `{name, value}` 对,用于序列化提交 | `getInfo(fullList)` |
| `getFieldValue(infoList, property)` | 根据字段类型(date/datetime/select/radio)格式化后获取显示值 | `getFieldValue(infoList, property)` |
| `getFullInfoList(properties, infoList, showDefault)` | 将 Properties 配置与已有值合并,生成完整字段列表 | `getFullInfoList(props, values, true)` |
| `getFilterFullInfoList(properties, infoList, showDefault)` | 同上,但只返回 `filterable=true` 的字段 | `getFilterFullInfoList(props, values)` |
| `validateFullInfoValue(fullInfoList)` | 校验完整字段列表,必填项为空时弹出错误提示并返回 false | `if (!validateFullInfoValue(list)) return` |
| `getProperties(referenceCode, referenceId)` | 远程获取 Properties 配置(支持 collection 递归展开),返回按 displayOrder 排序的字段列表 | `const props = await getProperties('user_form')` |
| `parseFormData(formData)` | 解析 formData(兼容 JSON 字符串和数组格式)为 `[{name, value}]` 数组 | `parseFormData(jsonString)` |
| `getProcessFormFields(referenceCode, referenceId, formData, showDefault)` | 组合方法:获取 Properties 配置并与 formData 合并,返回可直接渲染的完整字段列表 | `const fields = await getProcessFormFields('code', id, formData)` |
| `serializeWorkFlowFormData(fullInfoList)` | 将完整字段列表序列化为流程 formData 格式(JSON 字符串),与后端 WorkFlowDraft.formData 一致 | `const json = serializeWorkFlowFormData(fields)` |
### 组合式函数
项目通用组合式函数位于 `src/composables` 目录:
#### useDarkMode — 显示模式
管理应用的显示模式,支持浅色 / 深色 / 跟随系统三种模式。
* 导出:
| 导出 | 类型 | 说明 |
|------|------|------|
| `useDarkMode()` | Function | 组合式函数,返回 `{ mode, isDark }`,供组件在 setup 中使用 |
| `applyTheme()` | Function | 纯函数,读取当前模式并立即应用 `.dark` class 到 DOM,供 app 启动时调用 |
* `useDarkMode()` 返回值:
| 属性 | 类型 | 说明 |
|------|------|------|
| `mode` | `Ref<'light'\|'dark'\|'system'>` | 当前显示模式,双向绑定,持久化到 localStorage |
| `isDark` | `ComputedRef` | 当前是否为深色(system 模式下自动跟随系统偏好) |
* 使用示例:
```js
// 组件中响应式使用
const { mode, isDark } = useDarkMode()
// app 启动时同步调用(防止闪烁)
import { applyTheme } from '@/composables/useDarkMode'
applyTheme()
```
#### useCrudTable — CRUD 表格页面通用逻辑
封装“搜索抽屉 + 表格 + 分页 + 多选”的通用逻辑,适用于所有基于 el-table 的列表管理页面。
* 参数:
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `apiMethod` | Function | — | API 请求方法,接收 `searchParam` 返回 Promise |
| `buildExtra` | Function | `() => ({})` | 构建页面独有搜索参数 |
| `processData` | Function | `null` | 对返回的 content 数组做后处理 |
| `beforeSearch` | Function | `null` | 搜索前置守卫,返回 false 则跳过请求 |
| `defaultOrder` | String | `'id'` | 默认排序字段 |
| `defaultDir` | String | `'descending'` | 默认排序方向 |
| `searchOnMount` | Boolean | `true` | 是否初始化时自动搜索 |
* 返回值:`searchForm`、`table`(模板引用)、`t`、`appStore`、`searchDrawer`、`modal_loading`、`searchData`、`selectedData`、`searchFormModel`、`isMobile`、`pickerShortcuts`、`handleSearch`、`handleSortChange`、`handlePageSizeSearch`、`handleSearchReset`、`handleSelect`、`selectableFun`、`handleRowClick`、`openSearchDrawer`
* 使用示例:
```js
const {
searchForm, table, t, appStore,
searchDrawer, modal_loading, searchData, selectedData,
searchFormModel, isMobile, pickerShortcuts,
handleSearch, handleSortChange, handlePageSizeSearch,
handleSearchReset, handleSelect, selectableFun, handleRowClick,
openSearchDrawer
} = useCrudTable({
apiMethod: (param) => api.request.user.query(param),
buildExtra: (form) => ({ name: form.name }),
defaultOrder: 'sort',
defaultDir: 'ascending'
})
```
### 静态资源
* 路径:`src/assets`
## 部署
### 编译打包
工程根目录下运行:
* test 环境
```bash
npm run build-test
```
* prod 环境
```bash
npm run build-prod
```
执行成功后工程根目录下会出现 `dist` 文件夹,将 `dist` 文件夹中的所有文件复制到 nginx 的 html 下即可。
* 如果部署在 nginx 里 html 的根目录,访问 url 为 http://nginxHost:port
* 如果部署在 nginx 里 html 的子目录(如 platform/admin),访问 url 为 http://nginxHost:port/platform/admin
构建产物同时包含 `.gz` 压缩文件(由 vite-plugin-compression 生成),nginx 开启 `gzip_static on` 可直接使用预压缩文件,降低服务器实时压缩开销。
### nginx 配置
假如工程部署在 nginx 中,需要修改 nginx.conf,增加后端接口的反向代理:
* 代理后端 gateway
```nginx
location ~ ^.*/v1/api/ {
proxy_pass http://apiBackend$backend_raw_uri;
proxy_set_header host $http_host;
proxy_set_header x-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
}
```
* 启用预压缩静态资源(可选)
```nginx
gzip_static on;
```
## 界面展示
* 登录

* 首页


* 深色模式

* 头像裁剪

* 字典配置

* 文件在线预览

* json在线编辑

* 富文本编辑

* SolonFlow编辑器

* 机构配置

* 角色配置

* 用户配置

* 用户选择组件

* 属性配置


* 流程管理

* 流程草稿

* 流程查看

* 流程任务处理

* 流程查看

* 404 页面

* 500 页面

* demo
- 文件上传

## 相关文档
* [Vue 3](https://cn.vuejs.org/)
* [Vite](https://cn.vite.dev/)
* [Pinia](https://pinia.vuejs.org/zh/)
* [Vue Router](https://router.vuejs.org/zh/)
* [Element Plus](https://element-plus.org/zh-CN/)
* [Vue I18n](https://vue-i18n.intlify.dev/)
* [@vueuse/core](https://vueuse.org/)
* [ECharts](https://echarts.apache.org/zh/)
* [Fluent Editor](https://opentiny.github.io/fluent-editor/)
* [Cropper.js](https://cropperjs.github.io/)
* [Flyfish Viewer](https://doc.flyfish.dev/)
* [MSW](https://mswjs.io/)
## 开源协议
[Apache-2.0](LICENSE)
### Customize configuration
See [Configuration Reference](https://cn.vite.dev/config/).