Compare commits
6 Commits
b724969912
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48cfebd008 | ||
|
|
2cc8329695 | ||
|
|
0f3b5d4371 | ||
|
|
000b87eb92 | ||
|
|
8a4511dc36 | ||
|
|
3eaea3116d |
2
.env
2
.env
@@ -4,7 +4,7 @@
|
||||
VUE_APP_TITLE=D2Admin
|
||||
|
||||
# 网络请求公用地址
|
||||
VUE_APP_API=/api/
|
||||
VUE_APP_API=/background/v1/
|
||||
|
||||
# 仓库地址
|
||||
VUE_APP_REPO=https://github.com/d2-projects/d2-admin
|
||||
|
||||
@@ -2,3 +2,6 @@
|
||||
|
||||
# 页面 title 前缀
|
||||
VUE_APP_TITLE=D2Admin Dev
|
||||
|
||||
# 后台接口地址(代理目标)
|
||||
VUE_APP_BASE_URL=http://127.0.0.1:8787/background/
|
||||
|
||||
424
README.md
424
README.md
@@ -1,192 +1,270 @@
|
||||

|
||||
# MES-UI
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/d2-projects/d2-admin/stargazers" target="_blank"><img src="https://img.shields.io/github/stars/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/network/members" target="_blank"><img src="https://img.shields.io/github/forks/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/issues" target="_blank"><img src="https://img.shields.io/github/issues/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/issues?q=is%3Aissue+is%3Aclosed" target="_blank"><img src="https://img.shields.io/github/issues-closed/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/pulls" target="_blank"><img src="https://img.shields.io/github/issues-pr/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/pulls?q=is%3Apr+is%3Aclosed" target="_blank"><img src="https://img.shields.io/github/issues-pr-closed/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank"><img src="https://img.shields.io/github/last-commit/d2-projects/d2-admin.svg"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank"><img src="https://visitor-badge.glitch.me/badge?page_id=d2-projects.d2-admin"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/releases" target="_blank"><img src="https://img.shields.io/github/release/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://deepscan.io/dashboard#view=project&tid=8014&pid=10161&bid=136697"><img src="https://deepscan.io/api/teams/8014/projects/10161/branches/136697/badge/grade.svg" alt="DeepScan grade"></a>
|
||||
</p>
|
||||
基于 [D2 Admin](https://github.com/d2-projects/d2-admin)(Vue 2 + Element UI)的企业级中后台前端项目。
|
||||
|
||||
[D2Admin](https://github.com/d2-projects/d2-admin) is a fully open source and free enterprise back-end product front-end integration solution, using the latest front-end technology stack, javascript files loading of local first screen less than 60kb, has prepared most of the project preparations, and with a lot of sample code to help the management system agile development.
|
||||
---
|
||||
|
||||
[](https://open.vscode.dev/d2-projects/d2-admin)
|
||||
## 项目目录结构
|
||||
|
||||
[中文](https://github.com/d2-projects/d2-admin/blob/master/README.zh.md) | **English**
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
[](https://app.netlify.com/sites/d2-admin/deploys)
|
||||
|
||||
The following access addresses are built and deployed by the latest master branch code at the same time. The access effect is completely consistent. Please select the appropriate access link according to your own network situation.
|
||||
|
||||
| server | link | server |
|
||||
| --- | --- | --- |
|
||||
| d2.pub | [Link](https://d2.pub/d2-admin/preview) | China server |
|
||||
| github | [Link](https://d2-projects.github.io/d2-admin) | GitHub pages |
|
||||
| netlify | [Link](https://d2-admin.netlify.com) | Netlify CDN |
|
||||
|
||||
## Document
|
||||
|
||||
[document on https://d2.pub](https://d2.pub/doc/d2-admin/)
|
||||
|
||||
## Features
|
||||
|
||||
* Build with vue-cli3
|
||||
* First screen loading waiting animation
|
||||
* Five themes
|
||||
* Built-in UEditor rich text editor
|
||||
* Detailed documentation
|
||||
* Login and logout
|
||||
* Separate routing and menu settings
|
||||
* Foldable sidebar
|
||||
* Multi-national language
|
||||
* Rich text editor
|
||||
* Markdown editor
|
||||
* full screen
|
||||
* Fontawesome icon library
|
||||
* Icon selector
|
||||
* Automatically register SVG icon
|
||||
* Simulation data
|
||||
* Clipboard package
|
||||
* Chart library
|
||||
* Time and date calculation tool
|
||||
* Import Excel ( xlsx + csv )
|
||||
* Data export Excel ( xlsx + csv )
|
||||
* Data export text
|
||||
* Digital animation
|
||||
* Drag and drop the size of the block layout
|
||||
* Grid layout for drag and resize and position
|
||||
* Out-of-the-box page layout components
|
||||
* Load and parse markdown files
|
||||
* GitHub style markdown display component
|
||||
* markdown internal code highlighting
|
||||
* Expanded Baidu cloud link resolution and optimized display for markdown
|
||||
* Right click menu component
|
||||
* Custom scrollbars and scrolling controls
|
||||
* Common style extraction, convenient theme customization
|
||||
* Support temporary menu configuration
|
||||
* System function display module `1.1.4 +`
|
||||
* Multi-tab mode `1.1.4 +`
|
||||
* Beautify the scroll bar `1.1.4 +`
|
||||
* json view `1.1.4 +`
|
||||
* cookie wrapper `1.1.5 +`
|
||||
* Multi-tab global control API `1.1.5 +`
|
||||
* Menu Global Control API `1.1.5 +`
|
||||
* Multi-tab page close control support right-click menu `1.1.10 +`
|
||||
* Modular global state management `1.2.0 +`
|
||||
* Multiple data persistence methods: distinguish users, distinguish routes, page data snapshot function `1.2.0 +`
|
||||
* Support for menu system that jumps out of external links `1.2.0 +`
|
||||
* Support menu svg icon `1.3.0 +`
|
||||
* Logging and error catching `1.3.0 +`
|
||||
* Global menu search `1.3.0 +`
|
||||
* Custom login redirect `1.3.0 +`
|
||||
* Switch global base component size `1.4.0 +`
|
||||
* Page loading progress bar `1.4.1 +`
|
||||
* Adaptive top menu bar `1.4.7 +`
|
||||
* Support for merging cells when exporting xslx `1.5.4 +`
|
||||
* Multiple tabs support drag and drop sorting `1.8.0 +`
|
||||
* load only local JavaScript code less than 60kb on the homepage `1.8.0 +`
|
||||
* Built in build file volume checking tool `1.8.0 +`
|
||||
* Example of multi page `1.23.0 +`
|
||||
* Split chunks `1.23.0 +`
|
||||
|
||||
## Other synchronous repositories
|
||||
|
||||
| type | link |
|
||||
| --- | --- |
|
||||
| gitee | [https://gitee.com/d2-projects/d2-admin](https://gitee.com/d2-projects/d2-admin) |
|
||||
| coding | [https://d2-projects.coding.net/p/d2-projects/d/d2-admin/git](https://d2-projects.coding.net/p/d2-projects/d/d2-admin/git) |
|
||||
|
||||
## Other versions
|
||||
|
||||
| Name | HomePage | Preview | Introduction |
|
||||
| --- | --- | --- | --- |
|
||||
| Starter template | [Link](https://github.com/d2-projects/d2-admin-start-kit) | [Link](https://d2.pub/d2-admin-start-kit/preview) | The simplest version |
|
||||
|
||||
## Open source backend implementation
|
||||
|
||||
> The backend is contributed by the open source community. The latest version of D2Admin is not guaranteed. Please contact its open source author for related usage issues.
|
||||
|
||||
| Name | technology | HomePage | Preview | Introduction |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| django-vue-admin-pro | Django | [Link](https://github.com/dvadmin-pro/django-vue-admin-pro) | [Link](http://demo.pro.django-vue-admin.com) | Django + Jwt + D2Admin |
|
||||
| boot-admin | SpringBoot | [Link](https://github.com/hb0730/boot-admin) | [Link](http://admin.hb0730.com/) | Management system based on SpringBoot |
|
||||
| FlaskPermission | Flask | [Link](https://github.com/huguodong/flask-permission) | [Link](http://47.97.218.139:9999) | Permission management based on Flask |
|
||||
| CareyShop | ThinkPHP5 | [Link](https://github.com/dnyz520/careyshop-admin) | [Link](https://demo.careyshop.cn/admin/) | High Performance Mall Framework System for CareyShop |
|
||||
| jiiiiiin-security | Spring Boot | [Link](https://github.com/Jiiiiiin/jiiiiiin-security) | [Link](https://github.com/Jiiiiiin/jiiiiiin-security) | Content management infrastructure projects |
|
||||
| Taroco | Spring Cloud | [Link](https://github.com/liuht777/Taroco) | [Link](http://111.231.192.110/) | Complete microservice enterprise solution |
|
||||
| Aooms | Spring Cloud | [Link](https://gitee.com/cyb-javaer/Aooms) | [Link](https://www.yuboon.com/Aooms) | Extremely fast microservice development, not just as simple as JFinal |
|
||||
| GOA | Beego | [Link](https://github.com/Qsnh/goa) | [Link](http://goaio.vip/) | Online question answering system based on Beego + Vue |
|
||||
| CMDB | Django | [Link](https://github.com/CJFJack/django_vue_cmdb) | [Link](https://mp.weixin.qq.com/s?__biz=MzU1OTYzODA4Mw==&mid=2247484250&idx=1&sn=981024ac0580d8a3eba95742bd32b268) | authority system with dynamic menu |
|
||||
|
||||
## Community projects
|
||||
|
||||
> These projects are contributed by the open source community and are not guaranteed to use the latest version of D2Admin. Please contact their open source authors for related usage questions.
|
||||
|
||||
| Name | HomePage | Preview | Introduction |
|
||||
| --- | --- | --- | --- |
|
||||
| d2-admin-xiya-go-cms | [Link](https://github.com/d2-projects/d2-admin-xiya-go-cms) | [Link](https://d2.pub/d2-admin-xiya-go-cms/preview) | D2Admin + authority system + dynamic router |
|
||||
| d2-advance | [Link](https://github.com/d2-projects/d2-advance) | [Link](https://d2.pub/d2-advance/preview) | Technical exploration inspired by D2Admin |
|
||||
| d2-crud-plus | [Link](https://github.com/greper/d2-crud-plus) | [Link](http://qiniu.veryreader.com/D2CrudPlusExample/index.html) | Easy development of crud function |
|
||||
| d2-crud | [Link](https://github.com/d2-projects/d2-crud) | [Link]() | Encapsulation of common operations in tables |
|
||||
| d2-admin-pm | [Link](https://github.com/wjkang/d2-admin-pm) | [Link](http://jaycewu.coding.me/d2-admin-pm) | RBAC privilege management solution based on D2Admin |
|
||||
| LanBlog | [Link](https://github.com/sinksmell/LanBlog) | [Link](http://47.101.222.133/) | Vue + Beego restful api personal blog system |
|
||||
| d2-admin-start-kit-plus | [Link](https://github.com/hank-cp/d2-admin-start-kit-plus) | [Link](https://github.com/hank-cp/d2-admin-start-kit-plus) | D2Admin Start kit modular version |
|
||||
| d2-ribbons | [Link](https://github.com/d2-projects/d2-ribbons) | [Link](https://github.com/d2-projects/d2-ribbons) | Open source project logo Library |
|
||||
|
||||
## Badge
|
||||
|
||||
If your open source project is based on D2Admin development, please add the following badge to your README:
|
||||
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/d2-projects/d2-admin/master/docs/image/d2-admin@2x.png" width="200">
|
||||
</a>
|
||||
|
||||
Copy the following code into the README to:
|
||||
|
||||
``` html
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank"><img src="https://raw.githubusercontent.com/d2-projects/d2-admin/master/docs/image/d2-admin@2x.png" width="200"></a>
|
||||
```
|
||||
mes-ui/
|
||||
├── .github/ # GitHub 相关配置
|
||||
├── docs/ # 项目文档
|
||||
├── public/ # 静态资源(不经过 webpack 处理)
|
||||
├── src/ # PC 端业务源码
|
||||
│ ├── api/ # 接口请求层
|
||||
│ ├── assets/ # 静态资源(经 webpack 处理)
|
||||
│ ├── components/ # 公共组件
|
||||
│ ├── composables/ # 可复用逻辑函数
|
||||
│ ├── layout/ # 页面布局
|
||||
│ ├── libs/ # 工具函数
|
||||
│ ├── locales/ # 国际化语言包
|
||||
│ ├── menu/ # 菜单配置
|
||||
│ ├── plugin/ # Vue 插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── store/ # Vuex 状态管理
|
||||
│ └── views/ # 业务页面
|
||||
├── src.mobile/ # 移动端业务源码
|
||||
├── .browserslistrc # 浏览器兼容配置
|
||||
├── .editorconfig # 编辑器统一配置
|
||||
├── .env / .env.development / .env.preview # 环境变量
|
||||
├── .eslintignore / .eslintrc.js # ESLint 配置
|
||||
├── .gitignore # Git 忽略配置
|
||||
├── .postcssrc.js # PostCSS 配置
|
||||
├── babel.config.js # Babel 配置
|
||||
├── jest.config.js # Jest 单元测试配置
|
||||
├── jsconfig.json # VS Code 路径别名提示
|
||||
├── package.json # 项目依赖与脚本
|
||||
├── pnpm-lock.yaml # pnpm 依赖锁定文件
|
||||
├── vue.config.js # Vue CLI 构建配置
|
||||
└── README.md # 项目说明(本文件)
|
||||
```
|
||||
|
||||
At the same time, you can report your project to us. We will place the excellent project in D2Admin and help you publicize it.
|
||||
---
|
||||
|
||||
## Contributor
|
||||
## 各目录详解
|
||||
|
||||
* [@FairyEver](https://github.com/FairyEver)
|
||||
* [@sunhaoxiang](https://github.com/sunhaoxiang)
|
||||
* [@Aysnine](https://github.com/Aysnine)
|
||||
* [@luchaohai](https://github.com/luchaohai)
|
||||
* [@han-feng](https://github.com/han-feng)
|
||||
* [@rongxingsun](https://github.com/rongxingsun)
|
||||
* [@dnyz520](https://github.com/dnyz520)
|
||||
### `.github/`
|
||||
|
||||
## Become a sponsor
|
||||
GitHub 相关配置,包含 Issue 模板和 CI/CD 工作流(`workflows/`)。
|
||||
|
||||
[Sponsor me on afdian.net](https://afdian.net/@fairyever)
|
||||
### `docs/`
|
||||
|
||||
## Sponsor
|
||||
项目文档目录,存放变更日志、修复记录等 Markdown 文档。子目录 `image/` 存放文档引用的图片。
|
||||
|
||||
**cochlea** | **Baron** | **苦行僧** | **吴地安宁** | **KingDong** | **sunyongmofang**
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [表格组件使用说明](./docs/表格组件使用说明.md) | page-table / page-dialog-form 的完整使用手册 |
|
||||
| [sct-base-table 重构方案](./docs/sct-base-table-refactor-design.md) | 旧 sct-base-table → 新架构的设计文档 |
|
||||
| [sass-deprecation-fixes.md](./docs/sass-deprecation-fixes.md) | Sass 废弃警告修复记录 |
|
||||
| [CHANGELOG.md](./docs/CHANGELOG.md) | 项目变更日志 |
|
||||
|
||||
## Visitor
|
||||
### `public/`
|
||||
|
||||

|
||||
**静态资源目录**,文件会原样复制到构建产物中,不经过 webpack 编译。
|
||||
|
||||
> Total visitor since 2019.08.27
|
||||
| 子目录 | 作用 |
|
||||
|--------|------|
|
||||
| `lib/UEditor/` | 百度 UEditor 富文本编辑器(第三方库,通过 `<script>` 直接引入) |
|
||||
| `image/theme/` | 各主题的 Logo 图片和预览图(chester / d2 / element / line / star / tomorrow-night-blue / violet) |
|
||||
| `image/loading/` | 页面加载动画的 SVG |
|
||||
| `html/` | 独立 HTML 页面(如 demo.html) |
|
||||
| `index.html` | SPA 入口 HTML 模板 |
|
||||
| `icon.ico` | 网站 Favicon |
|
||||
|
||||
## Star history
|
||||
### `src/` — PC 端源码
|
||||
|
||||
[](https://starchart.cc/d2-projects/d2-admin)
|
||||
#### `src/api/`
|
||||
**接口请求层**。封装后端 API 调用,按业务模块拆分为独立文件。
|
||||
- `_service.js` — 基于 axios 的请求服务实例
|
||||
- `_tools.js` — 请求工具函数
|
||||
- `demo.js` — 演示接口
|
||||
- `sys.user.js` — 用户相关接口
|
||||
|
||||
## License
|
||||
#### `src/assets/`
|
||||
**静态资源**,会被 webpack 处理。
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fd2-projects%2Fd2-admin?ref=badge_large)
|
||||
| 子目录 | 作用 |
|
||||
|--------|------|
|
||||
| `style/unit/color.scss` | 全局颜色变量定义 |
|
||||
| `style/public.scss` | 全局 SCSS 变量、mixin、placeholder(通过 `additionalData` 自动注入每个组件) |
|
||||
| `style/public-class.scss` | 全局通用 CSS class(间距、浮动、文字对齐等工具类) |
|
||||
| `style/fixed/` | 第三方库样式覆盖(element / markdown / n-progress / vue-splitpane 等) |
|
||||
| `style/animate/` | 过渡动画样式(vue-transition) |
|
||||
| `style/theme/` | 主题切换样式,每个主题独立目录,含 `index.scss` + `setting.scss` |
|
||||
| `svg-icons/icons/` | SVG 图标文件,通过 `svg-sprite-loader` 生成雪碧图 |
|
||||
| `svg-icons/index.js` | SVG 图标自动注册脚本 |
|
||||
|
||||
#### `src/components/`
|
||||
**公共组件库**,所有可复用组件集中管理。
|
||||
|
||||
| 组件 | 作用 |
|
||||
|------|------|
|
||||
| `d2-container` | 页面容器组件,提供卡片、全屏、透明等多种布局变体 |
|
||||
| `d2-container-frame` | iframe 嵌套外部页面容器 |
|
||||
| `d2-count-up` | 数字滚动动画组件 |
|
||||
| `d2-highlight` | 代码高亮展示组件 |
|
||||
| `d2-icon` / `d2-icon-svg` | 图标组件(Font Awesome / SVG) |
|
||||
| `d2-icon-select` / `d2-icon-svg-select` | 图标选择器组件 |
|
||||
| `d2-link-btn` | 链接式按钮 |
|
||||
| `d2-markdown` | Markdown 渲染组件 |
|
||||
| `d2-module-index-banner` | 首页 Banner 展示组件 |
|
||||
| `d2-module-index-menu` | 首页菜单组件 |
|
||||
| `d2-quill` | Quill 富文本编辑器封装 |
|
||||
| `d2-scrollbar` | 自定义滚动条组件 |
|
||||
| `d2-ueditor` | 百度 UEditor 编辑器封装 |
|
||||
| `page-table` | **新** CRUD 表格便捷组合体(按钮栏 + 表格 + 分页 + 高度自适应 + 帮助按钮) |
|
||||
| `page-dialog-form` | **新** 增删改查弹框组件(表单渲染 + 校验 + i18n) |
|
||||
|
||||
> `page-table` 和 `page-dialog-form` 的详细用法见:[表格组件使用说明](./docs/表格组件使用说明.md)
|
||||
|
||||
#### `src/composables/`
|
||||
**可复用的逻辑函数(Composition Utilities)**。把散落在各页面中的重复 JS 逻辑抽成纯函数,组件只需调用即可。
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `useTableColumns.js` | 列定义工厂,消除手动分配 `idx` 序号;约定 `prop: '_actions'` 为操作列 |
|
||||
| `useTableButtons.js` | 按钮定义工厂,一键生成顶部工具栏 + 行内操作按钮,自动注入权限过滤 |
|
||||
| `useI18n.js` | i18n Mixin 工厂,通过 `i18nMixin(prefix)` 注入 `key()` / `ckey()` 方法,消除每个页面手写 `T` 常量 |
|
||||
|
||||
> **为什么放在 `src/` 根目录?**
|
||||
> composable 不是 UI 组件(不需要 `<template>`),也不属于 `libs/`(通用工具库)。它是介于**组件和逻辑之间**的中间层——被多个组件调用,但本身不渲染 DOM。放在 `src/` 根目录下与 `components/`、`views/` 同级,方便一眼看到项目的公共逻辑层。
|
||||
|
||||
#### `src/layout/`
|
||||
**页面布局**。当前使用 `header-aside` 布局(顶栏 + 侧边栏 + 主内容区)。
|
||||
|
||||
| 子目录 | 作用 |
|
||||
|--------|------|
|
||||
| `layout.vue` | 布局主文件 |
|
||||
| `components/menu-side/` | 侧边菜单组件 |
|
||||
| `components/menu-header/` | 顶部导航菜单组件 |
|
||||
| `components/header-user/` | 顶栏用户信息下拉 |
|
||||
| `components/header-theme/` | 主题切换面板 |
|
||||
| `components/header-color/` | 主题色切换 |
|
||||
| `components/header-locales/` | 多语言切换 |
|
||||
| `components/header-fullscreen/` | 全屏切换 |
|
||||
| `components/header-search/` | 全局搜索 |
|
||||
| `components/header-log/` | 操作日志 |
|
||||
| `components/header-size/` | 字号大小切换 |
|
||||
| `components/tabs/` | 多标签页栏 |
|
||||
| `components/contextmenu/` | 标签页右键菜单 |
|
||||
| `components/panel-search/` | 全局搜索面板 |
|
||||
| `mixins/search.js` | 搜索 mixin |
|
||||
| `mixins/` | 菜单相关 mixin |
|
||||
|
||||
#### `src/libs/`
|
||||
**工具函数库**。
|
||||
- `util.js` — 通用工具方法
|
||||
- `util.cookies.js` — Cookie 操作
|
||||
- `util.db.js` — 本地存储(基于 lowdb)
|
||||
- `util.log.js` — 日志工具
|
||||
- `util.import.development.js` / `util.import.production.js` — 开发/生产环境动态加载
|
||||
|
||||
#### `src/locales/`
|
||||
**国际化(i18n)**。JSON 格式的语言文件,支持:
|
||||
- `zh-chs` — 简体中文
|
||||
- `zh-cht` — 繁体中文
|
||||
- `en` — 英文
|
||||
- `ja` — 日文
|
||||
- `mixin.js` — Vue 国际化 mixin
|
||||
|
||||
#### `src/menu/`
|
||||
**菜单配置**。按业务模块拆分,定义侧边栏菜单结构。`modules/` 下按 demo-components、demo-playground、demo-plugins 组织。
|
||||
|
||||
#### `src/plugin/`
|
||||
**Vue 插件**。
|
||||
- `d2admin/` — D2Admin 核心插件(初始化全局功能)
|
||||
- `error/` — 全局错误捕获
|
||||
- `log/` — 通用日志输出
|
||||
- `open/` — 新窗口打开工具
|
||||
|
||||
#### `src/router/`
|
||||
**路由配置**。`modules/` 下按 components、playground、plugins 拆分路由表。`routes.js` 为汇总路由,`index.js` 创建 Vue Router 实例。
|
||||
|
||||
#### `src/store/`
|
||||
**Vuex 状态管理**。`modules/d2admin/` 下按功能拆分为独立模块:
|
||||
|
||||
| 模块 | 作用 |
|
||||
|------|------|
|
||||
| `account` | 用户账户信息 |
|
||||
| `color` | 主题色管理 |
|
||||
| `db` | 本地数据库状态 |
|
||||
| `fullscreen` | 全屏状态 |
|
||||
| `gray` | 灰度模式 |
|
||||
| `log` | 操作日志 |
|
||||
| `menu` | 菜单状态(展开/收缩/激活项) |
|
||||
| `page` | 页面缓存与标签页 |
|
||||
| `releases` | 版本信息 |
|
||||
| `search` | 全局搜索 |
|
||||
| `size` | 字号大小 |
|
||||
| `theme` | 主题切换 |
|
||||
| `transition` | 页面过渡动画 |
|
||||
| `ua` | 用户代理信息 |
|
||||
| `user` | 用户登录态 |
|
||||
|
||||
#### `src/views/`
|
||||
**页面视图**。
|
||||
- `demo/` — 各功能展示页面(组件演示 / 插件演示 / 功能试验场)
|
||||
- `system/` — 系统级页面(首页 / 登录 / 404 / 日志 / 重定向 / 刷新)
|
||||
- `production-master-data/` — 生产配置模块(工厂区域 / 产线 / 工艺 / 产品 / 物料等)
|
||||
|
||||
> 页面按一级模块 → 二级模块 → 三级模块 三层目录组织,与 [后台Webman界面截图对照表](./后台Webman界面截图对照表.md) 中的英文命名一致。
|
||||
|
||||
#### `src/` 根文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `main.js` | PC 端应用入口 |
|
||||
| `App.vue` | 根组件 |
|
||||
| `setting.js` | 全局设置(站点标题等) |
|
||||
| `i18n.js` | 国际化初始化 |
|
||||
|
||||
---
|
||||
|
||||
### `src.mobile/` — 移动端源码
|
||||
|
||||
基于 Vant UI 的移动端适配版本,与 PC 端共用部分业务逻辑。
|
||||
|
||||
| 文件/目录 | 作用 |
|
||||
|-----------|------|
|
||||
| `main.js` | 移动端应用入口 |
|
||||
| `App.vue` | 移动端根组件 |
|
||||
| `vant.js` | Vant UI 按需引入配置 |
|
||||
| `router/` | 移动端路由 |
|
||||
| `store/` | 移动端状态管理 |
|
||||
| `views/` | 移动端页面 |
|
||||
|
||||
---
|
||||
|
||||
### 构建配置文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `vue.config.js` | Vue CLI 构建配置(多页入口、CDN、webpack 插件、主题色替换、SVG sprites、路径别名) |
|
||||
| `babel.config.js` | Babel 转译配置 |
|
||||
| `.postcssrc.js` | PostCSS 自动前缀等配置 |
|
||||
| `.eslintrc.js` | ESLint 代码规范 |
|
||||
| `jest.config.js` | 单元测试框架配置 |
|
||||
| `jsconfig.json` | VS Code 路径别名智能提示(`@` → `src/`) |
|
||||
| `.browserslistrc` | 目标浏览器范围 |
|
||||
| `.editorconfig` | 编辑器缩进/换行风格统一 |
|
||||
|
||||
### 环境变量文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `.env` | 所有环境通用变量 |
|
||||
| `.env.development` | 开发环境变量 |
|
||||
| `.env.preview` | 预发布环境变量 |
|
||||
|
||||
---
|
||||
|
||||
## 常用命令
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `pnpm serve` | 启动开发服务器 |
|
||||
| `pnpm build` | 生产环境构建 |
|
||||
| `pnpm build:preview` | 预发布环境构建 |
|
||||
| `pnpm lint` | ESLint 检查并自动修复 |
|
||||
| `pnpm test:unit` | 运行单元测试 |
|
||||
|
||||
205
README.zh.md
205
README.zh.md
@@ -1,205 +0,0 @@
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/d2-projects/d2-admin/stargazers" target="_blank"><img src="https://img.shields.io/github/stars/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/network/members" target="_blank"><img src="https://img.shields.io/github/forks/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/issues" target="_blank"><img src="https://img.shields.io/github/issues/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/issues?q=is%3Aissue+is%3Aclosed" target="_blank"><img src="https://img.shields.io/github/issues-closed/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/pulls" target="_blank"><img src="https://img.shields.io/github/issues-pr/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/pulls?q=is%3Apr+is%3Aclosed" target="_blank"><img src="https://img.shields.io/github/issues-pr-closed/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank"><img src="https://img.shields.io/github/last-commit/d2-projects/d2-admin.svg"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank"><img src="https://visitor-badge.glitch.me/badge?page_id=d2-projects.d2-admin"></a>
|
||||
<a href="https://github.com/d2-projects/d2-admin/releases" target="_blank"><img src="https://img.shields.io/github/release/d2-projects/d2-admin.svg"></a>
|
||||
<a href="https://deepscan.io/dashboard#view=project&tid=8014&pid=10161&bid=136697"><img src="https://deepscan.io/api/teams/8014/projects/10161/branches/136697/badge/grade.svg" alt="DeepScan grade"></a>
|
||||
</p>
|
||||
|
||||
[D2Admin](https://github.com/d2-projects/d2-admin) 是一个完全 **开源免费** 的企业中后台产品前端集成方案,使用最新的前端技术栈,小于 60kb 的本地首屏 js 加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助力管理系统敏捷开发。
|
||||
|
||||
[](https://open.vscode.dev/d2-projects/d2-admin)
|
||||
|
||||
**中文** | [English](https://github.com/d2-projects/d2-admin)
|
||||
|
||||
## 预览
|
||||
|
||||

|
||||
[](https://app.netlify.com/sites/d2-admin/deploys)
|
||||
|
||||
下列访问地址均由最新的 master 分支代码同时构建部署,访问效果完全一致,请根据自身网络情况选择合适的访问链接。
|
||||
|
||||
| 位置 | 链接 | 部署位置 |
|
||||
| --- | --- | --- |
|
||||
| d2.pub | [preview](https://d2.pub/d2-admin/preview) | 中国服务器 |
|
||||
| github | [preview](https://d2-projects.github.io/d2-admin) | GitHub pages |
|
||||
| netlify | [preview](https://d2-admin.netlify.com) | Netlify CDN |
|
||||
|
||||
## 文档
|
||||
|
||||
[在 https://d2.pub 上的本项目文档](https://d2.pub/doc/d2-admin/)
|
||||
|
||||
## 功能
|
||||
|
||||
* 使用 vue-cli3 构建
|
||||
* 首屏加载等待动画
|
||||
* 五款主题
|
||||
* 内置 UEditor 富文本编辑器
|
||||
* 详细的文档
|
||||
* 登录和注销
|
||||
* 分离的路由和菜单设置
|
||||
* 可折叠侧边栏
|
||||
* 多国语
|
||||
* 富文本编辑器
|
||||
* Markdown 编辑器
|
||||
* 全屏
|
||||
* Fontawesome 图标库
|
||||
* 图标选择器
|
||||
* 自动注册 SVG 图标
|
||||
* 模拟数据
|
||||
* 剪贴板封装
|
||||
* 图表库
|
||||
* 时间日期计算工具
|
||||
* 导入 Excel ( xlsx + csv )
|
||||
* 数据导出 Excel ( xlsx + csv )
|
||||
* 数据导出文本
|
||||
* 数字动画
|
||||
* 可拖拽调整大小的区块布局
|
||||
* 可拖拽调整大小和位置的网格布局
|
||||
* 开箱即用的页面布局组件
|
||||
* 加载并解析 markdown 文件
|
||||
* GitHub 样式的 markdown 显示组件
|
||||
* markdown 内代码高亮
|
||||
* 为 markdown 扩展了百度云链接解析和优化显示
|
||||
* 右键菜单组件
|
||||
* 自定义滚动条和滚动控制
|
||||
* 公用样式抽离,方便的主题定制
|
||||
* 支持临时菜单配置
|
||||
* 系统功能展示模块 `1.1.4 +`
|
||||
* 多标签页模式 `1.1.4 +`
|
||||
* 美化滚动条 `1.1.4 +`
|
||||
* json view `1.1.4 +`
|
||||
* cookie 封装 `1.1.5 +`
|
||||
* 多标签页全局控制 API `1.1.5 +`
|
||||
* 菜单全局控制 API `1.1.5 +`
|
||||
* 多标签页关闭控制支持右键菜单 `1.1.10 +`
|
||||
* 模块化全局状态管理 `1.2.0 +`
|
||||
* 多种数据持久化方式:区分用户,区分路由,页面数据快照功能 `1.2.0 +`
|
||||
* 支持跳出外部链接的菜单系统 `1.2.0 +`
|
||||
* 支持菜单 svg 图标 `1.3.0 +`
|
||||
* 日志记录和错误捕捉 `1.3.0 +`
|
||||
* 全局菜单搜索 `1.3.0 +`
|
||||
* 自定义登录重定向 `1.3.0 +`
|
||||
* 切换全局基础组件尺寸 `1.4.0 +`
|
||||
* 页面载入进度条 `1.4.1 +`
|
||||
* 自适应的顶部菜单栏 `1.4.7 +`
|
||||
* 数据导出 xslx 时支持合并单元格 `1.5.4 +`
|
||||
* 多标签页支持拖拽排序 `1.8.0 +`
|
||||
* 优化生产环境构建,首页只加载小于 60kb 的本地 js 代码 `1.8.0 +`
|
||||
* 内置了构建文件体积检查工具 `1.8.0 +`
|
||||
* 构建多页面示例 `1.23.0 +`
|
||||
* 分包优化 `1.23.0 +`
|
||||
|
||||
## 其它同步仓库
|
||||
|
||||
| 位置 | 链接 |
|
||||
| --- | --- |
|
||||
| 码云 | [https://gitee.com/d2-projects/d2-admin](https://gitee.com/d2-projects/d2-admin) |
|
||||
| coding | [https://d2-projects.coding.net/p/d2-projects/d/d2-admin/git](https://d2-projects.coding.net/p/d2-projects/d/d2-admin/git) |
|
||||
|
||||
> 如果您在 github 仓库下载很慢,可以尝试使用我们的码云仓库克隆代码
|
||||
|
||||
## 其它版本
|
||||
|
||||
| 名称 | 主页 | 预览 | 介绍 |
|
||||
| --- | --- | --- | --- |
|
||||
| 简化版模板 | [Github](https://github.com/d2-projects/d2-admin-start-kit) | [预览](https://d2.pub/d2-admin-start-kit/preview) | 无 |
|
||||
|
||||
## 开源后端实现
|
||||
|
||||
> 后端由开源社区贡献,不保证使用 D2Admin 最新版本,相关使用问题请联系其开源作者。
|
||||
|
||||
| 名称 | 技术 | 主页 | 预览 | 介绍 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| django-vue-admin-pro | Django | [Github](https://github.com/dvadmin-pro/django-vue-admin-pro) | [预览](http://demo.pro.django-vue-admin.com) | Django + Jwt + D2Admin |
|
||||
| boot-admin | SpringBoot | [Github](https://github.com/hb0730/boot-admin) | [预览](http://admin.hb0730.com/) | 基于 SpringBoot 前后端分离的后台管理系统 |
|
||||
| FlaskPermission | Flask | [Github](https://github.com/huguodong/flask-permission) | [预览](http://47.97.218.139:9999) | 基于 Python Flask 的权限管理 |
|
||||
| CareyShop | ThinkPHP5 | [Github](https://github.com/dnyz520/careyshop-admin) | [预览](https://demo.careyshop.cn/admin/#/index) | 适用于 CareyShop 的高性能商城框架系统 |
|
||||
| jiiiiiin-security | Spring Boot | [Github](https://github.com/Jiiiiiin/jiiiiiin-security) | [预览](https://github.com/Jiiiiiin/jiiiiiin-security) | 前后端分离的内容管理基础项目,注重用户权限管理功能 |
|
||||
| Taroco | Spring Cloud | [Github](https://github.com/liuht777/Taroco) | [预览](http://111.231.192.110/) | 整套微服务企业级解决方案 |
|
||||
| Aooms | Spring Cloud | [码云](https://gitee.com/cyb-javaer/Aooms) | [预览](https://www.yuboon.com/Aooms) | 极速微服务开发,不止像JFinal一样简单 |
|
||||
| GOA | Beego | [Github](https://github.com/Qsnh/goa) | [预览](http://goaio.vip/) | 基于 Beego + Vue 开发的在线问答系统 |
|
||||
| CMDB | Django | [Github](https://github.com/CJFJack/django_vue_cmdb) | [预览](https://mp.weixin.qq.com/s?__biz=MzU1OTYzODA4Mw==&mid=2247484250&idx=1&sn=981024ac0580d8a3eba95742bd32b268) | 分用户加载不同菜单和权限 |
|
||||
|
||||
## 社区项目
|
||||
|
||||
> 这些项目由开源社区贡献,不保证使用 D2Admin 最新版本,相关使用问题请联系其开源作者。
|
||||
|
||||
| 名称 | 主页 | 预览 | 介绍 |
|
||||
| --- | --- | --- | --- |
|
||||
| d2-admin-xiya-go-cms | [Github](https://github.com/d2-projects/d2-admin-xiya-go-cms) | [预览](https://d2.pub/d2-admin-xiya-go-cms/preview) | D2Admin + 权限系统 + 动态路由 |
|
||||
| d2-advance | [Github](https://github.com/d2-projects/d2-advance) | [预览](https://d2.pub/d2-advance/preview) | 由 D2Admin 启发的技术探索 |
|
||||
| d2-crud-plus | [Github](https://github.com/greper/d2-crud-plus) | [预览](http://qiniu.veryreader.com/D2CrudPlusExample/index.html) | 简化d2-crud配置,快速开发crud功能 |
|
||||
| d2-crud | [Github](https://github.com/d2-projects/d2-crud) | [预览]() | 表格常用操作封装 |
|
||||
| d2-admin-pm | [Github](https://github.com/wjkang/d2-admin-pm) | [预览](http://jaycewu.coding.me/d2-admin-pm) | 基于 D2Admin 的 RBAC 权限管理解决方案 |
|
||||
| LanBlog | [Github](https://github.com/sinksmell/LanBlog) | [预览](http://47.101.222.133/) | Vue + Beego restful api 开发的懒人博客 |
|
||||
| d2-admin-start-kit-plus | [Github](https://github.com/hank-cp/d2-admin-start-kit-plus) | [预览](https://github.com/hank-cp/d2-admin-start-kit-plus) | D2Admin 简化版模块化版本 |
|
||||
| d2-ribbons | [Github](https://github.com/d2-projects/d2-ribbons) | [预览](https://github.com/d2-projects/d2-ribbons) | 开源项目徽标库 |
|
||||
|
||||
## 加入我们
|
||||
|
||||
D2Admin 是完全开源免费的项目,旨在帮助开发者更方便地进行管理系统开发,同时也提供 QQ 交流群和微信群,前后端的朋友可以相互答疑,项目组成员全部在内,所有 D2 相关项目使用问题欢迎在群内提问。
|
||||
|
||||
* QQ 1 群 `806395827`
|
||||
* QQ 2 群 `592981556`
|
||||
|
||||

|
||||
|
||||
## 徽章
|
||||
|
||||
如果您的开源项目基于 D2Admin 开发,请在您的 README 添加下面的徽章:
|
||||
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/FairyEver/d2-admin/master/docs/image/d2-admin@2x.png" width="200">
|
||||
</a>
|
||||
|
||||
复制下面代码加入到 README 中即可:
|
||||
|
||||
``` html
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank"><img src="https://raw.githubusercontent.com/FairyEver/d2-admin/master/docs/image/d2-admin@2x.png" width="200"></a>
|
||||
```
|
||||
|
||||
同时您可以将您的项目汇报给我们,优秀项目我们会放置在 D2Admin 相关位置并帮助您宣传。
|
||||
|
||||
## 贡献
|
||||
|
||||
* [@FairyEver](https://github.com/FairyEver)
|
||||
* [@sunhaoxiang](https://github.com/sunhaoxiang)
|
||||
* [@Aysnine](https://github.com/Aysnine)
|
||||
* [@luchaohai](https://github.com/luchaohai)
|
||||
* [@han-feng](https://github.com/han-feng)
|
||||
* [@rongxingsun](https://github.com/rongxingsun)
|
||||
* [@dnyz520](https://github.com/dnyz520)
|
||||
|
||||
## 成为赞助者
|
||||
|
||||
[在 "爱发电" 上赞助我](https://afdian.net/@fairyever)
|
||||
|
||||
## 赞助
|
||||
|
||||
**cochlea** | **Baron** | **苦行僧** | **吴地安宁** | **KingDong** | **sunyongmofang**
|
||||
|
||||
## 访问统计
|
||||
|
||||

|
||||
|
||||
> 自 2019-08-27 起
|
||||
|
||||
## Star 历史
|
||||
|
||||
[](https://starchart.cc/d2-projects/d2-admin)
|
||||
|
||||
## License
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fd2-projects%2Fd2-admin?ref=badge_large)
|
||||
|
||||

|
||||
445
docs/i18n-rules.md
Normal file
445
docs/i18n-rules.md
Normal file
@@ -0,0 +1,445 @@
|
||||
# i18n 国际化规范文档
|
||||
|
||||
> **版本**:v1.0
|
||||
> **适用项目**:`mes-ui`
|
||||
> **语言包文件**:`src/locales/zh-chs.json` / `src/locales/en.json` / `src/locales/zh-cht.json` / `src/locales/ja.json`
|
||||
> **配合使用**:`src/composables/useI18n.js`(`i18nMixin`)
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [语言包文件规范](#1-语言包文件规范)
|
||||
2. [Key 命名规范(三层层级)](#2-key-命名规范三层层级)
|
||||
3. [公共 Key 规范(page.common)](#3-公共-key-规范pagecommon)
|
||||
4. [页面中如何使用(i18nMixin)](#4-页面中如何使用i18nmixin)
|
||||
5. [常见场景 Key 模板](#5-常见场景-key-模板)
|
||||
6. [旧项目 Key 迁移对照](#6-旧项目-key-迁移对照)
|
||||
7. [翻译格式规范](#7-翻译格式规范)
|
||||
8. [新增页面 Checklist](#8-新增页面-checklist)
|
||||
|
||||
---
|
||||
|
||||
## 1. 语言包文件规范
|
||||
|
||||
### 1.1 文件清单
|
||||
|
||||
| 文件 | 语言 | `_element` 值 |
|
||||
|------|------|--------------|
|
||||
| `src/locales/zh-chs.json` | 简体中文 | `"zh-CN"` |
|
||||
| `src/locales/zh-cht.json` | 繁体中文 | `"zh-TW"` |
|
||||
| `src/locales/en.json` | 英文 | `"en"` |
|
||||
| `src/locales/ja.json` | 日文 | `"ja"` |
|
||||
|
||||
### 1.2 文件格式
|
||||
|
||||
- **编码**:UTF-8
|
||||
- **格式**:标准 JSON(不允许尾逗号,不允许注释)
|
||||
- **顶层结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"_element": "zh-CN",
|
||||
"_name": "简体中文",
|
||||
"page": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
- `_element`:对应 `element-ui/lib/locale/lang/` 的语言包文件名
|
||||
- `_name`:语言切换下拉菜单中的显示名
|
||||
- `page`:所有业务翻译的根节点,之下按模块层级嵌套
|
||||
|
||||
### 1.3 维护原则
|
||||
|
||||
- 中文和英文文件**必须保持 key 结构完全一致**,不允许一边有 key 而另一边没有
|
||||
- 新增页面时**中英文同步添加**
|
||||
- 每次新增后用 `node -e "JSON.parse(require('fs').readFileSync('src/locales/zh-chs.json','utf8'))"` 验证 JSON 合法性
|
||||
|
||||
---
|
||||
|
||||
## 2. Key 命名规范(三层层级)
|
||||
|
||||
### 2.1 核心规则
|
||||
|
||||
```
|
||||
page.{一级模块英文} .{二级模块英文} .{三级模块英文} .{具体key}
|
||||
└─ snake_case ─┘└─ snake_case ─┘└─ snake_case ─┘
|
||||
```
|
||||
|
||||
**英文来源**:[后台Webman界面截图对照表](./后台Webman界面截图对照表.md) 中的英文列。
|
||||
|
||||
**格式转换**:对照表英文 → 全小写 + 空格替换为下划线 + `&` 替换为下划线。
|
||||
|
||||
### 2.2 对照表 → i18n Key 映射表
|
||||
|
||||
| 对照表英文名 | snake_case(用于 i18n key) | kebab-case(用于目录名) |
|
||||
|-------------|---------------------------|------------------------|
|
||||
| System Administration | `system_administration` | `system-administration` |
|
||||
| Production Master Data | `production_master_data` | `production-master-data` |
|
||||
| Equipment Management | `equipment_management` | `equipment-management` |
|
||||
| Planning & Production | `planning_production` | `planning-production` |
|
||||
| Quality Management | `quality_management` | `quality-management` |
|
||||
| Data Platform | `data_platform` | `data-platform` |
|
||||
| Factory Model | `factory_model` | `factory-model` |
|
||||
| Process Model | `process_model` | `process-model` |
|
||||
| Product Management | `product_management` | `product-management` |
|
||||
| Material Model | `material_model` | `material-model` |
|
||||
| SPC Configuration | `spc_configuration` | `spc-configuration` |
|
||||
| Team Model | `team_model` | `team-model` |
|
||||
| User Management | `user_management` | `user-management` |
|
||||
| Menu Management | `menu_management` | `menu-management` |
|
||||
| System Utilities | `system_utilities` | `system-utilities` |
|
||||
| System Monitoring | `system_monitoring` | `system-monitoring` |
|
||||
| Batch Management | `batch_management` | `batch-management` |
|
||||
| Production Monitoring | `production_monitoring` | `production-monitoring` |
|
||||
| Process Control | `process_control` | `process-control` |
|
||||
| Inspection Management | `inspection_management` | `inspection-management` |
|
||||
| Traceability | `traceability` | `traceability` |
|
||||
| Production Reports | `production_reports` | `production-reports` |
|
||||
| Correlation Analysis | `correlation_analysis` | `correlation-analysis` |
|
||||
| Factory Area | `factory_area` | `factory-area` |
|
||||
| Production Line | `production_line` | `production-line` |
|
||||
|
||||
> **完整映射表**见本文档末尾附录 A。
|
||||
|
||||
### 2.3 完整示例
|
||||
|
||||
以「生产配置 → 工厂模型 → 工厂区域」页面为例:
|
||||
|
||||
```json
|
||||
{
|
||||
"page": {
|
||||
"production_master_data": {
|
||||
"factory_model": {
|
||||
"factory_area": {
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"code": "所区编码",
|
||||
"name": "所区名称",
|
||||
"add": "新 增",
|
||||
"edit": "编 辑",
|
||||
"delete": "删 除"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
对应的 `i18nMixin` 前缀:
|
||||
|
||||
```js
|
||||
mixins: [i18nMixin('page.production_master_data.factory_model.factory_area')]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 公共 Key 规范(page.common)
|
||||
|
||||
### 3.1 什么应该放公共
|
||||
|
||||
跨页面、跨模块重复出现的文案,提取到 `page.common` 下:
|
||||
|
||||
```json
|
||||
{
|
||||
"page": {
|
||||
"common": {
|
||||
"help": "帮 助",
|
||||
"confirm": "确定",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"add": "新增",
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"operation": "操作",
|
||||
"tip": "提示",
|
||||
"confirm_delete": "确定要执行该操作吗?",
|
||||
"operation_success": "操作成功",
|
||||
"no_data": "暂无数据",
|
||||
"loading": "加载中..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 已有公共 Key 清单
|
||||
|
||||
| key | 中文 | 英文 | 用途 |
|
||||
|-----|------|------|------|
|
||||
| `page.common.help` | 帮 助 | Help | 表格工具栏右侧帮助按钮 |
|
||||
|
||||
> 后续发现跨页面共用 key 时,持续往 `page.common` 中补充。
|
||||
|
||||
### 3.3 如何使用
|
||||
|
||||
```vue
|
||||
<page-table :help-text="$t(ckey('help'))" />
|
||||
```
|
||||
|
||||
`ckey('help')` → `'page.common.help'`,由 `i18nMixin` 自动注入。
|
||||
|
||||
### 3.4 判断标准:该不该放 common?
|
||||
|
||||
| 情况 | 放哪里 | 示例 |
|
||||
|------|--------|------|
|
||||
| 多个一级模块都出现 | `page.common` | 帮助、确定、取消 |
|
||||
| 只在本模块内出现 | 模块自己的 key | 所区编码、产线编码 |
|
||||
| 不确定 | 先放在模块内,等第二次出现时提取 | — |
|
||||
|
||||
---
|
||||
|
||||
## 4. 页面中如何使用(i18nMixin)
|
||||
|
||||
### 4.1 页面 Script 部分
|
||||
|
||||
```js
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
|
||||
export default {
|
||||
mixins: [i18nMixin('page.production_master_data.factory_model.factory_area')],
|
||||
data () {
|
||||
const t = this.$t.bind(this)
|
||||
const k = (s) => t(this.key(s)) // 当前页面翻译
|
||||
const ck = (s) => t(this.ckey(s)) // 公共翻译
|
||||
|
||||
return {
|
||||
formCols: [
|
||||
[{ label: k('code'), placeholder: k('enter_code') }]
|
||||
],
|
||||
rules: {
|
||||
code: [{ required: true, message: k('enter_code'), trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.columns = useTableColumns([
|
||||
{ prop: 'code', label: this.key('code') }
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 页面 Template 部分
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- 当前页面翻译 -->
|
||||
<el-form-item :label="$t(key('code'))">
|
||||
<el-input :placeholder="$t(key('enter_code'))" />
|
||||
</el-form-item>
|
||||
<el-button>{{ $t(key('search')) }}</el-button>
|
||||
|
||||
<!-- 公共翻译 -->
|
||||
<page-table :help-text="$t(ckey('help'))" />
|
||||
</template>
|
||||
```
|
||||
|
||||
### 4.3 data() 中翻译的时机说明
|
||||
|
||||
`data()` 在 `created` 之前执行,此时 `this` 已经可用。因此:
|
||||
|
||||
- **column label / formCols / rules.message**:在 `data()` 中或 `created()` 中 `this.key()` 都可以
|
||||
- **推荐在 `data()` 中用 `k()` 提前翻译**,避免子组件 `$t()` 的 webpack HMR 缓存问题
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见场景 Key 模板
|
||||
|
||||
### 5.1 标准 CRUD 页面(每个页面必有的 key)
|
||||
|
||||
```json
|
||||
{
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"add": "新 增",
|
||||
"edit": "编 辑",
|
||||
"delete": "删 除",
|
||||
"operation": "操作",
|
||||
"add_title": "新增{模块名}",
|
||||
"edit_title": "编辑{模块名}",
|
||||
"operation_success": "操作成功",
|
||||
"confirm_delete": "确定要执行该操作吗?",
|
||||
"enter_xxx": "请输入{字段名}",
|
||||
"select_xxx": "请选择{字段名}"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 列表页字段 Key
|
||||
|
||||
```json
|
||||
{
|
||||
"sort": "序号",
|
||||
"code": "{实体}编码",
|
||||
"name": "{实体}名称",
|
||||
"remark": "备注"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 表单校验 Key
|
||||
|
||||
```json
|
||||
{
|
||||
"enter_code": "请输入{实体}编码",
|
||||
"enter_name": "请输入{实体}名称",
|
||||
"remark_length": "长度在 1 到 100 个字符",
|
||||
"validation_fail": "校验失败"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 登录页 Key
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"login": "登录",
|
||||
"please_enter_username": "请输入用户名",
|
||||
"please_enter_password": "请输入密码",
|
||||
"form_validation_failed": "表单校验失败,请检查"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 旧项目 Key 迁移对照
|
||||
|
||||
### 6.1 旧 Key 结构 vs 新 Key 结构
|
||||
|
||||
旧项目 key 路径深且命名不统一:
|
||||
|
||||
| 旧 key 前缀 | 新 key 前缀 |
|
||||
|-----------|-----------|
|
||||
| `page.system_settings.user_management.role` | `page.system_administration.user_management.role` |
|
||||
| `page.system_settings.user_management.user` | `page.system_administration.user_management.user` |
|
||||
| `page.system_settings.menu_configuration.menu` | `page.system_administration.menu_management.menu_configuration` |
|
||||
| `page.system_settings.system_assistant.operate_log` | `page.system_administration.system_utilities.operation_logs` |
|
||||
| `page.system_settings.system_monitoring.system.login` | `page.system_administration.system_monitoring.login` |
|
||||
| `page.production_configuration.factory_model.factory_area` | `page.production_master_data.factory_model.factory_area` |
|
||||
| `page.production_configuration.matetial_model.*` | `page.production_master_data.material_model.*` |
|
||||
| `page.planning_production.production_batch_management.batch` | `page.planning_production.batch_management.batch_list` |
|
||||
| `page.data_middleground.basic_traceability.*` | `page.data_platform.traceability.*` |
|
||||
| `page.warehouse.*` | `page.warehouse.*`(待确定对照表英文名) |
|
||||
|
||||
### 6.2 搬迁时的操作步序
|
||||
|
||||
1. 确定页面对应的对照表三级模块英文名
|
||||
2. 按 `page.{一级}.{二级}.{三级}` 组装新 key 前缀
|
||||
3. 在 `zh-chs.json` 中按三层嵌套创建节点
|
||||
4. 在 `en.json` 中创建相同结构
|
||||
5. 将旧 key 的翻译值复制到新 key 下
|
||||
6. 页面代码中 `i18nMixin` 参数改为新前缀
|
||||
7. 模板中 `$t('旧key')` 全部改为 `$t(key('新suffix'))`
|
||||
|
||||
---
|
||||
|
||||
## 7. 翻译格式规范
|
||||
|
||||
### 7.1 中文规范
|
||||
|
||||
| 场景 | 格式 | 示例 |
|
||||
|------|------|------|
|
||||
| 按钮文字 | 字间加空格(两字不加) | `"查询"`、`"新 增"`、`"批量删除"` |
|
||||
| 占位提示 | `请输入{名称}` | `"请输入所区编码"` |
|
||||
| 校验消息 | 完整句,不加句号 | `"长度在 1 到 100 个字符"` |
|
||||
| 提示弹框 | 加问号 | `"确定要执行该操作吗?"` |
|
||||
| 操作成功消息 | 不加"成功"后缀的冗余 | `"操作成功"`(而非"保存成功"、"编辑成功"各自定义) |
|
||||
|
||||
### 7.2 英文规范
|
||||
|
||||
| 场景 | 格式 | 示例 |
|
||||
|------|------|------|
|
||||
| 按钮文字 | 首字母大写 | `"Search"`、`"Add"`、`"Edit"` |
|
||||
| 占位提示 | `Please enter {name}` | `"Please enter area code"` |
|
||||
| 校验消息 | 完整句 | `"Length should be 1 to 100 characters"` |
|
||||
| 提示弹框 | 问句 | `"Are you sure to delete?"` |
|
||||
| 操作成功消息 | 过去式或名词 | `"Operation succeeded"` |
|
||||
|
||||
### 7.3 占位符
|
||||
|
||||
使用 `{name}` 格式,不使用 `%s` / `{0}`:
|
||||
|
||||
```json
|
||||
"please_enter": "请输入{name}"
|
||||
```
|
||||
|
||||
```js
|
||||
this.$t('please_enter', { name: '所区编码' })
|
||||
// → "请输入所区编码"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 新增页面 Checklist
|
||||
|
||||
新增一个 CRUD 页面时,按以下顺序操作:
|
||||
|
||||
- [ ] 1. 在 [后台Webman界面截图对照表](./后台Webman界面截图对照表.md) 中找到该页面的三级英文名
|
||||
- [ ] 2. 确定 i18n key 前缀:`page.{一级snake_case}.{二级snake_case}.{三级snake_case}`
|
||||
- [ ] 3. 在 `zh-chs.json` 的对应节点下添加本页面全部 key
|
||||
- [ ] 4. 在 `en.json` 的相同节点下添加对应英文翻译
|
||||
- [ ] 5. 验证 JSON 合法性:`node -e "JSON.parse(require('fs').readFileSync('src/locales/zh-chs.json','utf8'))"`
|
||||
- [ ] 6. 页面中使用 `mixins: [i18nMixin('key前缀')]`
|
||||
- [ ] 7. 模板中用 `$t(key('xxx'))` 替代硬编码文字
|
||||
- [ ] 8. data() 中用 `k('xxx')` 提前翻译 formCols / rules
|
||||
- [ ] 9. 公共文案(帮助、确定、取消等)用 `ckey('xxx')` 引用
|
||||
- [ ] 10. `pnpm lint` 无报错
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:对照表 → i18n Key 完整映射
|
||||
|
||||
### 一级模块
|
||||
|
||||
| 对照表英文 | snake_case(i18n) | kebab-case(目录) |
|
||||
|-----------|-------------------|-------------------|
|
||||
| System Administration | `system_administration` | `system-administration` |
|
||||
| Production Master Data | `production_master_data` | `production-master-data` |
|
||||
| Equipment Management | `equipment_management` | `equipment-management` |
|
||||
| Planning & Production | `planning_production` | `planning-production` |
|
||||
| Quality Management | `quality_management` | `quality-management` |
|
||||
| Data Platform | `data_platform` | `data-platform` |
|
||||
| Warehouse | `warehouse` | `warehouse` |
|
||||
| SCADA Management | `scada_management` | `scada-management` |
|
||||
|
||||
### 生产配置 → 二级模块
|
||||
|
||||
| 二级(对照表) | snake_case | 三级页面数 |
|
||||
|---------------|-----------|:--:|
|
||||
| Factory Model | `factory_model` | 2 |
|
||||
| Process Model | `process_model` | 3 |
|
||||
| Product Management | `product_management` | 2 |
|
||||
| Material Model | `material_model` | 4 |
|
||||
| SPC Configuration | `spc_configuration` | 1 |
|
||||
| Team Model | `team_model` | 3 |
|
||||
|
||||
### 设备模型 → 二级模块
|
||||
|
||||
| 二级(对照表) | snake_case |
|
||||
|---------------|-----------|
|
||||
| Equipment Category | `equipment_category` |
|
||||
| Equipment Management | `equipment_management` |
|
||||
| Inspection Management | `inspection_management` |
|
||||
| Maintenance Management | `maintenance_management` |
|
||||
| Repair Management | `repair_management` |
|
||||
| Consumables Management | `consumables_management` |
|
||||
|
||||
### 系统设置 → 二级/三级
|
||||
|
||||
| 二级(对照表) | snake_case | 三级 snake_case |
|
||||
|---------------|-----------|----------------|
|
||||
| User Management | `user_management` | `role` / `user` |
|
||||
| Menu Management | `menu_management` | `menu_configuration` |
|
||||
| System Utilities | `system_utilities` | `operation_logs` / `api_logs` |
|
||||
| System Monitoring | `system_monitoring` | `monitoring_configuration` |
|
||||
| Organization | `organization` | `production_team_manage` / `production_shift_management` / `production_shift_calender` |
|
||||
| OCR | `ocr` | `config` / `log` |
|
||||
|
||||
---
|
||||
|
||||
## 附录 B:当前已实施页面
|
||||
|
||||
| 页面 | i18nMixin 前缀 | 语言包节点 |
|
||||
|------|---------------|----------|
|
||||
| 工厂区域 | `page.production_master_data.factory_model.factory_area` | `zh-chs.json` / `en.json` ✅ |
|
||||
439
docs/login-page-migration.md
Normal file
439
docs/login-page-migration.md
Normal file
@@ -0,0 +1,439 @@
|
||||
# 登录页面搬迁文档
|
||||
|
||||
> **搬迁源**:`D:\code\company\SCTMES_MES_V5\vue-app`
|
||||
> **搬迁目标**:`d:\code\mes\mes-ui`
|
||||
> **状态**:✅ 已完成(2026-05-26)
|
||||
|
||||
---
|
||||
|
||||
## 一、文件清单与依赖关系
|
||||
|
||||
```
|
||||
登录页面 index.vue
|
||||
│
|
||||
├── ① 页面自身
|
||||
├── ② 依赖的 Store 模块
|
||||
├── ③ 依赖的 API 文件
|
||||
├── ④ 依赖的静态资源(图片)
|
||||
├── ⑤ 依赖的 i18n 语言包
|
||||
├── ⑥ 依赖的 CSS 公共样式(% placeholder)
|
||||
├── ⑦ 依赖的工具库
|
||||
└── ⑧ 路由中已有框架(无需搬迁本文件)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、需要搬迁的文件
|
||||
|
||||
### 2.1 页面核心文件
|
||||
|
||||
| 文件 | 旧路径 | 新目标路径 | 说明 |
|
||||
|------|--------|-----------|------|
|
||||
| 登录页面 | `src/views/system_settings/system_monitoring/system/login/index.vue` | **覆盖** `src/views/system/login/index.vue` | 新项目已有一个简易占位页面,需用旧项目的完整页面替换 |
|
||||
|
||||
> 旧路径嵌套很深:`system_settings/system_monitoring/system/login/`。
|
||||
> 新项目按对照表命名为简化的 `system/login/`,保持一致的命名风格。
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Store 模块(Vuex)
|
||||
|
||||
| 文件 | 旧路径 | 新目标路径 | 说明 |
|
||||
|------|--------|-----------|------|
|
||||
| account | `src/store/modules/d2admin/modules/account.js` | `src/store/modules/d2admin/modules/account.js` | **核心**:含 `login` 和 `logout` action |
|
||||
| user | `src/store/modules/d2admin/modules/user.js` | `src/store/modules/d2admin/modules/user.js` | account.login 中调用 `d2admin/user/set`,**新项目已存在** |
|
||||
|
||||
> `account.js` 的 `login` action 依赖链:
|
||||
> `login()` → `loginAdminUser()` API → 写 cookie(token/uuid) → `dispatch(d2admin/user/set)` → `dispatch(load)`
|
||||
> `load()` → 依次加载 user/theme/transition/page/menu/size/color
|
||||
|
||||
**account.js 依赖的其它模块(新项目已存在,不需搬迁)**:
|
||||
|
||||
| 模块 | 路径 | 状态 |
|
||||
|------|------|:--:|
|
||||
| user | `src/store/modules/d2admin/modules/user.js` | ✅ 已有 |
|
||||
| theme | `src/store/modules/d2admin/modules/theme.js` | ✅ 已有 |
|
||||
| transition | `src/store/modules/d2admin/modules/transition.js` | ✅ 已有 |
|
||||
| page | `src/store/modules/d2admin/modules/page.js` | ✅ 已有 |
|
||||
| menu | `src/store/modules/d2admin/modules/menu.js` | ✅ 已有 |
|
||||
| size | `src/store/modules/d2admin/modules/size.js` | ✅ 已有 |
|
||||
| color | `src/store/modules/d2admin/modules/color.js` | ✅ 已有 |
|
||||
| db | `src/store/modules/d2admin/modules/db.js` | ✅ 已有 |
|
||||
|
||||
---
|
||||
|
||||
### 2.3 API 文件
|
||||
|
||||
| 文件 | 旧路径 | 新目标路径 | 说明 |
|
||||
|------|--------|-----------|------|
|
||||
| 登录/注销 API | `src/api/login.js` | `src/api/auth.js` | 含 `loginAdminUser()` 和 `logoutAdminUser()` |
|
||||
|
||||
> 注意:旧项目 `login.js` 中 import 了 `@/plugin/axios/request`,这是旧项目的 axios 封装。
|
||||
> 新项目使用 `@/api/_service` 中的 `request`,搬迁时需替换 import 路径。
|
||||
|
||||
---
|
||||
|
||||
### 2.4 静态资源(图片)
|
||||
|
||||
| 文件 | 旧路径 | 新目标路径 | 说明 |
|
||||
|------|--------|-----------|------|
|
||||
| 登录页 Logo | `public/image/logo/sc_logo.png` | `public/image/logo/sc_logo.png` | 登录表单上方的 Logo |
|
||||
|
||||
> 旧项目 `public/image/logo/` 目录下共有 9 个文件,但登录页面只用 `sc_logo.png`。
|
||||
> 根据需要可按需搬其他 logo 文件。新项目原有 `src/views/system/login/image/logo@2x.png`,搬迁后可删除。
|
||||
|
||||
---
|
||||
|
||||
### 2.5 i18n 语言包
|
||||
|
||||
**i18n key 前缀**:`page.system_administration.system_monitoring.login`
|
||||
|
||||
| key | 中文 | 英文 |
|
||||
|-----|------|------|
|
||||
| `time_is_most_precious` | 时间是一切财富中最宝贵的财富 | Time is the most precious of all wealth |
|
||||
| `username` | 用户名 | Username |
|
||||
| `password` | 密码 | Password |
|
||||
| `login` | 登录 | Login |
|
||||
| `quick_select_user` | 快速选择用户 | Quick Select User |
|
||||
| `please_enter_username` | 请输入用户名 | Please enter username |
|
||||
| `please_enter_password` | 请输入密码 | Please enter password |
|
||||
| `please_enter_code` | 请输入验证码 | Please enter captcha |
|
||||
| `dev_version` | 开发版本 | Development Version |
|
||||
| `test_version` | 测试版本 | Test Version |
|
||||
| `form_validation_failed` | 表单校验失败,请检查 | Form validation failed |
|
||||
|
||||
**搬迁操作**:
|
||||
|
||||
1. 在 `src/locales/zh-chs.json` 的 `page.system_administration.system_monitoring` 下新增:
|
||||
```json
|
||||
"login": {
|
||||
"time_is_most_precious": "时间是一切财富中最宝贵的财富",
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"login": "登录",
|
||||
"please_enter_username": "请输入用户名",
|
||||
"please_enter_password": "请输入密码",
|
||||
"dev_version": "开发版本",
|
||||
"test_version": "测试版本",
|
||||
"form_validation_failed": "表单校验失败,请检查"
|
||||
}
|
||||
```
|
||||
|
||||
2. 在 `src/locales/en.json` 中对应添加英文翻译。
|
||||
|
||||
---
|
||||
|
||||
### 2.6 CSS 公共样式(依赖但不在页面内)
|
||||
|
||||
页面中使用了 SCSS 的 `%placeholder` 选择器:
|
||||
|
||||
| placeholder | 作用 | 定义位置 |
|
||||
|-------------|------|---------|
|
||||
| `%unable-select` | 禁止用户选中 + 鼠标变手形 | `src/assets/style/public.scss` |
|
||||
| `%full` | 绝对定位填满父元素 | `src/assets/style/public.scss` |
|
||||
| `%flex-center-col` | flex 垂直水平居中 | `src/assets/style/public.scss` |
|
||||
| `$color-text-normal` | 文字颜色变量 | `src/assets/style/unit/color.scss` |
|
||||
| `$color-primary` | 主色变量 | `src/assets/style/unit/color.scss` |
|
||||
| `$color-bg` | 背景色变量 | `src/assets/style/unit/color.scss` |
|
||||
|
||||
> ✅ 这些在新项目中已存在(通过 `additionalData` 全局注入),不需要额外搬迁。
|
||||
|
||||
---
|
||||
|
||||
### 2.7 工具/插件依赖(新项目已有,不需搬迁)
|
||||
|
||||
| 依赖 | 旧路径 | 新项目状态 |
|
||||
|------|--------|:--:|
|
||||
| `util.cookies` | `src/libs/util.cookies.js` → `src/libs/util.js` | ✅ 已有 |
|
||||
| `localeMixin` | `src/locales/mixin.js` | ✅ 已有 |
|
||||
| `dayjs` | npm 包 | ✅ 已有(`package.json` 中) |
|
||||
| `$baseUrl` | `Vue.prototype.$baseUrl = process.env.BASE_URL` | ✅ 已在 `plugin/d2admin/index.js` 注册 |
|
||||
| `vuex` `mapActions` | npm 包 | ✅ 已有 |
|
||||
|
||||
---
|
||||
|
||||
### 2.8 路由(新项目已配置,不需搬迁)
|
||||
|
||||
旧路由:
|
||||
```js
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: _import('system_settings/system_monitoring/system/login')
|
||||
}
|
||||
```
|
||||
|
||||
新项目路由已在 `routes.js` 的 `frameOut` 中配置:
|
||||
```js
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: _import('system/login')
|
||||
}
|
||||
```
|
||||
|
||||
> ✅ 新路由已指向 `system/login`,对应的 `src/views/system/login/index.vue` 需要替换为旧页面内容。
|
||||
|
||||
---
|
||||
|
||||
## 三、搬迁执行步骤
|
||||
|
||||
| 步骤 | 操作 | 文件 |
|
||||
|:--:|------|------|
|
||||
| 1 | 拷贝登录 API(替换 import 为 `@/api/_service`) | `src/api/login.js` → `src/api/auth.js` |
|
||||
| 2 | 拷贝 account Store(替换 API 引用路径) | `src/store/modules/d2admin/modules/account.js` |
|
||||
| 3 | 拷贝 Logo 图片 | `public/image/logo/sc_logo.png` |
|
||||
| 4 | 更新登录页面(替换 import 路径 + i18n key + logo 路径) | `src/views/system/login/index.vue` |
|
||||
| 5 | 添加 i18n 语言包 | `src/locales/zh-chs.json` / `en.json` |
|
||||
| 6 | 验证:`pnpm serve` 启动 → 访问 `/login` 测试登录流程 |
|
||||
|
||||
---
|
||||
|
||||
## 四、页面中需要修改的内容
|
||||
|
||||
登录页面 `index.vue` 从旧项目拷贝后,需改以下几处:
|
||||
|
||||
### 4.1 import 路径修正
|
||||
|
||||
```diff
|
||||
- import { mapActions } from 'vuex'
|
||||
- import util from '@/libs/util'
|
||||
- import localeMixin from '@/locales/mixin.js'
|
||||
+ // 这些在新项目中路径一致,无需修改
|
||||
+ // util.cookies、localeMixin、mapActions 均同上
|
||||
```
|
||||
|
||||
> 注意:旧页面 mixins 中引用了 `localeMixin`(切换语言),新项目中此 mixin 在 `src/locales/mixin.js` 已存在,不需动。
|
||||
|
||||
### 4.2 Logo 图片路径
|
||||
|
||||
```diff
|
||||
- <img class="page-login--logo" :src="`${$baseUrl}image/logo/sc_logo.png`">
|
||||
+ <img class="page-login--logo" src="/image/logo/sc_logo.png">
|
||||
```
|
||||
|
||||
> 新项目中 `$baseUrl` 同样已注册,两种写法均可。建议用绝对路径 `/image/logo/sc_logo.png` 更直观。
|
||||
|
||||
### 4.3 i18n key 修正
|
||||
|
||||
旧页面使用的 key 前缀为 `page.system_settings.system_monitoring.system.login.xxx`,在新项目中统一简化为 `page.system.login.xxx`,或按新项目目录结构调整。
|
||||
|
||||
建议调整为更简洁的 key:
|
||||
|
||||
| 旧 key(太长) | 新 key(建议) |
|
||||
|---------------|---------------|
|
||||
| `page.system_settings.system_monitoring.system.login.username` | `page.system.login.username` |
|
||||
| `page.system_settings.system_monitoring.system.login.password` | `page.system.login.password` |
|
||||
|
||||
---
|
||||
|
||||
## 五、不涉及的依赖(记录说明)
|
||||
|
||||
| 依赖 | 原因 |
|
||||
|------|------|
|
||||
| `@/libs/websocket.js` | 登录页面本身不 import websocket,但 `account.js` 的 `logout` action 中引用。新项目如不需 websocket,注释掉即可 |
|
||||
| `@/api/modules/sys.user.api.js` | 旧 `account.login` 只调 `login.js` API,不涉及此文件 |
|
||||
| `public/image/logo/` 其他 png | 登录页只用 `sc_logo.png`,其余 logo 按需搬迁 |
|
||||
|
||||
---
|
||||
|
||||
## 登录流程完整流程图
|
||||
|
||||
### 正向流程:用户登录
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant LoginPage as Login 页面
|
||||
participant VUEX as Store account
|
||||
participant API as 后端 POST /login
|
||||
participant Cookie as Cookie
|
||||
participant Router as router.beforeEach
|
||||
participant Home as 首页
|
||||
|
||||
User->>LoginPage: 输入用户名密码,点击登录
|
||||
LoginPage->>LoginPage: el-form.validate 校验表单
|
||||
alt 校验失败
|
||||
LoginPage-->>User: message.error 表单校验失败
|
||||
else 校验通过
|
||||
LoginPage->>VUEX: dispatch login { username, password }
|
||||
VUEX->>API: loginAdminUser
|
||||
alt 登录失败 401 或密码错误
|
||||
API-->>VUEX: throw Error
|
||||
VUEX->>VUEX: remove token 和 uuid
|
||||
VUEX-->>LoginPage: Promise rejected
|
||||
LoginPage-->>User: message.error
|
||||
else 登录成功
|
||||
API-->>VUEX: token 和 userInfo
|
||||
VUEX->>Cookie: 写入 token / uuid 有效期365天
|
||||
VUEX->>VUEX: localStorage.setItem user_id
|
||||
VUEX->>VUEX: dispatch user/set
|
||||
VUEX->>VUEX: dispatch load 加载持久化配置
|
||||
Note over VUEX: load按序加载: user theme transition page menu size color sourceData
|
||||
VUEX-->>LoginPage: Promise resolved
|
||||
LoginPage->>Router: router.replace redirect or /
|
||||
Router->>Router: beforeEach 检查 token
|
||||
Router-->>Home: 鉴权通过,跳转目标页
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 反向流程:用户注销
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant Header as 顶栏用户菜单
|
||||
participant VUEX as Store account
|
||||
participant API as 后端 POST /logout
|
||||
participant Cookie as Cookie
|
||||
participant WS as WebSocket
|
||||
participant LoginPage as Login 页面
|
||||
|
||||
User->>Header: 点击注销
|
||||
Header->>VUEX: dispatch logout { confirm: true }
|
||||
VUEX->>User: confirm 确定要注销吗
|
||||
alt 取消
|
||||
User-->>VUEX: 取消
|
||||
else 确认
|
||||
VUEX->>VUEX: Loading 遮罩
|
||||
VUEX->>WS: closeSock 断开 WebSocket
|
||||
VUEX->>API: logoutAdminUser
|
||||
API-->>VUEX: 服务端清除 session
|
||||
VUEX->>VUEX: dispatch db/set 清空 sourceData
|
||||
VUEX->>VUEX: dispatch user/set 清空用户信息
|
||||
VUEX->>Cookie: remove token / uuid / set block true
|
||||
VUEX->>VUEX: Loading 关闭
|
||||
VUEX->>LoginPage: router.push { name: login }
|
||||
LoginPage-->>User: 返回登录页面
|
||||
end
|
||||
```
|
||||
|
||||
### 路由守卫鉴权流程(分三种情况)
|
||||
|
||||
#### 情况 A:无 Token → 跳转登录页
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["用户访问 URL"] --> B{"路由需要 auth?"}
|
||||
B -->|是| C["Cookie.get(token)"]
|
||||
C -->|"token 为空"| D["重定向到 /login?redirect=原地址"]
|
||||
D --> E["用户在登录页输入账号密码"]
|
||||
E --> F["登录成功"]
|
||||
F --> G["router.replace 回到目标页"]
|
||||
G --> H["进入首页"]
|
||||
```
|
||||
|
||||
#### 情况 B:有 Token → 恢复会话
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["用户访问 URL,Cookie 中已有 token"] --> B{"路由需要 auth?"}
|
||||
B -->|是| C["Cookie.get(token)"]
|
||||
C -->|"token 有效"| D["next 放行"]
|
||||
D --> E["框架渲染 header-aside 布局"]
|
||||
E --> F["d2admin/page/isLoaded"]
|
||||
F --> G["d2admin/size/isLoaded"]
|
||||
G --> H["页面正常加载"]
|
||||
|
||||
style C fill:#f9f,stroke:#333
|
||||
style D fill:#9f9,stroke:#333
|
||||
style H fill:#9f9,stroke:#333
|
||||
```
|
||||
|
||||
#### 情况 C:Token 有效但后端已过期 → API 层拦截
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["页面加载后调用业务 API"] --> B["axios 请求携带 Authorization header"]
|
||||
B --> C{"后端 code === 401?"}
|
||||
C -->|否| D["正常返回数据"]
|
||||
C -->|"是 401"| E["axios 响应拦截器 catch 401"]
|
||||
E --> F["清除 Cookie(token / uuid)"]
|
||||
F --> G["Message.error 请重新登录"]
|
||||
G --> H["router.push /login"]
|
||||
|
||||
style C fill:#f96,stroke:#333
|
||||
style H fill:#f96,stroke:#333
|
||||
```
|
||||
|
||||
### 已有 Token 时的完整恢复时序
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant Browser as 浏览器
|
||||
participant Router as router.beforeEach
|
||||
participant Cookie as Cookie
|
||||
participant Store as Vuex Store
|
||||
participant DB as Local DB lowdb
|
||||
participant Layout as Layout
|
||||
participant Page as 目标页面
|
||||
|
||||
User->>Browser: 打开网页
|
||||
Browser->>Router: beforeEnter 触发
|
||||
Router->>Store: dispatch page/isLoaded
|
||||
Router->>Store: dispatch size/isLoaded
|
||||
Router->>Cookie: get token
|
||||
Cookie-->>Router: token = eyJ...
|
||||
Router->>Router: next 放行
|
||||
Router->>Layout: 渲染 header-aside
|
||||
Layout->>Store: menu 加载侧边栏
|
||||
Store->>DB: get sys.menu.asideCollapse
|
||||
DB-->>Store: 上次展开收起状态
|
||||
Layout->>Store: theme 加载主题
|
||||
Store->>DB: get sys.theme.activeName
|
||||
DB-->>Store: 上次选择的主题
|
||||
Layout->>Store: page 加载多标签页
|
||||
Store->>DB: get sys.page.opened
|
||||
DB-->>Store: 上次打开的标签页列表
|
||||
Layout->>Page: 目标页面渲染完成
|
||||
Page-->>User: 首页正常显示,主题侧栏标签页全部恢复
|
||||
```
|
||||
|
||||
### 对比:首次登录 vs 已有 Token
|
||||
|
||||
| 维度 | 首次登录(无 Token) | 已有 Token 恢复 |
|
||||
|------|-------------------|----------------|
|
||||
| 入口 | `/login` 登录页 | 任意页面 URL |
|
||||
| 路由守卫 | 拦下 → 重定向 /login | 放行 |
|
||||
| 用户操作 | 输入用户名密码 | 无需操作 |
|
||||
| `account/login` | ✅ 触发 | ❌ 不触发 |
|
||||
| `account/load()` | ✅ 触发(写入 DB) | ❌ 不触发 |
|
||||
| `page/isLoaded` | 触发(首次无缓存) | 触发(从 DB 恢复) |
|
||||
| `size/isLoaded` | 触发 | 触发 |
|
||||
| 主题/侧栏/标签页 | 从登录后 load() 写入 | 从 lowdb 读取上次状态 |
|
||||
| Cookie | 新建 token / uuid | 复用已有 token |
|
||||
|
||||
---
|
||||
|
||||
### 整体状态流转
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 未登录: 首次访问
|
||||
[*] --> 已登录: 已有 token,恢复会话
|
||||
未登录 --> 登录页面: 路由守卫拦下
|
||||
登录页面 --> 提交中: 输入账号密码,点击登录
|
||||
提交中 --> 登录成功: API 返回 token
|
||||
提交中 --> 登录失败: API 报错 / 401
|
||||
登录失败 --> 登录页面: 清除 cookie,允许重试
|
||||
登录成功 --> 已登录: 写入 cookie + load() 加载配置
|
||||
已登录 --> 已登录: 页面刷新(token 仍有效,Router + DB 恢复)
|
||||
已登录 --> 未登录: API 返回 401,被强制退出
|
||||
已登录 --> 注销中: 点击注销
|
||||
注销中 --> 已登录: 取消确认
|
||||
注销中 --> 未登录: 确认 → logout API → 清 cookie → 跳 /login
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、搬迁后验证清单
|
||||
|
||||
- [ ] `pnpm lint` 无报错
|
||||
- [ ] `pnpm serve` 启动成功
|
||||
- [ ] 访问 `http://localhost:8081/login` 能看到完整登录页面
|
||||
- [ ] 输入正确的用户名密码能登录并跳转首页
|
||||
- [ ] 中文/英文切换按钮文字正确
|
||||
- [ ] 登录失败有表单校验提示
|
||||
- [ ] 浏览器 Cookie 中有 `token` 和 `uuid`
|
||||
601
docs/menu-permission-migration.md
Normal file
601
docs/menu-permission-migration.md
Normal file
@@ -0,0 +1,601 @@
|
||||
# Menu 权限迁移方案
|
||||
|
||||
> 参照项目:`D:\code\company\SCTMES_MES_V5\vue-app`(以下简称"源项目")
|
||||
|
||||
---
|
||||
|
||||
## 一、源项目(SCTMES)Menu 权限架构
|
||||
|
||||
### 1.1 后端接口
|
||||
|
||||
```
|
||||
GET system_settings/menu_configuration/menu/all
|
||||
```
|
||||
|
||||
API 文件:[src/api/system_settings/menu_configuration/menu.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\api\system_settings\menu_configuration\menu.js)
|
||||
|
||||
返回当前用户可见的**扁平数组**,字段如下:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `menu_id` | number | 主键,用于构建父子关系 |
|
||||
| `parent_id` | number | 父节点 ID,`0` 表示顶栏根节点 |
|
||||
| `url` | string | 前端路由路径,如 `/system_settings/user_management/user` |
|
||||
| `name` | string | 菜单显示名称 |
|
||||
| `icon` | string | 图标名(FontAwesome) |
|
||||
| `is_navi` | number | 是否作为导航菜单展示(1=是) |
|
||||
| `status` | number | 启用/禁用 |
|
||||
| `type` | string | 菜单类型 |
|
||||
| `params` | string | 额外参数 |
|
||||
| `sort` | number | 排序 |
|
||||
| `remark` | string | 备注 |
|
||||
|
||||
### 1.2 菜单数据全链路
|
||||
|
||||
```
|
||||
登录成功 → account.login() → dispatch('load')
|
||||
│
|
||||
▼
|
||||
account.load()
|
||||
│
|
||||
...加载 user/theme/transition/page/menu/size/color...
|
||||
│
|
||||
▼
|
||||
dispatch('d2admin/menu/sourceDataLoad')
|
||||
│
|
||||
┌────────────┴────────────┐
|
||||
│ │
|
||||
localStorage localStorage 为空
|
||||
有缓存 且有 token
|
||||
│ │
|
||||
▼ ▼
|
||||
直接读缓存 GET /menu/all 后端接口
|
||||
│
|
||||
▼
|
||||
写入 localStorage 缓存
|
||||
│
|
||||
└────────────┬────────────┘
|
||||
│
|
||||
▼
|
||||
menu.install(this, sourceData)
|
||||
```
|
||||
|
||||
**Store 文件**:[src/store/modules/d2admin/modules/menu.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\store\modules\d2admin\modules\menu.js)
|
||||
|
||||
```js
|
||||
state: {
|
||||
header: [],
|
||||
aside: [],
|
||||
asideCollapse: setting.menu.asideCollapse,
|
||||
asideTransition: setting.menu.asideTransition,
|
||||
authKey: {}, // ← 权限字典 { '/path': '菜单名', ... }
|
||||
sourceData: [] // ← 菜单源数据(来自后端或本地缓存)
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 `sourceDataLoad` action 核心逻辑
|
||||
|
||||
```js
|
||||
async sourceDataLoad({ state, dispatch }) {
|
||||
// 1. 优先从 localStorage 读取缓存
|
||||
state.sourceData = await dispatch('d2admin/db/get', {
|
||||
dbName: 'database',
|
||||
path: '$menu.sourceData',
|
||||
defaultValue: [],
|
||||
user: true
|
||||
}, { root: true })
|
||||
|
||||
// 2. 缓存为空且已登录 → 调后端获取
|
||||
if (!state.sourceData.length && util.cookies.get('token')) {
|
||||
const res = await getMenuAll(null)
|
||||
state.sourceData = res.data || []
|
||||
await dispatch('d2admin/db/set', {
|
||||
dbName: 'database',
|
||||
path: '$menu.sourceData',
|
||||
value: state.sourceData,
|
||||
user: true
|
||||
}, { root: true })
|
||||
}
|
||||
|
||||
// 3. 处理为 header/aside/authKey
|
||||
menu.install(this, state.sourceData)
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 `menu.install()` — 扁平数据 → 菜单树 + 权限字典
|
||||
|
||||
**文件**:[src/menu/index.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\menu\index.js)
|
||||
|
||||
```js
|
||||
export default {
|
||||
install(vm, source) {
|
||||
// 1. 构建权限字典 { '/path': '菜单名' }
|
||||
vm.commit('d2admin/menu/headerAuth', source)
|
||||
|
||||
// 2. 扁平数据转为 {header: [], aside: []} 树结构
|
||||
const { header, aside } = getMenuData(source)
|
||||
|
||||
// 3. 写入 store → 触发菜单渲染
|
||||
vm.commit('d2admin/menu/headerSet', header)
|
||||
vm.commit('d2admin/menu/asideSet', aside)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`getMenuData()` 核心流程:
|
||||
|
||||
```
|
||||
source (扁平数组)
|
||||
│
|
||||
├── 过滤: is_navi !== 1 的跳过
|
||||
│
|
||||
├── parent_id === 0 → 推入 header[]
|
||||
│
|
||||
└── 全部节点 → 推入 aside[]
|
||||
│
|
||||
▼
|
||||
util.formatDataToTree(aside) { menu_id ↔ parent_id }
|
||||
│
|
||||
▼
|
||||
树形结构 aside
|
||||
```
|
||||
|
||||
**`headerAuth` mutation**:遍历 `source`,将每个有 `url` 的节点写入 `authKey[url] = name`:
|
||||
|
||||
```js
|
||||
headerAuth(state, source) {
|
||||
let auth = {}
|
||||
source.forEach(value => {
|
||||
if (value.url) {
|
||||
auth[value.url] = value.name
|
||||
}
|
||||
})
|
||||
state.authKey = auth
|
||||
}
|
||||
```
|
||||
|
||||
### 1.5 权限控制系统
|
||||
|
||||
**`$permission()` 方法** — 定义在 [src/plugin/d2admin/index.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\plugin\d2admin\index.js#L50):
|
||||
|
||||
```js
|
||||
Vue.prototype.$permission = (value, type = 'menu') => {
|
||||
let path = ''
|
||||
const auth = store.state.d2admin.menu.authKey
|
||||
|
||||
switch (type) {
|
||||
case 'menu': // 直接用 URL 查
|
||||
path = value
|
||||
break
|
||||
case 'router': // 路由 name → URL
|
||||
path = value.name.replace(/-/g, '/')
|
||||
path.slice(0, 1) !== '/' && (path = '/' + path)
|
||||
break
|
||||
}
|
||||
|
||||
return !!(path && Object.prototype.hasOwnProperty.call(auth, path))
|
||||
}
|
||||
```
|
||||
|
||||
**`v-permission` 指令** — 定义在 [src/main.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\main.js#L72-L78):
|
||||
|
||||
```js
|
||||
Vue.directive('permission', {
|
||||
bind: (el, binding) => {
|
||||
if (!Vue.prototype.$permission(binding.value)) {
|
||||
el.parentNode ? el.parentNode.removeChild(el) : el.style.display = 'none'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
使用示例:
|
||||
```html
|
||||
<el-button v-permission="'/system_settings/user_management/user'">新增用户</el-button>
|
||||
```
|
||||
|
||||
### 1.6 菜单导航权限过滤
|
||||
|
||||
**Menu Mixin** — [src/layout/header-aside/components/mixin/menu.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\layout\header-aside\components\mixin\menu.js):
|
||||
|
||||
点击顶栏一级菜单时,不直接跳转,而是查找第一个有权限的子路由:
|
||||
|
||||
```js
|
||||
getRouterAuthPath(index, indexPath) {
|
||||
// 子路由直接访问
|
||||
if (index === '/index' || !indexPath || indexPath.length > 1) {
|
||||
this.$router.push({ path: index })
|
||||
return
|
||||
}
|
||||
|
||||
// 查找一级路由下第一个有权限的子路由
|
||||
let router = null
|
||||
for (const value of frameInRoutes) {
|
||||
if (value.path === index) {
|
||||
router = value.children
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!router) {
|
||||
this.$router.push({ path: index })
|
||||
return
|
||||
}
|
||||
|
||||
for (const value of router) {
|
||||
const newPath = index + '/' + value.path
|
||||
if (this.$permission(newPath)) {
|
||||
this.$router.push({ path: newPath })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 1.7 路由 — 全部静态注册
|
||||
|
||||
路由在 [src/router/routes.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\router\routes.js) 中**全部静态定义**,不使用 `addRoute`。
|
||||
|
||||
路由守卫([src/router/index.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\router\index.js))仅判断 token 是否存在,不校验具体路由权限。权限控制在**菜单渲染**和**菜单点击时**完成。
|
||||
|
||||
### 1.8 main.js 启动流程
|
||||
|
||||
[src/main.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\main.js#L108-L137)
|
||||
|
||||
```js
|
||||
created() {
|
||||
// 仅初始化页面池,不再设置静态菜单
|
||||
this.$store.commit('d2admin/page/init', frameInRoutes)
|
||||
// 静态菜单设置已注释:
|
||||
// this.$store.commit('d2admin/menu/headerSet', menuHeader)
|
||||
// this.$store.commit('d2admin/search/init', menuHeader)
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('d2admin/account/load') // → sourceDataLoad → 获取菜单
|
||||
},
|
||||
watch: {
|
||||
// $route.matched 切换侧边栏的 watch 已注释
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、mes-ui 当前现状 vs 目标
|
||||
|
||||
| 模块 | mes-ui 现状 | 源项目(目标) |
|
||||
|------|-----------|---------------|
|
||||
| 菜单数据来源 | `src/menu/modules/*.js` 静态硬编码 | 后端 `GET /menu/all` + localStorage 缓存 |
|
||||
| `sourceDataLoad` | 仅 localStorage 读取,不参与菜单构建 | localStorage 兜底 + 调后端 → `menu.install()` |
|
||||
| 权限字典 | 无 | `authKey: { '/path': 'name' }` |
|
||||
| 菜单树构建 | 编译时 `supplementPath()` | 运行时 `getMenuData()` + `formatDataToTree()` |
|
||||
| 权限检查 | 无 | `$permission()` + `v-permission` 指令 |
|
||||
| 菜单点击 | 直接 `$router.push` | `getRouterAuthPath()` 查找首个有权限子路由 |
|
||||
| main.js 菜单初始化 | `created` 中 `headerSet(menuHeader)` | 注释掉静态菜单,由 `mounted` 中的 `load()` 触发 |
|
||||
| 路由注册 | 全部静态 | 全部静态(一致) |
|
||||
| 路由守卫 | 仅 token 检查 | 仅 token 检查(一致) |
|
||||
|
||||
---
|
||||
|
||||
## 三、迁移计划
|
||||
|
||||
### 阶段 1️⃣:约定后端接口格式
|
||||
|
||||
后端接口:`GET /api/menu/all`(或复用源项目的 `system_settings/menu_configuration/menu/all`)
|
||||
|
||||
返回格式(扁平数组):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": [
|
||||
{ "menu_id": 1, "parent_id": 0, "url": "/index", "name": "首页", "icon": "home", "is_navi": 1 },
|
||||
{ "menu_id": 2, "parent_id": 0, "url": "/production_master_data", "name": "生产主数据", "icon": "cogs", "is_navi": 1 },
|
||||
{ "menu_id": 3, "parent_id": 2, "url": "/production_master_data/factory_model/factory_area", "name": "工厂区域", "icon": "map-marker", "is_navi": 1 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 阶段 2️⃣:新增/改造 API 层
|
||||
|
||||
**新增** `src/api/menu.js`(参照源项目 [menu.js](file:///D:\code\company\SCTMES_MES_V5\vue-app\src\api\system_settings\menu_configuration\menu.js)):
|
||||
|
||||
```js
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
export function getMenuAll(data) {
|
||||
return request({
|
||||
url: 'menu/all',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_menu_configuration_menu_all',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 阶段 3️⃣:改造 `menu.js` Store
|
||||
|
||||
**改造** [src/store/modules/d2admin/modules/menu.js](file:///d:\code\mes\mes-ui\src\store\modules\d2admin\modules\menu.js)
|
||||
|
||||
| 改动 | 内容 |
|
||||
|------|------|
|
||||
| state | 新增 `authKey: {}` |
|
||||
| `sourceDataLoad` action | 改为:localStorage 兜底 → 调后端 → `menu.install()` |
|
||||
| mutations | 新增 `headerAuth(state, source)` → 构建 `authKey` 字典 |
|
||||
|
||||
```js
|
||||
// 关键变更 — sourceDataLoad:
|
||||
async sourceDataLoad({ state, commit, dispatch }) {
|
||||
// 1. 先读 localStorage 缓存
|
||||
state.sourceData = await dispatch('d2admin/db/get', {
|
||||
dbName: 'database',
|
||||
path: '$menu.sourceData',
|
||||
defaultValue: [],
|
||||
user: true
|
||||
}, { root: true })
|
||||
|
||||
// 2. 缓存为空且已登录 → 调后端
|
||||
if (!state.sourceData.length && util.cookies.get('token')) {
|
||||
const res = await getMenuAll()
|
||||
state.sourceData = res || []
|
||||
await dispatch('d2admin/db/set', {
|
||||
dbName: 'database',
|
||||
path: '$menu.sourceData',
|
||||
value: state.sourceData,
|
||||
user: true
|
||||
}, { root: true })
|
||||
}
|
||||
|
||||
// 3. 构建菜单树 + 权限字典
|
||||
menuUtil.install(this, state.sourceData)
|
||||
}
|
||||
```
|
||||
|
||||
### 阶段 4️⃣:改造 `src/menu/index.js` — 从静态导出变为工具模块
|
||||
|
||||
将 [src/menu/index.js](file:///d:\code\mes\mes-ui\src\menu\index.js) 从"导出静态菜单配置"改为"菜单处理工具"(参照源项目):
|
||||
|
||||
```js
|
||||
import util from '@/libs/util'
|
||||
|
||||
function getMenuData(arr) {
|
||||
let tree = { header: [], aside: [] }
|
||||
|
||||
arr.forEach(value => {
|
||||
if (!value.is_navi) return // 过滤非导航菜单
|
||||
|
||||
const menuItem = {
|
||||
path: value.url,
|
||||
title: value.name,
|
||||
icon: value.icon,
|
||||
type: value.type,
|
||||
params: value.params
|
||||
}
|
||||
|
||||
if (value.parent_id === 0) {
|
||||
tree.header.push({ ...menuItem })
|
||||
}
|
||||
|
||||
menuItem.menu_id = value.menu_id
|
||||
menuItem.parent_id = value.parent_id
|
||||
tree.aside.push(menuItem)
|
||||
})
|
||||
|
||||
// 扁平数组转树
|
||||
tree.aside = util.formatDataToTree(tree.aside)
|
||||
return tree
|
||||
}
|
||||
|
||||
export default {
|
||||
install(vm, source) {
|
||||
vm.commit('d2admin/menu/headerAuth', source)
|
||||
const { header, aside } = getMenuData(source)
|
||||
vm.commit('d2admin/menu/headerSet', header)
|
||||
vm.commit('d2admin/menu/asideSet', aside)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
原有的静态菜单模块(`src/menu/modules/demo-*.js`)可保留但不再被引用,后续按需清理。
|
||||
|
||||
### 阶段 5️⃣:增加权限控制基础设施
|
||||
|
||||
#### 5.1 `$permission` 方法
|
||||
|
||||
**改造** `src/plugin/d2admin/index.js`,新增:
|
||||
|
||||
```js
|
||||
Vue.prototype.$permission = (value, type = 'menu') => {
|
||||
let path = ''
|
||||
const auth = store.state.d2admin.menu.authKey
|
||||
|
||||
switch (type) {
|
||||
case 'menu':
|
||||
path = value
|
||||
break
|
||||
case 'router':
|
||||
path = value.name.replace(/-/g, '/')
|
||||
path.slice(0, 1) !== '/' && (path = '/' + path)
|
||||
break
|
||||
}
|
||||
|
||||
return !!(path && Object.prototype.hasOwnProperty.call(auth, path))
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 `v-permission` 指令
|
||||
|
||||
**改造** `src/main.js`,新增:
|
||||
|
||||
```js
|
||||
Vue.directive('permission', {
|
||||
bind: (el, binding) => {
|
||||
if (!Vue.prototype.$permission(binding.value)) {
|
||||
el.parentNode ? el.parentNode.removeChild(el) : el.style.display = 'none'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 5.3 菜单点击权限过滤
|
||||
|
||||
**改造** [src/layout/header-aside/components/mixin/menu.js](file:///d:\code\mes\mes-ui\src\layout\header-aside\components\mixin\menu.js),增加 `getRouterAuthPath` 方法:
|
||||
|
||||
```js
|
||||
import { frameInRoutes } from '@/router/routes'
|
||||
|
||||
methods: {
|
||||
handleMenuSelect(index, indexPath) {
|
||||
if (/^d2-menu-empty-\d+$/.test(index) || index === undefined) {
|
||||
this.$message.warning('临时菜单')
|
||||
} else if (/^https:\/\/|http:\/\//.test(index)) {
|
||||
util.open(index)
|
||||
} else {
|
||||
this.getRouterAuthPath(index, indexPath)
|
||||
}
|
||||
},
|
||||
|
||||
getRouterAuthPath(index, indexPath) {
|
||||
if (index === '/index' || !indexPath || indexPath.length > 1) {
|
||||
this.$router.push({ path: index })
|
||||
return
|
||||
}
|
||||
|
||||
let router = null
|
||||
for (const value of frameInRoutes) {
|
||||
if (value.path === index) {
|
||||
router = value.children
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!router) {
|
||||
this.$router.push({ path: index })
|
||||
return
|
||||
}
|
||||
|
||||
for (const value of router) {
|
||||
const newPath = index + '/' + value.path
|
||||
if (this.$permission(newPath)) {
|
||||
this.$router.push({ path: newPath })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 阶段 6️⃣:改造 `main.js` 启动流程
|
||||
|
||||
**改造** [src/main.js](file:///d:\code\mes\mes-ui\src\main.js):
|
||||
|
||||
```js
|
||||
created() {
|
||||
this.$store.commit('d2admin/page/init', frameInRoutes)
|
||||
// 注释掉静态菜单设置:
|
||||
// this.$store.commit('d2admin/menu/headerSet', menuHeader)
|
||||
// this.$store.commit('d2admin/search/init', menuHeader)
|
||||
},
|
||||
mounted() {
|
||||
this.$store.commit('d2admin/releases/versionShow')
|
||||
this.$store.dispatch('d2admin/account/load') // → sourceDataLoad → 获取后端菜单
|
||||
this.$store.commit('d2admin/ua/get')
|
||||
this.$store.dispatch('d2admin/fullscreen/listen')
|
||||
},
|
||||
watch: {
|
||||
// 注释掉 $route.matched 侧边栏切换(由 store.aside 直接驱动)
|
||||
// '$route.matched': { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### 阶段 7️⃣:增加 `util.formatDataToTree` 工具函数
|
||||
|
||||
**改造** `src/libs/util.js`,新增:
|
||||
|
||||
```js
|
||||
util.formatDataToTree = (data, key = 'menu_id', pid = 'parent_id') => {
|
||||
if (!data || data.length <= 0) return []
|
||||
|
||||
let map = {}
|
||||
data.forEach(value => { map[value[key]] = { ...value } })
|
||||
|
||||
let tree = []
|
||||
data.forEach(item => {
|
||||
const id = item[key]
|
||||
if (map[id][pid] && map[map[id][pid]]) {
|
||||
if (!map[map[id][pid]].children) {
|
||||
map[map[id][pid]].children = []
|
||||
}
|
||||
map[map[id][pid]].children.push(map[id])
|
||||
} else {
|
||||
tree.push(map[id])
|
||||
}
|
||||
})
|
||||
return tree
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、影响的文件清单
|
||||
|
||||
| 文件 | 改动类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `src/api/menu.js` | **新增** | 菜单 API(`getMenuAll`) |
|
||||
| `src/store/modules/d2admin/modules/menu.js` | **改造** | `sourceDataLoad` 加后端调用 + `headerAuth` mutation + `authKey` state |
|
||||
| `src/menu/index.js` | **重写** | 从静态配置改为 `install()` 工具模块 |
|
||||
| `src/plugin/d2admin/index.js` | **改造** | 新增 `$permission` 方法 |
|
||||
| `src/main.js` | **改造** | 注释静态菜单设置;新增 `v-permission` 指令 |
|
||||
| `src/layout/header-aside/components/mixin/menu.js` | **改造** | 增加 `getRouterAuthPath` 权限过滤 |
|
||||
| `src/libs/util.js` | **改造** | 新增 `formatDataToTree` 工具函数 |
|
||||
| `src/menu/modules/demo-*.js` | **后续可删** | 静态菜单数据不再使用,单独清理 |
|
||||
|
||||
---
|
||||
|
||||
## 五、完整时序图
|
||||
|
||||
```
|
||||
用户登录
|
||||
│
|
||||
▼
|
||||
login/index.vue → dispatch('d2admin/account/login')
|
||||
│
|
||||
▼
|
||||
account.js login action
|
||||
├── loginAdminUser() → 后端登录接口
|
||||
├── util.cookies.set() → 存 token/uuid
|
||||
├── dispatch('d2admin/user/set') → 存用户信息
|
||||
└── dispatch('load')
|
||||
│
|
||||
├── d2admin/user/load → 加载用户名
|
||||
├── d2admin/theme/load → 加载主题
|
||||
├── d2admin/transition/load → 加载过渡效果
|
||||
├── d2admin/page/openedLoad → 加载标签页
|
||||
├── d2admin/menu/asideLoad → 加载侧边栏收起设置
|
||||
├── d2admin/size/load → 加载全局尺寸
|
||||
├── d2admin/color/load → 加载颜色设置
|
||||
└── d2admin/menu/sourceDataLoad
|
||||
│
|
||||
├── 先读 localStorage 缓存
|
||||
├── 缓存空 → GET /api/menu/all 后端接口
|
||||
├── 写入 localStorage 缓存
|
||||
└── menu.install(this, sourceData)
|
||||
├── headerAuth → 构建 authKey 权限字典
|
||||
├── getMenuData → 扁平 → {header, aside} 树
|
||||
├── headerSet → 顶栏菜单渲染
|
||||
└── asideSet → 侧边栏菜单渲染
|
||||
│
|
||||
▼
|
||||
跳转到首页 → 菜单已按权限渲染
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、注意事项
|
||||
|
||||
1. **路由仍是静态注册的**:mes-ui 不需要改造成动态 `addRoute`,路由全部在 `router/routes.js` 中静态定义即可,权限通过菜单显示/隐藏 + `getRouterAuthPath` 控制。
|
||||
2. **不要删除源项目代码**:源项目在 `D:\code\company\SCTMES_MES_V5\vue-app`,仅作为参照,不对其做任何修改。
|
||||
3. **localStorage 缓存**是离线兜底:首次登录调后端,后续从缓存读取;注销时清空缓存。
|
||||
4. **`block` cookie**:已在登录页 `mounted` 中设为 `'false'`,注销时设为 `'true'`,用于控制错误日志是否弹出提示。
|
||||
424
docs/migration-task-list.md
Normal file
424
docs/migration-task-list.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# MES-UI 搬迁任务列表
|
||||
|
||||
> 从旧项目 `D:\code\company\SCTMES_MES_V5\vue-app` 搬迁至本项目 `d:\code\mes\mes-ui`
|
||||
|
||||
## 搬迁规则
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 目录结构 | 按 `后台Webman界面截图对照表.md` 中的 一级→二级→三级 模块层级创建 |
|
||||
| 页面视图 | `src/views/{一级}/{二级}/{三级}/index.vue` |
|
||||
| API 接口 | `src/api/{一级}/{二级}.js` |
|
||||
| Vuex Store | `src/store/modules/{一级}/{二级}.js` |
|
||||
| 路由 | `src/router/modules/{一级}.js`,汇总到 `routes.js` |
|
||||
| 组件 | 公用组件放 `src/components/`,模块内组件放 `views` 下的 `components/` |
|
||||
| 表格 | 搬迁时统一替换为 `page-table` 或 `sct-table` + `sct-toolbar`(见 [重构方案](./sct-base-table-refactor-design.md)),不再使用旧的 `sct-base-table` |
|
||||
| 代码审查 | 搬迁前检查旧代码中的不合理处(见下方「代码审查与改进清单」),搬迁时一并修复 |
|
||||
|
||||
---
|
||||
|
||||
## 预览:前置准备
|
||||
|
||||
| ID | 任务 | 源文件 | 目标位置 | 状态 |
|
||||
|----|------|--------|----------|:----:|
|
||||
| P0 | 创建目标目录结构(所有一级/二级/三级空文件夹) | — | `src/views/` | ⬜ |
|
||||
| P1 | 迁移公共服务层(axios 实例、工具函数等) | `src/api/service.js` `src/api/tools.js` `src/api/index.js` `src/libs/` | `src/api/` `src/libs/` | ⬜ |
|
||||
| P2 | 迁移登录页面、首页、404/刷新/重定向等系统通用页面 | `src/views/system_settings/system_monitoring/system/` `src/views/system_settings/home_page/` | `src/views/system-settings/` | ⬜ |
|
||||
| P3 | 迁移路由入口和 routes.js 汇总 | `src/router/index.js` `src/router/routes.js` | `src/router/` | ⬜ |
|
||||
| P4 | 迁移全局 Store(d2admin) | `src/store/modules/d2admin/` | `src/store/modules/d2admin/` | ⬜ |
|
||||
| P5 | 迁移布局组件(header-aside)及菜单配置 | `src/layout/` `src/menu/` | `src/layout/` `src/menu/` | ⬜ |
|
||||
| P6 | 迁移第三方依赖(main.js 中的插件、组件注册) | `src/main.js` `src/App.vue` `src/plugin/` | `src/main.js` `src/App.vue` `src/plugin/` | ⬜ |
|
||||
| P7 | 迁移本地化文件(i18n) | `src/locales/` `src/i18n.js` | `src/locales/` `src/i18n.js` | ⬜ |
|
||||
| P8 | 迁移全局设置 | `src/setting.js` `.env` 系列 | `src/setting.js` `.env` 系列 | ⬜ |
|
||||
| P9 | 移植旧项目专用组件(form、sct-ace 等) | `src/components/battery/` `src/components/calculation/` `src/components/device-monitor/` `src/components/dm-print/` `src/components/form/` `src/components/menu-tree/` `src/components/sct-ace/` `src/components/sct-ace-editor/` `src/components/OCR/` | `src/components/` | ⬜ |
|
||||
| P10 | 迁移旧项目 public 目录下的静态资源 | `public/` | `public/` | ⬜ |
|
||||
| P11 | 迁移旧项目 assets 资源 | `src/assets/` | `src/assets/` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 一、系统设置 (System Administration)
|
||||
|
||||
> 目标目录根:`src/views/system-settings/`
|
||||
|
||||
### 1.1 用户管理 (User Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| S1 | 角色 (Role) | `system_settings/user_management/role/` | `system_settings/user_management/role.js` | `sctmesadmin/modules/role.js` | `system-settings/user-management/role/` | ⬜ |
|
||||
| S2 | 用户 (User) | `system_settings/user_management/user/` | `system_settings/user_management/user.js` | `sctmesadmin/modules/user.js` | `system-settings/user-management/user/` | ⬜ |
|
||||
|
||||
### 1.2 菜单管理 (Menu Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| S3 | 菜单配置 | `system_settings/menu_configuration/menu/` | `system_settings/menu_configuration/menu.js` | `system-settings/menu-management/menu-configuration/` | ⬜ |
|
||||
|
||||
### 1.3 系统助手 (System Utilities)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| S4 | 操作日志 | `system_settings/system_assistant/operate_log/` | `system_settings/system_assistant/operate_log.js` | `system-settings/system-utilities/operation-logs/` | ⬜ |
|
||||
| S5 | 接口日志 | `system_settings/system_assistant/interface_log/` | `system_settings/system_assistant/interface_log.js` | `system-settings/system-utilities/api-logs/` | ⬜ |
|
||||
|
||||
### 1.4 系统监控 (System Monitoring)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| S6 | 监控设置 | `system_settings/system_monitoring/setting/` | `system_settings/system_monitor/setting.js` | `system-settings/system-monitoring/monitoring-configuration/` | ⬜ |
|
||||
|
||||
### 1.5 系统设置 - 路由汇总
|
||||
|
||||
| ID | 任务 | 源文件 | 目标位置 | 状态 |
|
||||
|----|------|--------|----------|:----:|
|
||||
| S7 | 系统设置路由 | `router/system_settings/system.js` | `router/modules/system-settings.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 二、生产配置 (Production Master Data)
|
||||
|
||||
> 目标目录根:`src/views/production-configuration/`
|
||||
|
||||
### 2.1 工厂模型 (Factory Model)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| P1 | 产线设置 | `production_configuration/factory_model/factory_line/` | 需从路由确认 | `sctmesadmin/modules/line.js` | `production-configuration/factory-model/production-line/` | ⬜ |
|
||||
| P2 | 工厂区域 | `production_configuration/factory_model/factory_area/` | 需从路由确认 | `sctmesadmin/modules/area.js` | `production-configuration/factory-model/factory-area/` | ⬜ |
|
||||
|
||||
### 2.2 工艺模型 (Process Model)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| P3 | 工艺流程类别 | `production_configuration/technology_model/technology_flow_category/` | 需从路由确认 | `sctmesadmin/modules/technology_category.js` | `production-configuration/process-model/process-category/` | ⬜ |
|
||||
| P4 | 工序单元 | 需确认 | `production_configuration/workerman/workermanSet.js` | `sctmesadmin/modules/steps.js` | `production-configuration/process-model/process-step/` | ⬜ |
|
||||
| P5 | 工艺流程 | `production_configuration/technology_model/technology_flow/` | 需从路由确认 | `sctmesadmin/modules/technologyflow.js` | `production-configuration/process-model/process-routing/` | ⬜ |
|
||||
|
||||
### 2.3 产品管理 (Product Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| P6 | 产品列表 | `production_configuration/product_model/battery_model/` | 需从路由确认 | `sctmesadmin/modules/product_battery.js` | `production-configuration/product-management/product-list/` | ⬜ |
|
||||
| P7 | 不良管理 | `production_configuration/product_model/product_ng_info/` | 需从路由确认 | `sctmesadmin/modules/product_ng_info.js` | `production-configuration/product-management/defect-management/` | ⬜ |
|
||||
|
||||
### 2.4 物料模型 (Material Model)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| P8 | 物料类别列表 | `production_configuration/matetial_model/matetial_category/` | `warehouse/basic/material_category.js` | `sctmesadmin/modules/material_category.js` | `production-configuration/material-model/material-category/` | ⬜ |
|
||||
| P9 | 物料信息管理 | `production_configuration/matetial_model/matetial_management/` | `warehouse/basic/material.js` | — | `production-configuration/material-model/material-master/` | ⬜ |
|
||||
| P10 | BOM物料清单 | `production_configuration/matetial_model/bom/` | `production_configuration/matetial_model/bom.js` | — | `production-configuration/material-model/bom/` | ⬜ |
|
||||
| P11 | 计量单位 | `production_configuration/matetial_model/unit/` | `production_configuration/matetial_model/unit.js` | `sctmesadmin/modules/unit.js` | `production-configuration/material-model/unit-of-measure/` | ⬜ |
|
||||
|
||||
### 2.5 SPC采集模型 (SPC Configuration)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| P12 | SPC采集配置 | `production_configuration/spc_configuration/binding_scada_node/` | 需从路由确认 | `production-configuration/spc-configuration/spc-data-collection/` | ⬜ |
|
||||
|
||||
### 2.6 班组模型 (Team Model)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| P13 | 班组管理 | `system_settings/organization/production_team_manage/` | 需从路由确认 | `sctmesadmin/modules/production_team_manage.js` | `production-configuration/team-model/team-management/` | ⬜ |
|
||||
| P14 | 班次管理 | `system_settings/organization/production_shift_management/` | 需从路由确认 | — | `production-configuration/team-model/shift-management/` | ⬜ |
|
||||
| P15 | 排班日历 | `system_settings/organization/production_shift_calender/` | 需从路由确认 | — | `production-configuration/team-model/scheduling-calendar/` | ⬜ |
|
||||
|
||||
### 2.7 生产配置 - 路由汇总
|
||||
|
||||
| ID | 任务 | 源文件 | 目标位置 | 状态 |
|
||||
|----|------|--------|----------|:----:|
|
||||
| P16 | 生产配置路由 | `router/production_configuration/index.js` | `router/modules/production-configuration.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 三、设备模型 (Equipment Management)
|
||||
|
||||
> 目标目录根:`src/views/equipment-management/`
|
||||
|
||||
### 3.1 设备类别 (Equipment Category)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| E1 | 设备类别 | `production_configuration/device_model/device_category/` | 需从路由确认 | `sctmesadmin/modules/device_category.js` | `equipment-management/equipment-category/` | ⬜ |
|
||||
|
||||
### 3.2 设备信息 (Equipment Info)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| E2 | 设备信息 | `production_configuration/device_model/device_management/` | 需从路由确认 | `sctmesadmin/modules/device.js` | `equipment-management/equipment-info/` | ⬜ |
|
||||
|
||||
### 3.3 设备点检 (Inspection Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| E3 | 设备点检项目 | `device_management/device_check/device_check_items/` | 需从路由确认 | `sctmesadmin/modules/device_check_items.js` | `equipment-management/inspection-management/inspection-items/` | ⬜ |
|
||||
| E4 | 设备点检记录 | `device_management/device_check/device_check_record/` | 需从路由确认 | — | `equipment-management/inspection-management/inspection-records/` | ⬜ |
|
||||
| E5 | 设备点检日志 | `device_management/device_check/device_check_items_log/` | 需从路由确认 | — | `equipment-management/inspection-management/inspection-logs/` | ⬜ |
|
||||
|
||||
### 3.4 设备保养 (Maintenance Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| E6 | 设备保养项目 | `device_management/device_maintain/device_maintain_items/` | 需从路由确认 | `sctmesadmin/modules/device_maintain_items.js` | `equipment-management/maintenance-management/maintenance-items/` | ⬜ |
|
||||
| E7 | 设备保养详情 | `device_management/device_maintain/device_maintain_items_details/` | 需从路由确认 | — | `equipment-management/maintenance-management/maintenance-details/` | ⬜ |
|
||||
| E8 | 设备保养日志 | `device_management/device_maintain/device_maintain_items_log/` | 需从路由确认 | — | `equipment-management/maintenance-management/maintenance-logs/` | ⬜ |
|
||||
|
||||
### 3.5 设备维修 (Repair Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| E9 | 设备维修管理 | `device_management/device_repair/device_repair_management/` | 需从路由确认 | `equipment-management/repair-management/repair-management/` | ⬜ |
|
||||
| E10 | 设备维修日志 | `device_management/device_repair/device_repair_log/` | 需从路由确认 | `equipment-management/repair-management/repair-logs/` | ⬜ |
|
||||
|
||||
### 3.6 设备损耗品 (Consumables Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| E11 | 设备损耗品类别 | `device_management/device_consumables/device_consumables_category/` | 需从路由确认 | `sctmesadmin/modules/device_consumables_category.js` | `equipment-management/consumables/consumables-category/` | ⬜ |
|
||||
| E12 | 设备损耗品项目 | `device_management/device_consumables/device_consumables_items/` | 需从路由确认 | `sctmesadmin/modules/device_consumables_items.js` | `equipment-management/consumables/consumables-items/` | ⬜ |
|
||||
| E13 | 设备损耗品寿命管理 | 需确认 | 需从路由确认 | `sctmesadmin/modules/device_consumables_lifetime_management.js` | `equipment-management/consumables/consumables-lifecycle/` | ⬜ |
|
||||
| E14 | 设备损耗品更换日志 | `device_management/device_consumables/device_consumables_replace_log/` | 需从路由确认 | — | `equipment-management/consumables/replacement-logs/` | ⬜ |
|
||||
|
||||
### 3.7 设备模型 - 路由汇总
|
||||
|
||||
| ID | 任务 | 源文件 | 目标位置 | 状态 |
|
||||
|----|------|--------|----------|:----:|
|
||||
| E15 | 设备模型路由 | `router/device_management/device.js` | `router/modules/equipment-management.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 四、计划与生产 (Planning & Production)
|
||||
|
||||
> 目标目录根:`src/views/planning-production/`
|
||||
|
||||
### 4.1 生产批次管理 (Batch Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| B1 | 批次列表 | `planning_production/production_batch_management/batch/` | `data_dashboards/produce/batch.js` | `sctmesadmin/modules/batch.js` | `planning-production/batch-management/batch-list/` | ⬜ |
|
||||
| B2 | 批次托盘 | `planning_production/production_batch_management/batch_tray/` | 需从路由确认 | — | `planning-production/batch-management/tray-tracking/` | ⬜ |
|
||||
| B3 | 生产批次不良报表 | `planning_production/production_batch_management/bad/` | 需从路由确认 | — | `planning-production/batch-management/batch-defect-report/` | ⬜ |
|
||||
|
||||
### 4.2 预警中心 (Alert Center)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| B4 | 预警中心 | 需确认(可能在 pannel/index) | `planning_production/pannel/index.js` | `planning-production/alert-center/` | ⬜ |
|
||||
|
||||
### 4.3 生产监控 (Production Monitoring)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|-----------|:----:|
|
||||
| B5 | 物料监控 | `planning_production/produce/monitor/wareroom/ic/` 或 WIP | `data_dashboards/produce/material/wip.js` | — | `planning-production/production-monitoring/material-monitoring/` | ⬜ |
|
||||
| B6 | 电池复投管理 | 需确认 | `data_dashboards/produce/battery/replace.js` | — | `planning-production/production-monitoring/rework-management/` | ⬜ |
|
||||
| B7 | 托盘管理 | `planning_production/produce/monitor/tray_manage/` | `data_dashboards/produce/tray/list.js` `planning_production/produce/tray_manage.js` | — | `planning-production/production-monitoring/tray-management/` | ⬜ |
|
||||
| B8 | 托盘登录 | `planning_production/produce/monitor/tray_login/` | `planning_production/battery/login.js` `planning_production/produce/tray_login.js` | — | `planning-production/production-monitoring/tray-registration/` | ⬜ |
|
||||
| B9 | 设备监控 | `planning_production/produce/monitor/device/` | `data_dashboards/produce/report/device.js` | — | `planning-production/production-monitoring/equipment-monitoring/` | ⬜ |
|
||||
| B10 | 电池工序管理 | `planning_production/produce/monitor/batch_battery/` 等 | `planning_production/produce/batch_battery.js` | `sctmesadmin/modules/batch_battery.js` | `planning-production/production-monitoring/process-execution/` | ⬜ |
|
||||
|
||||
### 4.4 计划与生产 - 路由汇总
|
||||
|
||||
| ID | 任务 | 源文件 | 目标位置 | 状态 |
|
||||
|----|------|--------|----------|:----:|
|
||||
| B11 | 计划与生产路由 | `router/planning_production/index.js` | `router/modules/planning-production.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 五、质量管理 (Quality Management)
|
||||
|
||||
> 目标目录根:`src/views/quality-management/`
|
||||
|
||||
### 5.1 过程控制 (Process Control)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| Q1 | 检验类别管理 | `quality_control/first_inspection/category/` | `quality_control/xqc/inspection_category.js` | `quality-management/process-control/inspection-type/` | ⬜ |
|
||||
| Q2 | 首巡检项目配置 | `quality_control/first_inspection/setting/` | 需从路由确认 | `quality-management/process-control/first-article-inspection-config/` | ⬜ |
|
||||
| Q3 | 首巡检录入 | `quality_control/first_inspection/input/` | 需从路由确认 | `quality-management/process-control/first-article-inspection-records/` | ⬜ |
|
||||
| Q4 | 首巡检报表 | `quality_control/first_inspection/report/` | 需从路由确认 | `quality-management/process-control/first-article-inspection-reports/` | ⬜ |
|
||||
|
||||
### 5.2 检验控制 (Inspection Management)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| Q5 | 检验单管理 | `quality_control/xqc/inspection_order_manage/` | `quality_control/xqc/inspection_order_manage.js` | `quality-management/inspection-control/inspection-orders/` | ⬜ |
|
||||
| Q6 | 检验标准 | `quality_control/xqc/inspection_standard/` | `quality_control/xqc/inspection_standard.js` | `quality-management/inspection-control/inspection-standards/` | ⬜ |
|
||||
| Q7 | 接收质量限 (AQL) | `quality_control/xqc/aql_config/` `quality_control/xqc/aql_sample/` | `quality_control/xqc/aql_config.js` `quality_control/xqc/aql_sample.js` | `quality-management/inspection-control/aql-standards/` | ⬜ |
|
||||
| Q8 | 检测方案维护 | `quality_control/xqc/inspection_plan/` | `quality_control/xqc/inspection_plan.js` | `quality-management/inspection-control/inspection-plans/` | ⬜ |
|
||||
| Q9 | 检验项目 | `quality_control/xqc/inspection_item/` | `quality_control/xqc/inspection_item.js` | `quality-management/inspection-control/inspection-items/` | ⬜ |
|
||||
| Q10 | 抽样方案配置 | `quality_control/xqc/sampling_plan/` | `quality_control/xqc/sampling_plan.js` | `quality-management/inspection-control/sampling-plans/` | ⬜ |
|
||||
|
||||
### 5.3 SPC统计过程控制 (SPC Control)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| Q11 | SPC渲染条件配置 | `spc/manage/` | `spc/index.js` | `quality-management/spc-control/spc-configuration/` | ⬜ |
|
||||
|
||||
### 5.4 SPC计量型报表 (SPC Variable Charts)
|
||||
|
||||
| ID | 图表 | 源 views | 目标 views | 状态 |
|
||||
|----|------|---------|----------|:----:|
|
||||
| Q12 | XBar-R | `spc/spc_chart/xbar-r/` | `quality-management/spc-variable-charts/xbar-r/` | ⬜ |
|
||||
| Q13 | XBar-S | `spc/spc_chart/xbar-s/` | `quality-management/spc-variable-charts/xbar-s/` | ⬜ |
|
||||
| Q14 | I-MR | `spc/spc_chart/i-mr/` | `quality-management/spc-variable-charts/i-mr/` | ⬜ |
|
||||
| Q15 | Levey-Jennings | `spc/spc_chart/levey-jennings/` | `quality-management/spc-variable-charts/levey-jennings/` | ⬜ |
|
||||
| Q16 | EWMA | `spc/spc_chart/ewma/` | `quality-management/spc-variable-charts/ewma/` | ⬜ |
|
||||
| Q17 | CUSUM | `spc/spc_chart/cusum/` | `quality-management/spc-variable-charts/cusum/` | ⬜ |
|
||||
| Q18 | MA | `spc/spc_chart/ma/` | `quality-management/spc-variable-charts/ma/` | ⬜ |
|
||||
| Q19 | MAMR | `spc/spc_chart/mamr/` | `quality-management/spc-variable-charts/mamr/` | ⬜ |
|
||||
| Q20 | MAMS | `spc/spc_chart/mams/` | `quality-management/spc-variable-charts/mams/` | ⬜ |
|
||||
| Q21 | CPK | `spc/spc_chart/Cpk/` | `quality-management/spc-variable-charts/cpk/` | ⬜ |
|
||||
|
||||
### 5.5 SPC计数型报表 (SPC Attribute Charts)
|
||||
|
||||
| ID | 图表 | 源 views | 目标 views | 状态 |
|
||||
|----|------|---------|----------|:----:|
|
||||
| Q22 | DPMO | `spc/spc_chart/dpmo/` | `quality-management/spc-attribute-charts/dpmo/` | ⬜ |
|
||||
| Q23 | PChart | `spc/spc_chart/p-chart/` | `quality-management/spc-attribute-charts/p-chart/` | ⬜ |
|
||||
| Q24 | NPChart | `spc/spc_chart/np-chart/` | `quality-management/spc-attribute-charts/np-chart/` | ⬜ |
|
||||
| Q25 | CChart | `spc/spc_chart/c-chart/` | `quality-management/spc-attribute-charts/c-chart/` | ⬜ |
|
||||
| Q26 | UChart | `spc/spc_chart/u-chart/` | `quality-management/spc-attribute-charts/u-chart/` | ⬜ |
|
||||
|
||||
### 5.6 质量管理 - 路由汇总
|
||||
|
||||
| ID | 任务 | 源文件 | 目标位置 | 状态 |
|
||||
|----|------|--------|----------|:----:|
|
||||
| Q27 | 质量管理路由 | `router/quality_control/quality.js` | `router/modules/quality-management.js` | ⬜ |
|
||||
| Q28 | SPC路由 | `router/spc/index.js` | 合并到 `router/modules/quality-management.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 六、数据中台 (Data Platform)
|
||||
|
||||
> 目标目录根:`src/views/data-platform/`
|
||||
|
||||
### 6.1 基础追溯 (Traceability)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| D1 | 反向追溯 | `data_middleground/basic_traceability/reverse_direction_traceability/` 或正向追溯 | `data_dashboards/produce/traceability/battery.js` | `data-platform/traceability/backward/` | ⬜ |
|
||||
| D2 | 正向追溯 | `data_middleground/basic_traceability/` 下确认 | `data_dashboards/produce/traceability/bom_batch.js` | `data-platform/traceability/forward/` | ⬜ |
|
||||
| D3 | 电池曲线 | `planning_production/produce/traceability/curve/` | `data_dashboards/produce/traceability/curve.js` | `data-platform/traceability/battery-curve/` | ⬜ |
|
||||
| D4 | 托盘追溯 | `planning_production/produce/traceability/tray/` | `data_dashboards/produce/traceability/tray.js` | `data-platform/traceability/tray/` | ⬜ |
|
||||
| D5 | 电池追溯 | `planning_production/produce/traceability/battery/` | `data_dashboards/produce/traceability/battery.js` | `data-platform/traceability/battery/` | ⬜ |
|
||||
|
||||
### 6.2 生产报表 (Production Reports)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| D6 | 设备履历报表 | `planning_production/produce/report/battery/` 或设备报告 | `data_dashboards/produce/report/all_report.js` `data_dashboards/produce/report/device.js` | `data-platform/production-reports/equipment-history/` | ⬜ |
|
||||
| D7 | 电池详情报表 | `planning_production/produce/report/battery_details_report/` 或电池报告 | `data_dashboards/produce/report/battery.js` | `data-platform/production-reports/battery-detail/` | ⬜ |
|
||||
|
||||
### 6.3 相关性分析 (Correlation Analysis)
|
||||
|
||||
| ID | 三级模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|---------|---------|--------|----------|:----:|
|
||||
| D8 | 鹰眼 (Hawkeye) | `data_middleground/eagle_eyes/` | `data_middle_office/eagle_eyes/index.js` | `data-platform/correlation-analysis/hawkeye/` | ⬜ |
|
||||
|
||||
### 6.4 数据中台 - 路由汇总
|
||||
|
||||
| ID | 任务 | 源文件 | 目标位置 | 状态 |
|
||||
|----|------|--------|----------|:----:|
|
||||
| D9 | 数据中台路由 | `router/data_middleground/index.js` | `router/modules/data-platform.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 七、仓储管理 (Warehouse)
|
||||
|
||||
> 目标目录根:`src/views/warehouse/`
|
||||
|
||||
| ID | 模块 | 源 views | 源 api | 源 store | 目标 views | 状态 |
|
||||
|----|------|---------|--------|----------|-----------|:----:|
|
||||
| W1 | 仓库设置(业务类型/单据类型/仓库/库区/库位/货架) | `warehouse/setting/` | `warehouse/setting/` | `warehouse/modules/` | `warehouse/settings/` | ⬜ |
|
||||
| W2 | 基础数据(客户/物料类别/物料/供应商/领料单) | `warehouse/basic/` | `warehouse/basic/` | `warehouse/modules/` | `warehouse/basic/` | ⬜ |
|
||||
| W3 | ERP接口(接口/采购订单/产品出库/发送日志) | `warehouse/erp/` | `warehouse/erp/` | — | `warehouse/erp/` | ⬜ |
|
||||
| W4 | 收货管理(采购入库/打印/来料检验/上架等) | `warehouse/receiving_management/` | `warehouse/receiving_management/` | — | `warehouse/receiving/` | ⬜ |
|
||||
| W5 | 发货管理(出库/委外出库等) | `warehouse/shipping_management/` | 需确认 | — | `warehouse/shipping/` | ⬜ |
|
||||
| W6 | 库存管理(库存/冻结/锁定/操作日志) | `warehouse/stock_management/` | `warehouse/stock_management/` | — | `warehouse/stock/` | ⬜ |
|
||||
| W7 | 作业管理(库存移动/盘点) | `warehouse/working/` | `warehouse/working/` | — | `warehouse/operations/` | ⬜ |
|
||||
| W8 | 仓储路由 | `router/warehouse/index.js` | — | — | `router/modules/warehouse.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 八、SCADA管理(保留旧模块,不在主文档中)
|
||||
|
||||
> 目标目录根:`src/views/scada-management/`
|
||||
|
||||
| ID | 模块 | 源 views | 源 api | 目标 views | 状态 |
|
||||
|----|------|---------|--------|----------|:----:|
|
||||
| SC1 | 车间管理(配置/点位) | `scada_manage/workshop_manage/` | `scada_manage/workshop_manage/` | `scada-management/workshop/` | ⬜ |
|
||||
| SC2 | 基础配置(SCADA配置/查询/节点映射/EMS) | `scada_manage/basic_configuration/` | `modules/scada.configure.api.js` | `scada-management/basic-config/` | ⬜ |
|
||||
| SC3 | 边缘服务器(配置/监控/日志) | `scada_manage/lecpserver/` `scada_manage/EdgeProcessorsManage/` | `modules/edgeServer.api.js` | `scada-management/edge-server/` | ⬜ |
|
||||
| SC4 | 设备采集监控 | `scada_manage/device_gather/` | `scada_manage/huankong_management/` | `scada-management/device-gather/` | ⬜ |
|
||||
| SC5 | SCADA路由 | `router/scada_management/scada.js` | — | `router/modules/scada-management.js` | ⬜ |
|
||||
|
||||
---
|
||||
|
||||
## 代码审查与改进清单
|
||||
|
||||
> 在搬迁过程中,逐项检查旧代码中的不合理之处,搬迁时一并修复。每个问题标注 **发现时间** 和 **处理状态**。
|
||||
|
||||
### 🔴 严重问题(必须修复)
|
||||
|
||||
| # | 问题描述 | 影响范围 | 改进方案 | 状态 |
|
||||
|---|---------|---------|---------|:--:|
|
||||
| CR01 | **拼写错误遍布路由和目录名**:`matetial`→应为`material`,`freezeunfreeze`→`freeze-unfreeze`,出现 23+ 处 | `router/production_configuration/index.js`<br>`views/production_configuration/matetial_model/` | 搬迁时统一修正为正确拼写,路由 path 也一并修正 | ⬜ |
|
||||
| CR02 | **生产代码残留 `console.log`**:285+ 处 `console.log/warn/error` 分布在 100+ 个文件中 | `src/views/` 几乎所有模块 | 移除所有调试用 `console.log`,仅保留关键 `console.error` 并使用统一日志工具 `util.log` | ⬜ |
|
||||
| CR03 | **重复/拷贝目录残留**:`scada_query copy/` 显式拷贝备份目录 | `views/scada_manage/basic_configuration/scada_query copy/` | 删除该拷贝目录,如需要保留则合并到正式目录 | ⬜ |
|
||||
|
||||
### 🟡 中等问题(建议修复)
|
||||
|
||||
| # | 问题描述 | 影响范围 | 改进方案 | 状态 |
|
||||
|---|---------|---------|---------|:--:|
|
||||
| CR04 | **API 注入模式混乱**:旧模式用 `require.context` 动态注入(`api/modules/*.api.js`),新模式用直接 `import`(`api/system_settings/`),两套并存 | `api/index.js` + `api/modules/` | 统一为直接 `import` 模式,移除 `require.context` 动态注入和 `api/modules/` 目录 | ⬜ |
|
||||
| CR05 | **`let` 滥用**:API 文件中大量 `let url = urls + 'xxx'`,URL 拼接后从不重新赋值 | `api/` 下所有文件 | 全部改为 `const` | ⬜ |
|
||||
| CR06 | **Store 初始化直接用 `localStorage` 无容错**:`JSON.parse(localStorage.getItem('roleData'))` 放在 state 声明顶层,解析失败会导致模块加载崩溃 | `store/modules/sctmesadmin/modules/role.js` 等 36 个模块 | 改为在 getter 或 action 中惰性读取,并用 try-catch 包裹 `JSON.parse` | ⬜ |
|
||||
| CR07 | **每个 API 函数都手动传 `method` 和 `platform` 参数**,冗余且容易遗漏 | `api/` 下 80+ 文件 | 在 `request` 公共层统一注入 `platform: 'background'`;`method` 参数通过约定 url 自动映射 | ⬜ |
|
||||
| CR08 | **axios 拦截器 `switch-case` 冗长**:响应拦截器整段 switch-case 映射 HTTP 状态码到错误消息 | `api/service.js` | 改用 Map 结构 | error.message = statusMessages[status] \|\| error.message,更简洁 | ⬜ |
|
||||
| CR09 | **路由文件命名不一致**:有 `system.js`、`device.js`、`scada.js`、`index.js` 混用 | `router/` 所有文件 | 统一为 `index.js`,按文件夹区分模块 | ⬜ |
|
||||
| CR10 | **`system_Assistant` 大小写不一致**:路由 path 中是 `system_Assistant`(大写A),目录名是 `system_assistant`(小写a) | `router/system_settings/system.js` | 统一为 `system-assistant`(kebab-case) | ⬜ |
|
||||
| CR11 | **Store 模块 `namespaced: true` 命名不规范**:部分模块路径很深 `sctmesadmin/modules/xxx`,难以维护 | `store/modules/sctmesadmin/` | 按功能打平为 `store/modules/{功能名}.js`,去掉多余嵌套 | ⬜ |
|
||||
|
||||
### 🟢 建议优化(可选)
|
||||
|
||||
| # | 问题描述 | 影响范围 | 改进方案 | 状态 |
|
||||
|---|---------|---------|---------|:--:|
|
||||
| CR12 | **页面组件 `PageHeader/PageMain` 内联模式冗余**:绝大多数页面都是 `index.vue` + `components/PageHeader/index.vue` + `components/PageMain/index.vue` 三层结构,PageHeader 大多只传几个 props | 所有 views | 评估是否可将简单的 PageHeader/PageMain 合并为单文件组件,减少目录嵌套 | ⬜ |
|
||||
| CR13 | **`Promise.resolve(...)` 包裹 sync 数据**:Store actions 中 `return Promise.resolve(res.data)` 在 `async` 函数里是多余的 | `store/modules/` 所有 actions | 直接 `return res.data`,`async` 函数自动包装返回值 | ⬜ |
|
||||
| CR14 | **国际化键名硬编码**:大量组件中 `$t('xxx')` 的 key 没有类型约束,容易拼错 | 所有 Vue 组件 | 搬迁后统一整理 i18n key,考虑用常量池管理 | ⬜ |
|
||||
| CR15 | **公共组件未按功能分组**:`components/` 下散落 `battery/`、`calculation/`、`tray/`、`technology/` 等多个业务组件与通用组件混放 | `src/components/` | 业务组件迁入对应模块 views 的 `components/`,公共组件保留并文档化 | ⬜ |
|
||||
| CR16 | **`sct-base-table` 组件架构臃肿**:215 行代码包含致命 bug(引入 yargs)、60% 死代码、buttonList/columns 在 100+ 页面中重复定义 | `components/sct-base-table/` 及所有引用页面 | 📄 详见 [sct-base-table 重构方案](./sct-base-table-refactor-design.md):拆分为 7 个小组件 + 5 个 composable 函数,消除重复,迁移与搬迁同步进行 | ⬜ |
|
||||
|
||||
### 📄 专项重构方案
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [sct-base-table 重构方案](./sct-base-table-refactor-design.md) | 组合式重构:拆组件+composable,消除 buttonList/columns 手动定义,覆盖 100+ 页面 |
|
||||
|
||||
---
|
||||
|
||||
## 搬迁统计
|
||||
|
||||
| 一级模块 | 三级页面数 | 路由文件 | API文件 | Store模块 | 已完成 |
|
||||
|---------|-----------|---------|---------|----------|:----:|
|
||||
| 系统设置 | 6 | 1 | 5 | 2 | 0/13 |
|
||||
| 生产配置 | 15 | 1 | ~10 | 12 | 0/37 |
|
||||
| 设备模型 | 14 | 1 | ~10 | 7 | 0/31 |
|
||||
| 计划与生产 | 10 | 1 | ~8 | 3 | 0/21 |
|
||||
| 质量管理 | 26 | 2 | ~15 | 0 | 0/43 |
|
||||
| 数据中台 | 8 | 1 | ~8 | 0 | 0/17 |
|
||||
| 仓储管理 | 25+ | 1 | ~20 | 11 | 0/56 |
|
||||
| SCADA管理 | 10+ | 1 | ~6 | 0 | 0/16 |
|
||||
| 前置准备 | — | 2 | — | 1 | 0/11 |
|
||||
| **合计** | **114+** | **11** | **82+** | **36** | **0/245** |
|
||||
|
||||
---
|
||||
|
||||
## 搬迁流程说明
|
||||
|
||||
每条 ID 任务完成后,将状态从 ⬜ 改为 ✅,并在下方补充日期和备注。
|
||||
|
||||
示例:
|
||||
```
|
||||
| S1 | 角色 (Role) | `...` | `...` | `...` | ✅ |
|
||||
```
|
||||
146
docs/sass-deprecation-fixes.md
Normal file
146
docs/sass-deprecation-fixes.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Sass 废弃警告修复记录
|
||||
|
||||
> 项目运行 `pnpm serve` 时,Dart Sass 输出了多类废弃 API 警告。本文档记录问题现象、根因及修复方案。
|
||||
|
||||
---
|
||||
|
||||
## 1. `legacy-js-api` — Sass Loader 旧 JS API 警告
|
||||
|
||||
### 问题现象
|
||||
|
||||
```
|
||||
Deprecation Warning [legacy-js-api]: The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0.
|
||||
```
|
||||
|
||||
### 根因
|
||||
|
||||
`sass-loader` 旧版本默认通过旧版 JS API 调用 Dart Sass。Dart Sass 计划在 2.0 中移除该 API。
|
||||
|
||||
### 修复
|
||||
|
||||
**文件:`package.json`**
|
||||
|
||||
将 `sass-loader` 升级到 `10.5.2`(v10 最后一个稳定版,兼容 Webpack 4),该版本支持 `silenceDeprecations` 选项。
|
||||
|
||||
```diff
|
||||
- "sass-loader": "^10.3.1",
|
||||
+ "sass-loader": "~10.5.2",
|
||||
```
|
||||
|
||||
**文件:`vue.config.js`**
|
||||
|
||||
在 `css.loaderOptions.sass` 中添加 `silenceDeprecations` 配置。
|
||||
|
||||
```js
|
||||
sass: {
|
||||
additionalData: '@use "@/assets/style/public.scss" as *;',
|
||||
sassOptions: {
|
||||
silenceDeprecations: ['legacy-js-api', 'import']
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. `import` — Sass `@import` 废弃警告
|
||||
|
||||
### 问题现象
|
||||
|
||||
```
|
||||
DEPRECATION WARNING [import]: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
|
||||
|
||||
╷
|
||||
1 │ @import '~@/assets/style/unit/color.scss';
|
||||
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
╵
|
||||
src\assets\style\public.scss 1:9 @use
|
||||
```
|
||||
|
||||
### 根因
|
||||
|
||||
`public.scss` 通过 `@import '~@/assets/style/unit/color.scss'` 引入颜色变量文件。Dart Sass 正在推动从 `@import` 迁移到 `@use` / `@forward` 模块系统。
|
||||
|
||||
### 为何不改成 `@use`
|
||||
|
||||
该 `@import` 使用了 Webpack 专有的 `~` 前缀做模块路径解析(`~@/assets/...`),`@use` 对此机制支持不稳定,直接替换可能导致构建失败。
|
||||
|
||||
### 修复
|
||||
|
||||
在上述 `silenceDeprecations` 数组中追加 `'import'`,静默该警告。
|
||||
|
||||
---
|
||||
|
||||
## 3. `global-builtin` — 全局内置函数 `nth()` 废弃警告
|
||||
|
||||
### 问题现象
|
||||
|
||||
```
|
||||
Deprecation Warning [global-builtin]: Global built-in functions are deprecated and will be removed in Dart Sass 3.0.0.
|
||||
Use list.nth instead.
|
||||
|
||||
╷
|
||||
42 │ .#{$prefix}-m-#{nth($sizes, $index)} { margin: #{nth($sizes, $index)}px !important; }
|
||||
│ ^^^^^^^^^^^^^^^^^^^
|
||||
╵
|
||||
src\assets\style\public-class.scss 42:19 @import
|
||||
src\App.vue 3:9 root stylesheet
|
||||
```
|
||||
|
||||
### 根因
|
||||
|
||||
Dart Sass 正在将全局内置函数迁移到命名空间模块。`nth()` 属于 `sass:list` 模块,不应再作为全局函数直接调用。
|
||||
|
||||
### 修复(从源码根治)
|
||||
|
||||
**文件:`src/assets/style/public-class.scss`**
|
||||
|
||||
**步骤 1** — 文件顶部引入 `sass:list` 模块:
|
||||
|
||||
```diff
|
||||
+ @use 'sass:list';
|
||||
@import 'public';
|
||||
```
|
||||
|
||||
**步骤 2** — 将 `@for` 循环中所有 `nth()` 替换为 `list.nth()`:
|
||||
|
||||
```diff
|
||||
@for $index from 1 to 6 {
|
||||
- .#{$prefix}-m-#{nth($sizes, $index)} { margin: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-mt-#{nth($sizes, $index)} { margin-top: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-mr-#{nth($sizes, $index)} { margin-right: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-mb-#{nth($sizes, $index)} { margin-bottom: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-ml-#{nth($sizes, $index)} { margin-left: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-p-#{nth($sizes, $index)} { padding: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-pt-#{nth($sizes, $index)} { padding-top: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-pr-#{nth($sizes, $index)} { padding-right: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-pb-#{nth($sizes, $index)} { padding-bottom: #{nth($sizes, $index)}px !important; }
|
||||
- .#{$prefix}-pl-#{nth($sizes, $index)} { padding-left: #{nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-m-#{list.nth($sizes, $index)} { margin: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-mt-#{list.nth($sizes, $index)} { margin-top: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-mr-#{list.nth($sizes, $index)} { margin-right: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-mb-#{list.nth($sizes, $index)} { margin-bottom: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-ml-#{list.nth($sizes, $index)} { margin-left: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-p-#{list.nth($sizes, $index)} { padding: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-pt-#{list.nth($sizes, $index)} { padding-top: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-pr-#{list.nth($sizes, $index)} { padding-right: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-pb-#{list.nth($sizes, $index)} { padding-bottom: #{list.nth($sizes, $index)}px !important; }
|
||||
+ .#{$prefix}-pl-#{list.nth($sizes, $index)} { padding-left: #{list.nth($sizes, $index)}px !important; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 涉及文件汇总
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|---------|
|
||||
| `package.json` | `sass-loader` 版本 `^10.3.1` → `~10.5.2` |
|
||||
| `vue.config.js` | 添加 `sassOptions.silenceDeprecations` 配置 |
|
||||
| `src/assets/style/public-class.scss` | 引入 `sass:list` 模块,`nth()` → `list.nth()` |
|
||||
|
||||
---
|
||||
|
||||
## 后续建议
|
||||
|
||||
- 当项目未来升级到 Vue CLI 5 / Webpack 5 时,可考虑将 `public.scss` 中的 `@import` 改为 `@use`(届时 `~` 前缀路径的兼容方案需要重新评估)。
|
||||
- `silenceDeprecations` 中的 `'import'` 为临时静默方案,待 `@import` 改造完成后移除。
|
||||
1241
docs/sct-base-table-refactor-design.md
Normal file
1241
docs/sct-base-table-refactor-design.md
Normal file
File diff suppressed because it is too large
Load Diff
953
docs/表格组件使用说明.md
Normal file
953
docs/表格组件使用说明.md
Normal file
@@ -0,0 +1,953 @@
|
||||
# 表格组件使用说明
|
||||
|
||||
> 基于 `page-table` + `page-dialog-form` 的新一<E696B0><E4B880>?CRUD 表格方案<E696B9><E6A188>?
|
||||
> 源码位置:`src/components/page-table/`、`src/components/page-dialog-form/`、`src/composables/`
|
||||
> 完整可运行示例:`src/views/production-master-data/factory-model/factory-area/index.vue`
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [快速开始(三步走)](#1-快速开始三步走)
|
||||
2. [完整示例代码](#2-完整示例代码)
|
||||
3. [组件 API 参考](#3-组件-api-参<><E58F82>?
|
||||
4. [Composable API 参考](#4-composable-api-参<><E58F82>?
|
||||
5. [i18n 国际化方案](#5-i18n-国际化方<E58C96><E696B9>?
|
||||
6. [常用场景速查](#6-常用场景速查)
|
||||
7. [路由配置](#7-路由配置)
|
||||
8. [API 文件写法](#8-api-文件写法)
|
||||
9. [旧代码迁移对照](#9-旧代码迁移对<E7A7BB><E5AFB9>?
|
||||
10. [常见问题排查](#10-常见问题排查)
|
||||
|
||||
---
|
||||
|
||||
## 1. 快速开始(三步走)
|
||||
|
||||
### 第一步:定义列和按钮(`created()` 中)
|
||||
|
||||
```js
|
||||
import { useTableColumns } from '@/composables/useTableColumns'
|
||||
import { useTableButtons } from '@/composables/useTableButtons'
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
|
||||
export default {
|
||||
mixins: [i18nMixin('page.模块<E6A8A1><E59D97>?二级模块.三级模块')],
|
||||
|
||||
created () {
|
||||
// --- 列定<E58897><E5AE9A>?---
|
||||
// 只需声明 prop <20><>?label,idx 自动补齐
|
||||
// prop: '_actions' 约定为操作列,自动渲<E58AA8><E6B8B2>?rowButtons
|
||||
this.columns = useTableColumns([
|
||||
{ prop: 'sort', label: this.key('sort'), width: 80 },
|
||||
{ prop: 'code', label: this.key('code'), minWidth: 120 },
|
||||
{ prop: 'name', label: this.key('name') },
|
||||
{ prop: '_actions', label: this.key('operation'), width: 160, fixed: 'right' }
|
||||
])
|
||||
|
||||
// --- 按钮定义 ---
|
||||
// 不再分开<E58886><E5BC80>?buttonList / tableButtonList
|
||||
const btns = useTableButtons({
|
||||
toolbar: [
|
||||
{ key: 'add', label: this.key('add'), icon: 'el-icon-plus',
|
||||
type: 'primary', auth: '/xxx/create', onClick: this.openAdd }
|
||||
],
|
||||
row: [
|
||||
{ key: 'edit', label: this.key('edit'), icon: 'el-icon-edit',
|
||||
auth: '/xxx/edit', onClick: this.openEdit },
|
||||
{ key: 'delete', label: this.key('delete'), icon: 'el-icon-delete',
|
||||
color: 'danger', auth: '/xxx/delete', onClick: this.handleDelete }
|
||||
]
|
||||
}, this.$permission) // <20><>?第二个参数传入权限校验函<E9AA8C><E587BD>?
|
||||
this.toolbarButtons = btns.toolbarButtons
|
||||
this.rowButtons = btns.rowButtons
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 第二步:写模板(`<template>` 中)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<d2-container>
|
||||
<!-- 搜索<EFBFBD><EFBFBD>?-->
|
||||
<template #header>
|
||||
<el-form :inline="true" size="mini">
|
||||
<el-form-item :label="$t(key('code'))">
|
||||
<el-input v-model="search.code" :placeholder="$t(key('enter_code'))"
|
||||
clearable style="width:200px" @keyup.enter.native="onSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="onSearch">
|
||||
{{ $t(key('search')) }}
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="onReset">
|
||||
{{ $t(key('reset')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<!-- 表格 + 按钮<EFBFBD><EFBFBD>?+ 分页 -->
|
||||
<page-table
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:toolbar-buttons="toolbarButtons"
|
||||
:row-buttons="rowButtons"
|
||||
:pagination="pagination"
|
||||
help-url="/help/your-page"
|
||||
auto-height
|
||||
@page-change="onPageChange"
|
||||
@selection-change="onSelect"
|
||||
/>
|
||||
|
||||
<!-- 新增/编辑弹框 -->
|
||||
<page-dialog-form
|
||||
ref="dialogForm"
|
||||
:visible.sync="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="35%"
|
||||
:form-cols="formCols"
|
||||
:form-data="formData"
|
||||
:rules="rules"
|
||||
:submitting="submitting"
|
||||
:confirm-text="$t(key('confirm'))"
|
||||
:cancel-text="$t(key('cancel'))"
|
||||
@submit="onDialogSubmit"
|
||||
@close="onDialogClose"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 第三步:写业务方法(增删改查<E694B9><E69FA5>?
|
||||
```js
|
||||
methods: {
|
||||
// 获取列表数据
|
||||
async fetchData () {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getList({
|
||||
...this.search,
|
||||
page_no: this.pagination.current,
|
||||
page_size: this.pagination.size
|
||||
})
|
||||
this.tableData = res.data || []
|
||||
this.pagination.total = res.count || 0
|
||||
} finally { this.loading = false }
|
||||
},
|
||||
|
||||
// 搜索 / 重置
|
||||
onSearch () { this.pagination.current = 1; this.fetchData() },
|
||||
onReset () { this.search = { code: '', name: '' }; this.pagination.current = 1; this.fetchData() },
|
||||
|
||||
// 分页变化
|
||||
onPageChange (page) {
|
||||
this.pagination.current = page.current
|
||||
this.pagination.size = page.size
|
||||
this.fetchData()
|
||||
},
|
||||
|
||||
// 新增:打开弹窗
|
||||
openAdd () {
|
||||
this.handleType = 'create'
|
||||
this.dialogTitle = this.key('add_title')
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dialogForm.reset()
|
||||
this.resetForm()
|
||||
this.dialogVisible = true
|
||||
})
|
||||
},
|
||||
|
||||
// 编辑:回填表<E5A1AB><E8A1A8>? openEdit (row) {
|
||||
this.handleType = 'edit'
|
||||
this.dialogTitle = this.key('edit_title')
|
||||
this.editId = row.id
|
||||
this.formData = { code: row.code, name: row.name }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
async onDialogSubmit () {
|
||||
this.submitting = true
|
||||
try {
|
||||
if (this.handleType === 'create') {
|
||||
await createApi(this.formData)
|
||||
} else {
|
||||
await editApi({ ...this.formData, id: this.editId })
|
||||
}
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.dialogVisible = false
|
||||
this.fetchData()
|
||||
} finally { this.submitting = false }
|
||||
},
|
||||
|
||||
// 关闭弹窗
|
||||
onDialogClose () { this.resetForm() },
|
||||
|
||||
// 删除
|
||||
async handleDelete (row) {
|
||||
try {
|
||||
await this.$confirm(
|
||||
this.$t(this.key('confirm_delete')),
|
||||
this.$t(this.key('tip')),
|
||||
{ confirmButtonText: this.$t(this.key('confirm')),
|
||||
cancelButtonText: this.$t(this.key('cancel')),
|
||||
type: 'warning', closeOnClickModal: false }
|
||||
)
|
||||
await deleteApi({ id: [row.id] })
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.fetchData()
|
||||
} catch (e) { /* 取消或失<E68896><E5A4B1>?*/ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 完整示例代码
|
||||
|
||||
> 📁 `src/views/production-master-data/factory-model/factory-area/index.vue`
|
||||
> 这是一<EFBFBD><EFBFBD>?*可直接运行的完整 CRUD 页面**,包含搜索栏、表格、分页、新<E38081><E696B0>?编辑弹框、删除确认<E7A1AE><E8AEA4>?
|
||||
### 模板部分
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<d2-container>
|
||||
<template #header>
|
||||
<div class="search-bar">
|
||||
<el-form :inline="true" size="mini">
|
||||
<el-form-item :label="$t(key('code'))">
|
||||
<el-input v-model="search.code" :placeholder="$t(key('enter_code'))"
|
||||
clearable style="width:200px" @keyup.enter.native="onSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('name'))">
|
||||
<el-input v-model="search.name" :placeholder="$t(key('enter_name'))"
|
||||
clearable style="width:200px" @keyup.enter.native="onSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="onSearch">
|
||||
{{ $t(key('search')) }}
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="onReset">
|
||||
{{ $t(key('reset')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<page-table
|
||||
ref="pageTable"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:toolbar-buttons="toolbarButtons"
|
||||
:row-buttons="rowButtons"
|
||||
:pagination="pagination"
|
||||
help-url="/help/factory-area"
|
||||
auto-height
|
||||
@page-change="onPageChange"
|
||||
@selection-change="onSelect"
|
||||
/>
|
||||
|
||||
<page-dialog-form
|
||||
ref="dialogForm"
|
||||
:visible.sync="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="35%"
|
||||
:form-cols="formCols"
|
||||
:form-data="formData"
|
||||
:rules="rules"
|
||||
:submitting="submitting"
|
||||
:confirm-text="$t(key('confirm'))"
|
||||
:cancel-text="$t(key('cancel'))"
|
||||
@submit="onDialogSubmit"
|
||||
@close="onDialogClose"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Script 部分
|
||||
|
||||
```js
|
||||
import { useTableColumns } from '@/composables/useTableColumns'
|
||||
import { useTableButtons } from '@/composables/useTableButtons'
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import { getFactoryAreaList, createFactoryArea, editFactoryArea, deleteFactoryArea }
|
||||
from '@/api/production-master-data/factory-area'
|
||||
import PageTable from '@/components/page-table'
|
||||
import PageDialogForm from '@/components/page-dialog-form'
|
||||
|
||||
export default {
|
||||
name: 'factory-area',
|
||||
components: { PageTable, PageDialogForm },
|
||||
mixins: [i18nMixin('page.production_master_data.factory_model.factory_area')],
|
||||
|
||||
data () {
|
||||
const t = this.$t.bind(this)
|
||||
const k = (s) => t(this.key(s))
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
tableData: [],
|
||||
selectedRows: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
editId: '',
|
||||
handleType: 'create',
|
||||
search: { code: '', name: '' },
|
||||
pagination: { current: 1, size: 10, total: 0 },
|
||||
formData: { code: '', name: '', remark: '' },
|
||||
rules: {
|
||||
code: [
|
||||
{ required: true, message: k('enter_code'), trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: k('remark_length'), trigger: 'blur' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: k('enter_name'), trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: k('remark_length'), trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
columns: [],
|
||||
toolbarButtons: [],
|
||||
rowButtons: [],
|
||||
formCols: [
|
||||
[
|
||||
{ type: 'input', prop: 'code',
|
||||
label: k('code'), placeholder: k('enter_code'),
|
||||
clearable: true, style: { width: '90%' } }
|
||||
],
|
||||
[
|
||||
{ type: 'input', prop: 'name',
|
||||
label: k('name'), placeholder: k('enter_name'),
|
||||
clearable: true, style: { width: '90%' } }
|
||||
],
|
||||
[
|
||||
{ type: 'input', prop: 'remark', inputType: 'textarea',
|
||||
autosize: { minRows: 2, maxRows: 6 },
|
||||
label: k('remark'), placeholder: k('remark_required'),
|
||||
clearable: true, style: { width: '90%' } }
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.columns = useTableColumns([
|
||||
{ prop: 'sort', label: this.key('sort'), width: 80 },
|
||||
{ prop: 'code', label: this.key('code'), minWidth: 120 },
|
||||
{ prop: 'name', label: this.key('name'), minWidth: 120 },
|
||||
{ prop: 'remark', label: this.key('remark') },
|
||||
{ prop: '_actions', label: this.key('operation'), width: 160, fixed: 'right' }
|
||||
])
|
||||
const btns = useTableButtons({
|
||||
toolbar: [
|
||||
{ key: 'add', label: this.key('add'), icon: 'el-icon-plus', type: 'primary',
|
||||
auth: '/production_master_data/factory_model/factory_area/create',
|
||||
onClick: this.openAdd }
|
||||
],
|
||||
row: [
|
||||
{ key: 'edit', label: this.key('edit'), icon: 'el-icon-edit',
|
||||
auth: '/production_master_data/factory_model/factory_area/edit',
|
||||
onClick: this.openEdit },
|
||||
{ key: 'delete', label: this.key('delete'), icon: 'el-icon-delete',
|
||||
color: 'danger',
|
||||
auth: '/production_master_data/factory_model/factory_area/delete',
|
||||
onClick: this.handleDelete }
|
||||
]
|
||||
}, this.$permission)
|
||||
this.toolbarButtons = btns.toolbarButtons
|
||||
this.rowButtons = btns.rowButtons
|
||||
this.fetchData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchData () {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getFactoryAreaList({
|
||||
...this.search, page_no: this.pagination.current, page_size: this.pagination.size
|
||||
})
|
||||
this.tableData = res.data || []
|
||||
this.pagination.total = res.count || 0
|
||||
} finally { this.loading = false }
|
||||
},
|
||||
|
||||
onSearch () { this.pagination.current = 1; this.fetchData() },
|
||||
onReset () { this.search = { code: '', name: '' }; this.pagination.current = 1; this.fetchData() },
|
||||
onPageChange (page) {
|
||||
this.pagination.current = page.current
|
||||
this.pagination.size = page.size
|
||||
this.fetchData()
|
||||
},
|
||||
onSelect (rows) { this.selectedRows = rows },
|
||||
|
||||
resetForm () { this.formData = { code: '', name: '', remark: '' }; this.editId = '' },
|
||||
|
||||
openAdd () {
|
||||
this.handleType = 'create'
|
||||
this.dialogTitle = this.key('add_title')
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dialogForm && this.$refs.dialogForm.reset()
|
||||
this.resetForm()
|
||||
this.dialogVisible = true
|
||||
})
|
||||
},
|
||||
|
||||
openEdit (row) {
|
||||
this.handleType = 'edit'
|
||||
this.dialogTitle = this.key('edit_title')
|
||||
this.editId = row.id
|
||||
this.formData = { code: row.code, name: row.name, remark: row.remark || '' }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
async onDialogSubmit () {
|
||||
this.submitting = true
|
||||
try {
|
||||
if (this.handleType === 'create') {
|
||||
await createFactoryArea(this.formData)
|
||||
} else {
|
||||
await editFactoryArea({ ...this.formData, id: this.editId })
|
||||
}
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.dialogVisible = false
|
||||
this.fetchData()
|
||||
} finally { this.submitting = false }
|
||||
},
|
||||
|
||||
onDialogClose () { this.resetForm() },
|
||||
|
||||
async handleDelete (row) {
|
||||
try {
|
||||
await this.$confirm(
|
||||
this.$t(this.key('confirm_delete')),
|
||||
this.$t(this.key('tip')),
|
||||
{ confirmButtonText: this.$t(this.key('confirm')),
|
||||
cancelButtonText: this.$t(this.key('cancel')),
|
||||
type: 'warning', closeOnClickModal: false }
|
||||
)
|
||||
await deleteFactoryArea({ id: [row.id] })
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.pagination.current = Math.min(
|
||||
this.pagination.current,
|
||||
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
|
||||
)
|
||||
this.fetchData()
|
||||
} catch (e) { /* 取消删除 / 请求失败不处<E4B88D><E5A484>?*/ }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 组件 API 参<><E58F82>?
|
||||
### 3.1 `page-table` <20><>?表格 + 按钮<E68C89><E992AE>?+ 分页
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | 类型 | 默认<E9BB98><E8AEA4>?| 说明 |
|
||||
|------|------|--------|------|
|
||||
| `columns` | `Array` | `[]` | 列定义,<E4B989><EFBC8C>?`useTableColumns()` 生成 |
|
||||
| `data` | `Array` | `[]` | 表格行数<E8A18C><E695B0>?|
|
||||
| `loading` | `Boolean` | `false` | 是否显示 loading 遮罩 |
|
||||
| `height` | `String/Number` | <20><>?| 表格高度。不传则随内容撑开;传具体数值固定高度;<E5BAA6><EFBC9B>?`'auto'` 启用自适应 |
|
||||
| `auto-height` | `Boolean` | `false` | 启用高度自适应,表格自动填满可用空<E794A8><E7A9BA>?|
|
||||
| `border` | `Boolean` | `true` | 是否带边<E5B8A6><E8BEB9>?|
|
||||
| `row-key` | `String` | `'id'` | 行唯一 key |
|
||||
| `toolbar-buttons` | `Array` | `[]` | 顶部工具栏按钮,<E992AE><EFBC8C>?`useTableButtons()` 生成 |
|
||||
| `row-buttons` | `Array` | `[]` | 行内操作按钮,由 `useTableButtons()` 生成 |
|
||||
| `pagination` | `Object` | `null` | 分页参数 `{ current, size, total }`,传了才显示分页 |
|
||||
| `table-attrs` | `Object` | `{}` | 额外透传<E9808F><E4BCA0>?`el-table` 的属<E79A84><E5B19E>?|
|
||||
| `table-listeners` | `Object` | `{}` | 额外透传<E9808F><E4BCA0>?`el-table` 的事<E79A84><E4BA8B>?|
|
||||
| `help-url` | `String` | `''` | 帮助文档跳转 URL。传了才显示工具栏右侧的问号按钮,点击新窗口打开 |
|
||||
| `help-text` | `String` | `'帮助'` | 帮助按钮文字,支<EFBC8C><E694AF>?i18n key(组件自<E4BBB6><E887AA>?`$t()` 翻译<E7BFBB><E8AF91>?|
|
||||
|
||||
#### 事件
|
||||
|
||||
| 事件<E4BA8B><E4BBB6>?| 参数 | 说明 |
|
||||
|--------|------|------|
|
||||
| `@page-change` | `{ current, size, total }` | 分页变化(切换页<E68DA2><E9A1B5>?条数<E69DA1><E695B0>?|
|
||||
| `@selection-change` | `rows: Array` | 选中行变<E8A18C><E58F98>?|
|
||||
| `@sort-change` | 透传 el-table 原生事件 | 排序变化 |
|
||||
|
||||
#### 插槽
|
||||
|
||||
| 插槽<E68F92><E6A7BD>?| 作用<E4BD9C><E794A8>?| 说明 |
|
||||
|--------|--------|------|
|
||||
| `#col-{prop}` | `{ row, index }` | 自定义列的渲染(列定义中 prop 需<><E99C80>?`slot: true`<EFBFBD><EFBFBD>?|
|
||||
| `#toolbar-extra` | <20><>?| 工具栏区域追加自定义内容 |
|
||||
| `#empty` | <20><>?| 表格空数据时的占<E79A84><E58DA0>?|
|
||||
| `#append` | <20><>?| 表格最后一行后追加 |
|
||||
| `#extra` | <20><>?| 页面底部追加区域 |
|
||||
|
||||
#### 列定义规<E4B989><E8A784>?
|
||||
```js
|
||||
// 普通列
|
||||
{ prop: 'code', label: '编码', minWidth: 120 }
|
||||
|
||||
// 操作列(约定:prop === '_actions'<27><>?{ prop: '_actions', label: '操作', width: 160, fixed: 'right' }
|
||||
|
||||
// 自定义插槽列(slot: true<75><65>?{ prop: 'status', label: '状<><E78AB6>?, slot: true, width: 100 }
|
||||
|
||||
// 复选框<E98089><E6A186>?+ 序号列(通过 useTableColumns 第二个参数)
|
||||
useTableColumns([...], { selectionWidth: 55, indexWidth: 60 })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 `page-dialog-form` <20><>?增删改查弹框
|
||||
|
||||
#### Props
|
||||
|
||||
| Prop | 类型 | 默认<E9BB98><E8AEA4>?| 说明 |
|
||||
|------|------|--------|------|
|
||||
| `visible` | `Boolean` | `false` | 弹框显隐,使<EFBC8C><E4BDBF>?`.sync` 修饰<E4BFAE><E9A5B0>?|
|
||||
| `title` | `String` | `''` | 弹框标题,支<EFBC8C><E694AF>?i18n key |
|
||||
| `width` | `String` | `'35%'` | 弹框宽度 |
|
||||
| `form-cols` | `Array` | `[]` | 表单字段结构(二维数组,见下方) |
|
||||
| `form-data` | `Object` | `{}` | 表单数据对象 |
|
||||
| `rules` | `Object` | `{}` | 校验规则,与 `el-form` rules 一<><E4B880>?|
|
||||
| `label-width` | `String` | `'100px'` | label 宽度 |
|
||||
| `submitting` | `Boolean` | `false` | 提交 loading 状<><E78AB6>?|
|
||||
| `confirm-text` | `String` | `'确定'` | 确定按钮文字 |
|
||||
| `cancel-text` | `String` | `'取消'` | 取消按钮文字 |
|
||||
|
||||
#### 事件
|
||||
|
||||
| 事件<E4BA8B><E4BBB6>?| 说明 |
|
||||
|--------|------|
|
||||
| `@submit` | 表单验证通过后触发,父组件执行提交逻辑 |
|
||||
| `@close` | 弹框关闭后触<E5908E><E8A7A6>?|
|
||||
|
||||
#### 方法(通过 ref 调用<E8B083><E794A8>?
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `reset()` | 重置表单 |
|
||||
| `validate()` | 手动验证,返<EFBC8C><E8BF94>?`Promise<boolean>` |
|
||||
|
||||
#### formCols 数据结构
|
||||
|
||||
**注意:需<EFBC9A><E99C80>?`data()` 中用 `k(prop)` 提前完成 i18n 翻译**,不要传 raw key<65><79>?
|
||||
```js
|
||||
// 例:两个普通输入框 + 一个多行文本框
|
||||
formCols: [
|
||||
[
|
||||
{ type: 'input', prop: 'code',
|
||||
label: k('code'), placeholder: k('enter_code'),
|
||||
clearable: true, style: { width: '90%' } }
|
||||
],
|
||||
[
|
||||
{ type: 'input', prop: 'name',
|
||||
label: k('name'), placeholder: k('enter_name'),
|
||||
clearable: true, style: { width: '90%' } }
|
||||
],
|
||||
[
|
||||
{ type: 'input', prop: 'remark', inputType: 'textarea',
|
||||
autosize: { minRows: 2, maxRows: 6 },
|
||||
label: k('remark'), placeholder: k('remark_required'),
|
||||
clearable: true, style: { width: '90%' } }
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
**字段支持的属性:**
|
||||
|
||||
| 属<><E5B19E>?| 适用类型 | 说明 |
|
||||
|------|---------|------|
|
||||
| `type` | 全部 | `'input'`(文本输入)/ `'select'`(下拉) |
|
||||
| `prop` | 全部 | 绑定 `formData` 中的 key |
|
||||
| `label` | 全部 | 表单项标<E9A1B9><E6A087>?|
|
||||
| `placeholder` | 全部 | 占位提示 |
|
||||
| `inputType` | `input` | `'textarea'` 多行 / 不传为普<E4B8BA><E699AE>?text |
|
||||
| `autosize` | `input` | textarea <20><>?`{ minRows, maxRows }` |
|
||||
| `clearable` | 全部 | 是否可清空,默认 `true` |
|
||||
| `style` | 全部 | 样式对象 |
|
||||
| `options` | `select` | 选项数组 `[{ label, value }]` |
|
||||
| `filterable` | `select` | 是否可搜索,默认 `true` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Composable API 参<><E58F82>?
|
||||
### 4.1 `useTableColumns(rawColumns, options?)`
|
||||
|
||||
**作用**:消除手动分<EFBFBD><EFBFBD>?`idx` 序号,自动识别操作列和插槽列<E6A7BD><E58897>?
|
||||
| 参数 | 类型 | 默认<E9BB98><E8AEA4>?| 说明 |
|
||||
|------|------|--------|------|
|
||||
| `rawColumns` | `Array` | <20><>?| 列定<E58897><E5AE9A>?|
|
||||
| `options.selectionWidth` | `number` | `55` | 复选框列宽,传 `0` 隐藏 |
|
||||
| `options.indexWidth` | `number` | `0` | 序号列宽,传 `0` 隐藏 |
|
||||
|
||||
```js
|
||||
const columns = useTableColumns([
|
||||
{ prop: 'code', label: '编码', width: 120 },
|
||||
{ prop: 'name', label: '名称' },
|
||||
{ prop: '_actions', label: '操作', width: 160, fixed: 'right' } // <20><>?操作<E6938D><E4BD9C>?])
|
||||
```
|
||||
|
||||
**内部约定**:`prop === '_actions'` 自动标记<E6A087><E8AEB0>?`slot: '_actions'`,由 `page-table` 渲染为操作列<E4BD9C><E58897>?
|
||||
---
|
||||
|
||||
### 4.2 `useTableButtons(options, permissionCheck?)`
|
||||
|
||||
**作用**:统一生成工具栏按钮和行内操作按钮,自动注入权限校验结果<EFBFBD><EFBFBD>?
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `options.toolbar` | `Array` | 顶部按钮列表 |
|
||||
| `options.row` | `Array` | 行内按钮列表 |
|
||||
| `permissionCheck` | `Function` | 权限函数,通常<E9809A><E5B8B8>?`this.$permission` |
|
||||
|
||||
**返回<E8BF94><E59B9E>?*:`{ toolbarButtons, rowButtons }`
|
||||
|
||||
**按钮字段<E5AD97><E6AEB5>?*
|
||||
|
||||
| 字段 | toolbar | row | 说明 |
|
||||
|------|:---:|:---:|------|
|
||||
| `key` | <20><>?| <20><>?| 唯一标识 |
|
||||
| `label` | <20><>?| <20><>?| 显示文本 |
|
||||
| `icon` | <20><>?| <20><>?| Element UI 图标 |
|
||||
| `type` | <20><>?| <20><>?| `primary`/`success`/`warning`/`danger` |
|
||||
| `color` | <20><>?| <20><>?| `'danger'` 使文字变<E5AD97><E58F98>?|
|
||||
| `auth` | <20><>?| <20><>?| 权限 key |
|
||||
| `cssStyle` | <20><>?| <20><>?| 自定义样<E4B989><E6A0B7>?|
|
||||
| `onClick` | <20><>?| <20><>?| 点击回调 |
|
||||
| `hasPermission` | <20><>?| <20><>?| **自动注入**,由权限函数计算 |
|
||||
| `needSelection` | <20><>?| <20><>?| 需选中行才能点<E883BD><E782B9>?|
|
||||
|
||||
```js
|
||||
const btns = useTableButtons({
|
||||
toolbar: [
|
||||
{ key: 'add', label: '新增', icon: 'el-icon-plus', type: 'primary',
|
||||
auth: '/xxx/create', onClick: this.openAdd }
|
||||
],
|
||||
row: [
|
||||
{ key: 'edit', label: '编辑', icon: 'el-icon-edit',
|
||||
auth: '/xxx/edit', onClick: this.openEdit },
|
||||
{ key: 'delete', label: '删除', icon: 'el-icon-delete',
|
||||
color: 'danger', auth: '/xxx/delete', onClick: this.handleDelete }
|
||||
]
|
||||
}, this.$permission)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3 `i18nMixin(prefix)`
|
||||
|
||||
**作用**:注<EFBFBD><EFBFBD>?`key(suffix)` <20><>?`ckey(suffix)` 两个方法,消除每个页面手<E99DA2><E6898B>?`T` 常量<E5B8B8><E9878F>?`tkey` 方法<E696B9><E6B395>?
|
||||
| 方法 | 返回<E8BF94><E59B9E>?| 说明 |
|
||||
|------|--------|------|
|
||||
| `key(suffix)` | `prefix.suffix` | 当前页面<E9A1B5><E99DA2>?i18n key |
|
||||
| `ckey(suffix)` | `page.common.suffix` | 公共 i18n key(如"帮助"<22><>?确定"<22><>?取消"<22><>?|
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `prefix` | `String` | 该页面的 i18n key 前缀,如 `'page.production_master_data.factory_model.factory_area'` |
|
||||
|
||||
```js
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
|
||||
export default {
|
||||
mixins: [i18nMixin('page.production_master_data.factory_model.factory_area')],
|
||||
// 此后模板中用 $t(key('code')) 访问当前页面的翻<E79A84><E7BFBB>? // <20><>?$t(ckey('help')) 访问公共翻译 'page.common.help'
|
||||
// JS 中用 this.$t(this.key('code')) / this.$t(this.ckey('help'))
|
||||
}
|
||||
```
|
||||
|
||||
**`data()` 中翻译文本的技<E79A84><E68A80>?*<2A><>?
|
||||
```js
|
||||
data () {
|
||||
const t = this.$t.bind(this) // 绑定 i18n 翻译函数
|
||||
const k = (s) => t(this.key(s)) // 当前页面翻译:key + translate
|
||||
const ck = (s) => t(this.ckey(s)) // 公共翻译:ckey + translate
|
||||
|
||||
return {
|
||||
formCols: [
|
||||
[{ label: k('code'), placeholder: k('enter_code') }] // 页面<E9A1B5><E99DA2>?key
|
||||
],
|
||||
helpText: ck('help') // 公共 key
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. i18n 国际化方<E58C96><E696B9>?
|
||||
### 5.1 语言包文<E58C85><E69687>?
|
||||
| 语言 | 文件路径 |
|
||||
|------|---------|
|
||||
| 简体中<E4BD93><E4B8AD>?| `src/locales/zh-chs.json` |
|
||||
| 繁体中文 | `src/locales/zh-cht.json` |
|
||||
| 英文 | `src/locales/en.json` |
|
||||
| 日文 | `src/locales/ja.json` |
|
||||
|
||||
### 5.2 语言包结<E58C85><E7BB93>?
|
||||
<EFBFBD><EFBFBD>?`一级模<E7BAA7><E6A8A1>?<3F><>?二级模块 <20><>?三级模块` 三层嵌套<E5B58C><E5A597>?
|
||||
```json
|
||||
{
|
||||
"page": {
|
||||
"production_master_data": {
|
||||
"factory_model": {
|
||||
"factory_area": {
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"code": "所区编<E58CBA><E7BC96>?,
|
||||
"name": "所区名<EFBFBD><EFBFBD>?,
|
||||
"add": "<22><>?<3F><>?,
|
||||
"edit": "<EFBFBD><EFBFBD>?<3F><>?,
|
||||
"delete": "<22><>?<3F><>?,
|
||||
"enter_code": "请输入所区编<EFBFBD><EFBFBD>?,
|
||||
"enter_name": "请输入所区名<E58CBA><E5908D>?,
|
||||
"add_title": "新增所<EFBFBD><EFBFBD>?,
|
||||
"edit_title": "编辑所<E8BE91><E68980>?,
|
||||
"confirm": "确定",
|
||||
"cancel": "取消",
|
||||
"operation_success": "操作成功"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 组件自动翻译
|
||||
|
||||
`page-table` <20><>?`page-dialog-form` 内置 `$t()`<EFBFBD><EFBFBD>?
|
||||
- **<2A><>?label** <20><>?自动翻译
|
||||
- **按钮 label** <20><>?自动翻译
|
||||
- **表单 label / placeholder** <20><>?自动翻译
|
||||
- **弹框 title / confirm / cancel** <20><>?自动翻译
|
||||
|
||||
因此页面只需传入 i18n key 字符串,组件渲染时自动替换为当前语言的文本<E69687><E69CAC>?
|
||||
### 5.4 页面中手动翻<E58AA8><E7BFBB>?
|
||||
搜索区、校验消息、`$confirm` <20><>?UI 文字需手动<E6898B><E58AA8>?`$t()`<EFBFBD><EFBFBD>?
|
||||
```vue
|
||||
<!-- 模板<EFBFBD><EFBFBD>?<EFBFBD><EFBFBD>?key() 返回 i18n key -->
|
||||
<el-form-item :label="$t(key('code'))">
|
||||
<el-input :placeholder="$t(key('enter_code'))" />
|
||||
</el-form-item>
|
||||
<el-button>{{ $t(key('search')) }}</el-button>
|
||||
```
|
||||
|
||||
```js
|
||||
// JS <20><>?this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.$confirm(this.$t(this.key('confirm_delete')), this.$t(this.key('tip')), { ... })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 常用场景速查
|
||||
|
||||
### 场景 1:自定义列渲染(状<EFBC88><E78AB6>?tag<61><67>?
|
||||
列定义加<EFBFBD><EFBFBD>?`slot: true`<EFBFBD><EFBFBD>?
|
||||
```js
|
||||
useTableColumns([
|
||||
{ prop: 'status', label: '状<EFBFBD><EFBFBD>?, slot: true, width: 100 }
|
||||
])
|
||||
```
|
||||
|
||||
模板中用 `#col-status` 插槽<E68F92><E6A7BD>?
|
||||
```vue
|
||||
<page-table :columns="columns" :data="tableData">
|
||||
<template #col-status="{ row }">
|
||||
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
|
||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</page-table>
|
||||
```
|
||||
|
||||
### 场景 2:工具栏追加自定义按<E4B989><E68C89>?
|
||||
```vue
|
||||
<page-table :columns="columns" :toolbar-buttons="toolbarButtons">
|
||||
<template #toolbar-extra>
|
||||
<el-button size="mini" icon="el-icon-printer" @click="print">打印</el-button>
|
||||
</template>
|
||||
</page-table>
|
||||
```
|
||||
|
||||
### 场景 3:复选框<E98089><E6A186>?+ 序号<E5BA8F><E58FB7>?
|
||||
```js
|
||||
useTableColumns(
|
||||
[
|
||||
{ prop: 'code', label: '编码' },
|
||||
{ prop: 'name', label: '名称' }
|
||||
],
|
||||
{ selectionWidth: 55, indexWidth: 60 }
|
||||
)
|
||||
```
|
||||
|
||||
### 场景 4:带下拉选择的表<E79A84><E8A1A8>?
|
||||
```js
|
||||
formCols: [
|
||||
[
|
||||
{ type: 'select', prop: 'area_id',
|
||||
label: k('area'), placeholder: k('select_area'),
|
||||
options: [{ label: 'A厂区', value: 1 }, { label: 'B厂区', value: 2 }],
|
||||
clearable: true, style: { width: '90%' } }
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
### 场景 5:批量删除(工具<E5B7A5><E585B7>?+ 需要选中行)
|
||||
|
||||
```js
|
||||
useTableButtons({
|
||||
toolbar: [
|
||||
{ key: 'batchDelete', label: '批量删除', icon: 'el-icon-delete',
|
||||
type: 'danger', auth: '/xxx/batch-delete',
|
||||
needSelection: true, // <20><>?需要选中行才能点<E883BD><E782B9>? onClick: this.batchDelete }
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### 场景 6:表格自适应高度
|
||||
|
||||
只需<EFBFBD><EFBFBD>?`auto-height` 属性:
|
||||
|
||||
```vue
|
||||
<page-table ... auto-height />
|
||||
```
|
||||
|
||||
表格高度会自动填<EFBFBD><EFBFBD>?`d2-container` <20><>?body 区域剩余空间,窗<EFBC8C><E7AA97>?resize / 侧栏折叠时自动重算<E9878D><E7AE97>?
|
||||
### 场景 7:帮助按<E58AA9><E68C89>?
|
||||
<EFBFBD><EFBFBD>?`help-url` 传值即可在工具栏最右侧显示问号帮助按钮,点击在新窗口打开帮助文档<E69687><E6A1A3>?
|
||||
```vue
|
||||
<page-table ... help-url="/help/factory-area" :help-text="$t(ckey('help'))" />
|
||||
```
|
||||
|
||||
按钮文字通过 `help-text` prop 支持 i18n。推荐使用公<E794A8><E585AC>?key `page.common.help`(模板中<EFBFBD><EFBFBD>?`$t(ckey('help'))`),所有页面共享,无需每个模块重复定义<EFBFBD><EFBFBD>?
|
||||
不传 `help-url` 或传空字符串则不显示帮助按钮<E68C89><E992AE>?
|
||||
---
|
||||
|
||||
## 7. 路由配置
|
||||
|
||||
```js
|
||||
// src/router/modules/production-master-data.js
|
||||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
|
||||
const meta = { auth: true }
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
|
||||
export default {
|
||||
path: '/production_master_data',
|
||||
component: layoutHeaderAside,
|
||||
children: (pre => [
|
||||
{
|
||||
path: 'factory_model/factory_area',
|
||||
name: `${pre}factory_model-factory_area`,
|
||||
meta: { ...meta, cache: true, title: '工厂区域' },
|
||||
component: _import('production-master-data/factory-model/factory-area')
|
||||
}
|
||||
])('production_master_data-')
|
||||
}
|
||||
```
|
||||
|
||||
之后<EFBFBD><EFBFBD>?`src/router/routes.js` 中引入该模块并加<E5B9B6><E58AA0>?`frameIn` 数组<E695B0><E7BB84>?
|
||||
```js
|
||||
import productionConfiguration from './modules/production-master-data'
|
||||
|
||||
const frameIn = [
|
||||
// ... 其他路由 ...
|
||||
productionConfiguration
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. API 文件写法
|
||||
|
||||
```js
|
||||
// src/api/production-master-data/factory-area.js
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'production_master_data/factory_model/factory_area/'
|
||||
|
||||
function apiParams (method, data = {}) {
|
||||
return {
|
||||
method: `production_master_data_factory_model_factory_area_${method}`,
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
}
|
||||
|
||||
export function getFactoryAreaList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
method: 'get',
|
||||
params: apiParams('list', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function createFactoryArea (data) {
|
||||
return request({
|
||||
url: BASE + 'create',
|
||||
method: 'post',
|
||||
data: apiParams('create', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function editFactoryArea (data) {
|
||||
return request({
|
||||
url: BASE + 'edit',
|
||||
method: 'put',
|
||||
data: apiParams('edit', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteFactoryArea (data) {
|
||||
return request({
|
||||
url: BASE + 'delete',
|
||||
method: 'delete',
|
||||
data: apiParams('delete', data)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 旧代码迁移对<E7A7BB><E5AFB9>?
|
||||
| 旧写<E697A7><E58699>?| 新写<E696B0><E58699>?|
|
||||
|--------|--------|
|
||||
| 手动构建 `columns: [{ idx: 0, attrs: { prop, label } }]` | `useTableColumns([{ prop, label }])` |
|
||||
| `buttonList: [...]` + `tableButtonList: [...]` 分两<E58886><E4B8A4>?| `useTableButtons({ toolbar: [...], row: [...] })` |
|
||||
| `<sct-base-table>` + `<sct-base-dialog>` + `<SctBaseForm>` 三层 | `<page-table>` + `<page-dialog-form>` 两层 |
|
||||
| `<sct-form-search>` 组件 | 原生 `<el-form :inline>` |
|
||||
| `<sct-back-to-top>` 组件 | 需要时自行添加 |
|
||||
| 分页需要额<E8A681><E9A29D>?`<page-footer>` | `page-table` 内置分页 |
|
||||
| 每个页面定义 `T` 常量 + `tkey()` 方法 | `mixins: [i18nMixin(prefix)]`,使<EFBFBD><EFBFBD>?`this.key('xxx')` |
|
||||
| 表单字段<E5AD97><E6AEB5>?i18n raw key,子组件翻译 | `data()` 中用 `k(prop)` 提前翻译 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题排查
|
||||
|
||||
### Q1:弹框打开后不显示内容<E58685><E5AEB9>?
|
||||
确认 `formCols` 中的 `label` <20><>?`placeholder` 是否<E698AF><E590A6>?`data()` 阶段已翻译。子组件 `page-dialog-form` 会调<E4BC9A><E8B083>?`$t()` 处理 label,但建议<E5BBBA><E8AEAE>?`data()` 阶段就用 `k()` 提前翻译好,避免 webpack HMR 缓存问题<E997AE><E9A298>?
|
||||
### Q2:表格高度自适应不生效?
|
||||
|
||||
确认 `d2-container` <20><>?body 区域高度被正确约束(flex 填充)。如<E38082><E5A682>?`d2-container` 本身<E69CAC><E8BAAB>?`overflow: auto` 或其他高度约束问题,`page-table` <20><>?`height: 100%` 会失效。给 `d2-container` <20><>?body 部分<E983A8><E58886>?`overflow: hidden` 通常可解决<E8A7A3><E586B3>?
|
||||
### Q3:权限校验有按钮不显示?
|
||||
|
||||
`useTableButtons` 的第二个参数必须<E5BF85><E9A1BB>?`this.$permission`。如果项目没有注册这个全局方法,可以在 `useTableButtons` 中传入一个返<E4B8AA><E8BF94>?`true` 的占位函数:`() => true`<EFBFBD><EFBFBD>?
|
||||
### Q4:新增一条后页码不跳回第一页?
|
||||
|
||||
<EFBFBD><EFBFBD>?`onDialogSubmit` 成功后调<E5908E><E8B083>?`this.fetchData()` 前,如果删除了当前页最后一行导<E8A18C><E5AFBC>?`total - 1` 超出范围,需手动修正页码<E9A1B5><E7A081>?
|
||||
```js
|
||||
this.pagination.current = Math.min(
|
||||
this.pagination.current,
|
||||
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
|
||||
)
|
||||
```
|
||||
|
||||
### Q5:表单验证不提示错误文字<E69687><E5AD97>?
|
||||
确认 `page-dialog-form` <20><>?`<style>` 块存在(默认 22px `margin-bottom` + `position: absolute` 错误文字)。如果表单项之间被其他样式覆盖,检查是否有全局 CSS 重置<E9878D><E7BDAE>?`.el-form-item` <20><>?margin<69><6E>?
|
||||
### Q6:如何新增一<E5A29E><E4B880>?CRUD 页面最快?
|
||||
|
||||
1. 复制 `src/views/production-master-data/factory-model/factory-area/index.vue`
|
||||
2. 替换 API 引用(`import { getList, create, edit, ... } from '@/api/xxx'`<EFBFBD><EFBFBD>?3. 修改 `i18nMixin` 参数、`columns`、`formCols`、`rules`
|
||||
4. <20><>?`zh-chs.json` <20><>?`en.json` 中添加语言<E8AFAD><E8A880>?5. 添加路由配置
|
||||
|
||||
平均 10 分钟即可完成一个标<E4B8AA><E6A087>?CRUD 页面<E9A1B5><E99DA2>?
|
||||
@@ -71,7 +71,7 @@
|
||||
"less": "^3.13.1",
|
||||
"less-loader": "^7.3.0",
|
||||
"sass": "^1.54.5",
|
||||
"sass-loader": "^10.3.1",
|
||||
"sass-loader": "~10.5.2",
|
||||
"svg-sprite-loader": "^4.3.0",
|
||||
"text-loader": "^0.0.1",
|
||||
"vue-cli-plugin-i18n": "^1.0.1",
|
||||
|
||||
15889
pnpm-lock.yaml
generated
Normal file
15889
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/image/logo/sc_logo.png
Normal file
BIN
public/image/logo/sc_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -125,13 +125,15 @@ function createRequest (service) {
|
||||
const token = util.cookies.get('token')
|
||||
const configDefault = {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
'Content-Type': get(config, 'headers.Content-Type', 'application/json')
|
||||
},
|
||||
timeout: 5000,
|
||||
baseURL: process.env.VUE_APP_API,
|
||||
data: {}
|
||||
}
|
||||
if (token) {
|
||||
configDefault.headers.Authorization = 'Bearer ' + token
|
||||
}
|
||||
const option = merge(configDefault, config)
|
||||
// 处理 get 请求的参数
|
||||
// 请根据实际需要修改
|
||||
@@ -141,7 +143,7 @@ function createRequest (service) {
|
||||
}
|
||||
// 当需要以 form 形式发送时 处理发送的数据
|
||||
// 请根据实际需要修改
|
||||
if (!isEmpty(option.data) && option.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
|
||||
if (!isEmpty(option.data) && option.headers['Content-Type'] === 'application/x-www-form-urlen·coded') {
|
||||
option.data = stringify(option.data)
|
||||
}
|
||||
return service(option)
|
||||
|
||||
33
src/api/auth.js
Normal file
33
src/api/auth.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
/**
|
||||
* 账号登录
|
||||
* @param {Object} data
|
||||
* @param {string} data.username
|
||||
* @param {string} data.password
|
||||
*/
|
||||
export function loginAdminUser (data) {
|
||||
return request({
|
||||
url: 'login',
|
||||
method: 'post',
|
||||
data: {
|
||||
method: 'login',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销当前已登录的账号
|
||||
*/
|
||||
export function logoutAdminUser () {
|
||||
return request({
|
||||
url: 'logout',
|
||||
method: 'post',
|
||||
data: {
|
||||
method: 'logout',
|
||||
platform: 'background'
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { find, map, random } from 'lodash'
|
||||
import faker from 'faker/locale/zh_CN'
|
||||
import { requestForMock, mock } from '@/api/_service.js'
|
||||
import * as tools from '@/api/_tools.js'
|
||||
|
||||
const db = [
|
||||
{ id: '1', name: '用户 1', address: '上海市普陀区金沙江路 1518 弄' },
|
||||
{ id: '2', name: '用户 2', address: '上海市普陀区金沙江路 1517 弄' },
|
||||
{ id: '3', name: '用户 3', address: '上海市普陀区金沙江路 1519 弄' },
|
||||
{ id: '4', name: '用户 4', address: '上海市普陀区金沙江路 1516 弄' }
|
||||
]
|
||||
|
||||
/**
|
||||
* @description 列表
|
||||
*/
|
||||
export function DEMO_MOCK_LIST () {
|
||||
// 模拟数据
|
||||
mock
|
||||
.onAny('/demo/business/issues/142/fetch')
|
||||
.reply(...tools.responseSuccess({ list: db }))
|
||||
// 接口请求
|
||||
return requestForMock({
|
||||
url: '/demo/business/issues/142/fetch',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 详情
|
||||
* @param {String} id 项目 ID
|
||||
*/
|
||||
export function DEMO_MOCK_DETAIL (id) {
|
||||
// 模拟数据
|
||||
mock
|
||||
.onAny('/demo/business/issues/142/detail')
|
||||
.reply(config => tools.responseSuccess(find(db, { id: config.params.id })))
|
||||
// 接口请求
|
||||
return requestForMock({
|
||||
url: '/demo/business/issues/142/detail',
|
||||
method: 'get',
|
||||
params: {
|
||||
id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 列表
|
||||
*/
|
||||
export function DEMO_MOCK_LIST2 (params = {}) {
|
||||
// 模拟数据
|
||||
mock
|
||||
.onAny('/demo/business/table/1/fetch')
|
||||
.reply(config => tools.responseSuccess({
|
||||
page: {
|
||||
total: 1000
|
||||
},
|
||||
list: map(Array(config.params.pageSize), () => ({
|
||||
key: faker.random.uuid(),
|
||||
value: [10, 100, 200, 500][random(0, 3)],
|
||||
type: faker.random.boolean(),
|
||||
admin: faker.name.firstName() + faker.name.lastName(),
|
||||
adminNote: faker.random.words(),
|
||||
dateTimeCreat: faker.date.past(),
|
||||
used: faker.random.boolean(),
|
||||
dateTimeUse: faker.date.past()
|
||||
}))
|
||||
}))
|
||||
// 接口请求
|
||||
return requestForMock({
|
||||
url: '/demo/business/table/1/fetch',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 错误日志示例 请求一个不存在的地址
|
||||
*/
|
||||
export function DEMO_LOG_AJAX () {
|
||||
// 接口请求
|
||||
return requestForMock({
|
||||
url: '/invalid-url',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
13
src/api/menu.js
Normal file
13
src/api/menu.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const urls = 'system_settings/menu_configuration/menu/'
|
||||
|
||||
export function getMenuAll (data) {
|
||||
return request({
|
||||
url: urls + 'all',
|
||||
method: 'get',
|
||||
params: {
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
43
src/api/production-master-data/factory-area.js
Normal file
43
src/api/production-master-data/factory-area.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'production_configuration/factory_model/factory_area/'
|
||||
|
||||
function apiParams (method, data = {}) {
|
||||
return {
|
||||
method: `production_master_data_factory_model_factory_area_${method}`,
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
}
|
||||
|
||||
export function getFactoryAreaList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
method: 'get',
|
||||
params: apiParams('list', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function createFactoryArea (data) {
|
||||
return request({
|
||||
url: BASE + 'create',
|
||||
method: 'post',
|
||||
data: apiParams('create', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function editFactoryArea (data) {
|
||||
return request({
|
||||
url: BASE + 'edit',
|
||||
method: 'put',
|
||||
data: apiParams('edit', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteFactoryArea (data) {
|
||||
return request({
|
||||
url: BASE + 'delete',
|
||||
method: 'delete',
|
||||
data: apiParams('delete', data)
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
@use 'sass:list';
|
||||
@import 'public';
|
||||
|
||||
// 补丁 base
|
||||
@@ -39,17 +40,17 @@
|
||||
$sizes: (0, 5, 10, 15, 20);
|
||||
|
||||
@for $index from 1 to 6 {
|
||||
.#{$prefix}-m-#{nth($sizes, $index)} { margin: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-mt-#{nth($sizes, $index)} { margin-top: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-mr-#{nth($sizes, $index)} { margin-right: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-mb-#{nth($sizes, $index)} { margin-bottom: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-ml-#{nth($sizes, $index)} { margin-left: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-m-#{list.nth($sizes, $index)} { margin: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-mt-#{list.nth($sizes, $index)} { margin-top: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-mr-#{list.nth($sizes, $index)} { margin-right: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-mb-#{list.nth($sizes, $index)} { margin-bottom: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-ml-#{list.nth($sizes, $index)} { margin-left: #{list.nth($sizes, $index)}px !important; }
|
||||
|
||||
.#{$prefix}-p-#{nth($sizes, $index)} { padding: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pt-#{nth($sizes, $index)} { padding-top: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pr-#{nth($sizes, $index)} { padding-right: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pb-#{nth($sizes, $index)} { padding-bottom: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pl-#{nth($sizes, $index)} { padding-left: #{nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-p-#{list.nth($sizes, $index)} { padding: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pt-#{list.nth($sizes, $index)} { padding-top: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pr-#{list.nth($sizes, $index)} { padding-right: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pb-#{list.nth($sizes, $index)} { padding-bottom: #{list.nth($sizes, $index)}px !important; }
|
||||
.#{$prefix}-pl-#{list.nth($sizes, $index)} { padding-left: #{list.nth($sizes, $index)}px !important; }
|
||||
}
|
||||
|
||||
// 快速使用
|
||||
|
||||
323
src/components/page-dialog-form/index.vue
Normal file
323
src/components/page-dialog-form/index.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<!--
|
||||
page-dialog-form — 增删改查弹框组件
|
||||
===================================
|
||||
这是一个「带表单验证的弹框」组件,配合 page-table 使用。
|
||||
负责:弹框显隐控制 + 表单渲染 + 表单验证 + 确定/取消按钮。
|
||||
|
||||
不支持复杂表单联动——如有需要,通过默认插槽自定义内容。
|
||||
|
||||
依赖:element-ui 的 <el-dialog> <el-form> <el-input> <el-select>
|
||||
i18n:由调用方负责翻译,组件直接渲染传入的文本
|
||||
|
||||
@author 前端团队
|
||||
@since 2026-05
|
||||
-->
|
||||
|
||||
<!--
|
||||
el-dialog:
|
||||
visible.sync → 通过 .sync 修饰符双向绑定父组件的 visible prop
|
||||
destroy-on-close → 每次关闭销毁 DOM 重建,避免表单残留上次数据
|
||||
close-on-click-modal → 禁止点击遮罩关闭,防止误操作丢失填写数据
|
||||
-->
|
||||
<el-dialog
|
||||
:visible.sync="visibleProxy"
|
||||
:title="title"
|
||||
:width="width"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
@close="onClose"
|
||||
>
|
||||
<!-- ==================== 表单区 ==================== -->
|
||||
<!--
|
||||
el-form:
|
||||
rules 由父组件传入,字段名与 formData 的 key 一一对应
|
||||
label-width 默认 100px,可自定义
|
||||
-->
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
:label-width="labelWidth || '100px'"
|
||||
>
|
||||
<!--
|
||||
遍历 formCols 中所有行 → 每行中的每个字段 → 渲染对应的表单项
|
||||
当前支持两种字段类型:
|
||||
- type='input' → <el-input>(支持 textarea、密码等)
|
||||
- type='select' → <el-select> + <el-option>
|
||||
|
||||
由调用方负责翻译,传已翻译的文本即可
|
||||
-->
|
||||
<el-form-item
|
||||
v-for="col in flatFormCols"
|
||||
:key="col.prop"
|
||||
:label="col.label"
|
||||
:prop="col.prop"
|
||||
>
|
||||
<!-- ===== 输入框类型 ===== -->
|
||||
<!--
|
||||
input:
|
||||
inputType='textarea' → 多行文本框
|
||||
不传 inputType → 普通文本输入框
|
||||
clearable → 默认 true,传 false 可关闭
|
||||
-->
|
||||
<el-input
|
||||
v-if="col.type === 'input'"
|
||||
v-model="formData[col.prop]"
|
||||
:placeholder="col.placeholder"
|
||||
:type="col.inputType || 'text'"
|
||||
:autosize="col.autosize"
|
||||
:clearable="col.clearable !== false"
|
||||
:style="col.style"
|
||||
/>
|
||||
<!-- ===== 下拉选择类型 ===== -->
|
||||
<!--
|
||||
select:
|
||||
options → [{ label: '苹果', value: 1 }]
|
||||
filterable → 默认 true,支持搜索过滤
|
||||
-->
|
||||
<el-select
|
||||
v-else-if="col.type === 'select'"
|
||||
v-model="formData[col.prop]"
|
||||
:placeholder="col.placeholder"
|
||||
:clearable="col.clearable !== false"
|
||||
:style="col.style"
|
||||
:filterable="col.filterable !== false"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in col.options"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- ===== 自定义表单内容插槽 ===== -->
|
||||
<!--
|
||||
如果需要超出 input/select 的复杂表单控件
|
||||
父组件可以用此插槽添加任意内容
|
||||
-->
|
||||
<slot />
|
||||
</el-form>
|
||||
|
||||
<!-- ==================== 底部按钮 ==================== -->
|
||||
<!--
|
||||
确定:type='primary' + submitting loading 状态
|
||||
取消:调用 onClose → 重置表单 + 关闭弹框
|
||||
由调用方负责翻译,传已翻译的文本即可
|
||||
-->
|
||||
<template #footer>
|
||||
<el-button @click="onClose">{{ cancelText }}</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="onSubmit">{{ confirmText }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* PageDialogForm — 增删改查弹框组件
|
||||
*
|
||||
* 【核心职责】
|
||||
* 1. 管理弹框显隐
|
||||
* 2. 根据 formCols 声明式渲染表单
|
||||
* 3. 表单验证 + 验证失败提示
|
||||
* 4. 提交/取消事件通知父组件
|
||||
*
|
||||
* 【典型用法】
|
||||
* <page-dialog-form
|
||||
* ref="dialogForm"
|
||||
* :visible.sync="dialogVisible"
|
||||
* :title="dialogTitle"
|
||||
* width="35%"
|
||||
* :form-cols="formCols"
|
||||
* :form-data="formData"
|
||||
* :rules="rules"
|
||||
* :submitting="submitting"
|
||||
* @submit="onDialogSubmit"
|
||||
* @close="onDialogClose"
|
||||
* />
|
||||
*
|
||||
* 【父组件调用的方法(通过 ref)】
|
||||
* this.$refs.dialogForm.reset() — 重置表单
|
||||
* this.$refs.dialogForm.validate() — 手动验证,返回 Promise<boolean>
|
||||
*
|
||||
* 【formCols 数据结构说明】
|
||||
* 二维数组:外层是行,里层是该行的字段。大多数场景每行一个字段即可。
|
||||
*
|
||||
* 例:
|
||||
* formCols: [
|
||||
* [ { type: 'input', prop: 'code', label: T+'.code', placeholder: T+'.enter_code' } ],
|
||||
* [ { type: 'input', prop: 'remark', label: T+'.remark', inputType: 'textarea', autosize: { minRows: 2 } } ],
|
||||
* [ { type: 'select', prop: 'area_id', label: T+'.area', options: [{ label: 'A区', value: 1 }] } ]
|
||||
* ]
|
||||
*
|
||||
* 【表单字段支持的属性】
|
||||
* 基础:type / prop / label / placeholder
|
||||
* input:inputType('textarea' | 'text')/ autosize / clearable
|
||||
* select:options / filterable
|
||||
* 通用:style(如 { width: '90%' })
|
||||
*
|
||||
* 【表单验证(rules)】
|
||||
* rules 与 el-form 的 rules 完全一致:
|
||||
* rules: {
|
||||
* code: [{ required: true, message: '请输入编码', trigger: 'blur' }]
|
||||
* }
|
||||
* 验证失败时,组件会用 $message.warning 显示第一条错误信息
|
||||
* message 字段支持 i18n key,父组件用 this.$t() 传值即可
|
||||
*/
|
||||
export default {
|
||||
name: 'PageDialogForm',
|
||||
props: {
|
||||
/**
|
||||
* 弹框显隐状态,父组件用 .sync 绑定
|
||||
* 例::visible.sync="dialogVisible"
|
||||
*/
|
||||
visible: { type: Boolean, default: false },
|
||||
|
||||
/**
|
||||
* 弹框标题,由调用方负责翻译
|
||||
*/
|
||||
title: { type: String, default: '' },
|
||||
|
||||
/**
|
||||
* 弹框宽度,可以是百分比或像素
|
||||
* 例:'35%' / '600px'
|
||||
*/
|
||||
width: { type: String, default: '35%' },
|
||||
|
||||
/**
|
||||
* 表单字段结构,二维数组
|
||||
* 外层数组每一个元素代表表单的一行
|
||||
* 内层数组每个元素代表该行中的一个字段
|
||||
*
|
||||
* 每个字段对象支持的属性见组件顶部的注释
|
||||
*/
|
||||
formCols: { type: Array, default: () => [] },
|
||||
|
||||
/**
|
||||
* 表单数据对象,key 与 formCols 中的 prop 一一对应
|
||||
* 编辑时父组件将 row 数据赋值给 formData
|
||||
*/
|
||||
formData: { type: Object, default: () => ({}) },
|
||||
|
||||
/**
|
||||
* 表单验证规则,与 el-form 的 rules 完全一致
|
||||
* 例:{ code: [{ required: true, message: '请输入编码', trigger: 'blur' }] }
|
||||
*/
|
||||
rules: { type: Object, default: () => ({}) },
|
||||
|
||||
/**
|
||||
* 表单 label 宽度,默认 '100px'
|
||||
*/
|
||||
labelWidth: { type: String, default: '100px' },
|
||||
|
||||
/**
|
||||
* 提交 loading 状态,提交期间显示转圈防止重复点击
|
||||
*/
|
||||
submitting: { type: Boolean, default: false },
|
||||
|
||||
/**
|
||||
* 确定按钮文字,默认 '确定',由调用方负责 i18n 翻译
|
||||
*/
|
||||
confirmText: { type: String, default: '确定' },
|
||||
|
||||
/**
|
||||
* 取消按钮文字,默认 '取消',由调用方负责 i18n 翻译
|
||||
*/
|
||||
cancelText: { type: String, default: '取消' }
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* visible 代理:用于 .sync 双向绑定
|
||||
* get → 返回父组件传入的 visible
|
||||
* set → emit update:visible 通知父组件
|
||||
*/
|
||||
visibleProxy: {
|
||||
get () { return this.visible },
|
||||
set (val) { this.$emit('update:visible', val) }
|
||||
},
|
||||
|
||||
/**
|
||||
* 将二维 formCols 打平为一维数组,方便 v-for 遍历
|
||||
* 二维数组 → 一维数组:[ [{a}], [{b},{c}] ] → [a, b, c]
|
||||
*/
|
||||
flatFormCols () {
|
||||
return this.formCols.flat()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 点击确定按钮
|
||||
* 1. 调用 el-form 的 validate 方法验证所有字段
|
||||
* 2. 验证通过 → emit('submit') 通知父组件执行提交逻辑
|
||||
* 3. 验证失败 → 用 $message.warning 显示第一条错误信息
|
||||
*
|
||||
* 注意:rules 中的 message 由父组件传入时已用 $t() 翻译
|
||||
* 这里只负责显示,翻译在父组件或 i18n 语言包中完成
|
||||
*/
|
||||
onSubmit () {
|
||||
this.$refs.form.validate((valid, invalidFields) => {
|
||||
if (!valid) {
|
||||
// 验证失败:取第一条错误信息提示用户
|
||||
const firstKey = Object.keys(invalidFields)[0]
|
||||
if (firstKey && invalidFields[firstKey].length) {
|
||||
const msg = invalidFields[firstKey][0].message
|
||||
this.$message.warning(msg)
|
||||
}
|
||||
return
|
||||
}
|
||||
this.$emit('submit')
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭弹框
|
||||
* 1. 重置表单清除验证状态和输入内容
|
||||
* 2. emit update:visible 关闭弹框(.sync 机制)
|
||||
* 3. emit close 通知父组件执行清理逻辑
|
||||
*/
|
||||
onClose () {
|
||||
this.$refs.form && this.$refs.form.resetFields()
|
||||
this.$emit('update:visible', false)
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
/**
|
||||
* 手动验证表单,返回 Promise<boolean>
|
||||
* 主要用于父组件需要自行控制验证时机的场景
|
||||
*
|
||||
* 用法:const ok = await this.$refs.dialogForm.validate()
|
||||
*/
|
||||
validate () {
|
||||
return new Promise(resolve => {
|
||||
this.$refs.form.validate(valid => resolve(valid))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置表单(清除验证状态和输入值)
|
||||
* 通常在打开新增弹框时调用
|
||||
*/
|
||||
reset () {
|
||||
this.$refs.form && this.$refs.form.resetFields()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 表单项间距固定 22px,不受 Element UI 状态覆盖 */
|
||||
/deep/ .el-form-item {
|
||||
margin-bottom: 22px !important;
|
||||
}
|
||||
/* 错误文字下沉到 margin 间隙中,不挤占表单项自身高度 */
|
||||
/deep/ .el-form-item__error {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
bottom: -18px;
|
||||
}
|
||||
/* textarea 对齐顶部 */
|
||||
/deep/ .el-textarea {
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
517
src/components/page-table/index.vue
Normal file
517
src/components/page-table/index.vue
Normal file
@@ -0,0 +1,517 @@
|
||||
<template>
|
||||
<!--
|
||||
page-table — CRUD 表格便捷组合体
|
||||
================================
|
||||
这是一个把「顶部按钮栏 + 数据表格 + 底部分页」打包在一起的高层组件。
|
||||
适合 80% 的增删改查页面,你只需要传列定义、按钮定义、数据和分页参数即可。
|
||||
|
||||
依赖:element-ui 的 <el-table> <el-button> <el-pagination>
|
||||
i18n:由调用方负责翻译,组件直接渲染传入的文本
|
||||
|
||||
@author 前端团队
|
||||
@since 2026-05
|
||||
-->
|
||||
<div
|
||||
ref="root"
|
||||
class="page-table"
|
||||
:class="{ 'page-table--auto': autoHeight || height === 'auto' }"
|
||||
v-loading="loading"
|
||||
>
|
||||
<!-- ==================== 顶部:工具栏按钮区 ==================== -->
|
||||
<!--
|
||||
toolbarButtons 由 useTableButtons({ toolbar: [...] }) 生成
|
||||
内部自动过滤 hasPermission === false 的按钮(无权限不显示)
|
||||
按钮被 disabled 只在前端计算(如 needSelection 需要选中行),实际权限由后端校验
|
||||
-->
|
||||
<div ref="toolbar" class="page-table__toolbar" v-if="toolbarButtons.length || helpUrl">
|
||||
<div class="page-table__toolbar-left">
|
||||
<el-button
|
||||
v-for="btn in visibleToolbarButtons"
|
||||
:key="btn.key"
|
||||
:type="btn.type"
|
||||
:size="btn.size"
|
||||
:icon="btn.icon"
|
||||
:style="btn.cssStyle"
|
||||
:disabled="btn.needSelection && !selectedCount"
|
||||
@click="btn.onClick"
|
||||
>
|
||||
{{ btn.label }}
|
||||
</el-button>
|
||||
<!-- 自定义工具栏内容:如打印按钮、列筛选器等 -->
|
||||
<slot name="toolbar-extra" />
|
||||
</div>
|
||||
<!-- 帮助按钮:始终显示在工具栏最右侧,点击新窗口打开帮助文档 -->
|
||||
<el-button
|
||||
v-if="helpUrl"
|
||||
class="page-table__help-btn"
|
||||
size="mini"
|
||||
icon="el-icon-question"
|
||||
@click="openHelp"
|
||||
>
|
||||
{{ helpText }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 中部:数据表格 ==================== -->
|
||||
<!--
|
||||
height 由外部传入或自动计算(autoHeight=true 时)
|
||||
v-bind="tableAttrs" / v-on="tableListeners" 透传 el-table 原生属性和事件
|
||||
-->
|
||||
<div ref="tableWrapper" class="page-table__body">
|
||||
<el-table
|
||||
ref="table"
|
||||
:data="data"
|
||||
:height="tableHeight"
|
||||
:border="border"
|
||||
:row-key="rowKey"
|
||||
:header-cell-style="headerCellStyle"
|
||||
v-bind="tableAttrs"
|
||||
v-on="tableListeners"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<!--
|
||||
遍历 columns,有五种列类型:
|
||||
1. type='selection' → 复选框列
|
||||
2. type='index' → 序号列
|
||||
3. slot='_actions' → 操作列(自动渲染 rowButtons)
|
||||
4. slot/headerSlot → 自定义插槽列
|
||||
5. 其他 → 普通数据列
|
||||
-->
|
||||
<template v-for="col in columns">
|
||||
<!-- 1. 复选框列 -->
|
||||
<el-table-column
|
||||
v-if="col.type === 'selection'"
|
||||
:key="'sel-' + col.idx"
|
||||
type="selection"
|
||||
:width="col.width"
|
||||
/>
|
||||
<!-- 2. 序号列 -->
|
||||
<el-table-column
|
||||
v-else-if="col.type === 'index'"
|
||||
:key="'idx-' + col.idx"
|
||||
type="index"
|
||||
:width="col.width"
|
||||
:label="col.label || '#'"
|
||||
/>
|
||||
<!-- 3. 操作列:自动渲染 rowButtons(编辑/删除等行内按钮) -->
|
||||
<!--
|
||||
约定:columns 中 prop='_actions' 的列会被识别为操作列
|
||||
rowButtons 由 useTableButtons({ row: [...] }) 生成
|
||||
红色按钮通过 color='danger' 控制
|
||||
-->
|
||||
<el-table-column
|
||||
v-else-if="col.slot === '_actions' && rowButtons.length"
|
||||
:key="'act-' + col.idx"
|
||||
v-bind="colAttrs(col)"
|
||||
>
|
||||
<template #default="{ row, $index }">
|
||||
<span
|
||||
v-for="btn in visibleRowButtons"
|
||||
:key="btn.key"
|
||||
:style="btn.cssStyle"
|
||||
:class="{ 'action-btn--danger': btn.color === 'danger' }"
|
||||
class="action-btn"
|
||||
@click="btn.onClick(row, $index)"
|
||||
>
|
||||
<i v-if="btn.icon" :class="btn.icon" />
|
||||
{{ $t(btn.label) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 4. 自定义插槽列:列内容或表头可由父组件自定义 -->
|
||||
<!--
|
||||
使用方式:
|
||||
- col.slot='status' → 父组件用 <template #col-status="{ row }">
|
||||
- col.headerSlot='xyz' → 父组件用 <template #xyz> 自定义表头
|
||||
-->
|
||||
<el-table-column
|
||||
v-else-if="col.slot || col.headerSlot"
|
||||
:key="'slot-' + col.idx"
|
||||
v-bind="colAttrs(col)"
|
||||
>
|
||||
<template #header="{ column }">
|
||||
<slot v-if="col.headerSlot" :name="col.headerSlot" :data="col" :column="column">
|
||||
<span>{{ column.label }}</span>
|
||||
</slot>
|
||||
<span v-else>{{ column.label }}</span>
|
||||
</template>
|
||||
<template #default="{ row, $index }">
|
||||
<slot :name="'col-' + col.slot" :row="row" :index="$index">
|
||||
<span>{{ row[col.prop] }}</span>
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 5. 普通数据列:直接按 prop 取 row 数据 -->
|
||||
<el-table-column
|
||||
v-else
|
||||
:key="'col-' + col.idx"
|
||||
v-bind="colAttrs(col)"
|
||||
/>
|
||||
</template>
|
||||
<!-- 表格无数据时显示的占位内容 -->
|
||||
<template #empty>
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
<!-- 表格最后一行之后追加的内容 -->
|
||||
<template #append>
|
||||
<slot name="append" />
|
||||
</template>
|
||||
<slot />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 底部:分页组件 ==================== -->
|
||||
<!--
|
||||
只传 pagination 对象({ current, size, total })才显示分页
|
||||
page-change 事件回传 { current, size, total },父组件更新 pagination 后重新 fetchData
|
||||
-->
|
||||
<div ref="footer" class="page-table__footer" v-if="pagination">
|
||||
<el-pagination
|
||||
:current-page="pagination.current"
|
||||
:page-size="pagination.size || 10"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 25, 50, 100, 250, 500]"
|
||||
:disabled="loading"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="onSizeChange"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 自定义尾部内容 -->
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* PageTable — CRUD 表格便捷组合体
|
||||
*
|
||||
* 【核心职责】
|
||||
* 1. 渲染顶部工具栏(增删改查等操作按钮,自动权限过滤)
|
||||
* 2. 渲染数据表格(5 种列类型,自动 i18n 翻译)
|
||||
* 3. 渲染底部分页
|
||||
* 4. 表格高度自适应(可选)
|
||||
*
|
||||
* 【典型用法】
|
||||
* <page-table
|
||||
* :columns="columns"
|
||||
* :data="tableData"
|
||||
* :loading="loading"
|
||||
* :toolbar-buttons="toolbarButtons"
|
||||
* :row-buttons="rowButtons"
|
||||
* :pagination="pagination"
|
||||
* auto-height
|
||||
* @page-change="onPageChange"
|
||||
* @selection-change="onSelect"
|
||||
* />
|
||||
*
|
||||
* 【内部事件流】
|
||||
* toolbarButtons[i].onClick() → 父组件方法 → fetchData → data 更新 → 表格刷新
|
||||
* el-pagination @change → emit('page-change') → 父组件更新 pagination → fetchData
|
||||
* el-table @selection-change → emit('selection-change') → 父组件获取选中行
|
||||
*
|
||||
* 【与 useTableColumns / useTableButtons 配合】
|
||||
* 详见文档:docs/sct-base-table-refactor-design.md
|
||||
*/
|
||||
export default {
|
||||
name: 'PageTable',
|
||||
props: {
|
||||
/**
|
||||
* 列定义,由 useTableColumns() 生成
|
||||
*
|
||||
* 普通列:{ prop: 'code', label: '编码', minWidth: 120 }
|
||||
* 操作列:{ prop: '_actions', label: '操作', width: 160, fixed: 'right' }
|
||||
* 自定义列:{ prop: 'status', label: '状态', slot: true }
|
||||
*
|
||||
* 组件内部会按 type/slot/prop 自动分类渲染不同的 <el-table-column>
|
||||
*/
|
||||
columns: { type: Array, default: () => [] },
|
||||
|
||||
/**
|
||||
* 表格行数据,直接传接口返回的数组
|
||||
*/
|
||||
data: { type: Array, default: () => [] },
|
||||
|
||||
/**
|
||||
* 是否显示 loading 遮罩,通常在 fetchData 期间为 true
|
||||
*/
|
||||
loading: { type: Boolean, default: false },
|
||||
|
||||
/**
|
||||
* 表格高度,不传则 el-table 按内容撑开
|
||||
* 传 'auto' 或同时传 auto-height 启用自动计算(窗口高度 - 搜索区 - 工具栏 - 分页)
|
||||
* 传数字/字符串(如 '500px')则固定高度
|
||||
*/
|
||||
height: { type: [String, Number], default: undefined },
|
||||
|
||||
/**
|
||||
* 启用表格高度自适应:根据容器可视区域自动计算高度
|
||||
* 启用后 height prop 被忽略,组件内部通过 ResizeObserver 持续计算
|
||||
*/
|
||||
autoHeight: { type: Boolean, default: false },
|
||||
|
||||
/**
|
||||
* 是否显示表格边框,默认 true
|
||||
*/
|
||||
border: { type: Boolean, default: true },
|
||||
|
||||
/**
|
||||
* 行数据的唯一 key,默认 'id',用于 el-table 的 row-key
|
||||
* 树形表格或需要保留选中状态时必需
|
||||
*/
|
||||
rowKey: { type: String, default: 'id' },
|
||||
|
||||
/**
|
||||
* 顶部工具栏按钮列表,由 useTableButtons({ toolbar: [...] }) 生成
|
||||
*
|
||||
* 每个按钮字段:
|
||||
* key - 唯一标识(必填)
|
||||
* label - 显示文本,支持 i18n key(组件自动 $t() 翻译)
|
||||
* icon - element-ui 图标名('el-icon-plus' 等)
|
||||
* type - 按钮样式类型('primary' | 'success' | 'warning' | 'danger')
|
||||
* auth - 权限 key,传给 useTableButtons 的第二个参数判断
|
||||
* cssStyle - 自定义样式对象
|
||||
* needSelection - true 的话需要表格有选中行才能点击
|
||||
* onClick - 点击回调(由父组件绑定方法)
|
||||
* hasPermission - 自动注入,由 useTableButtons 计算(不要手动设)
|
||||
*/
|
||||
toolbarButtons: { type: Array, default: () => [] },
|
||||
|
||||
/**
|
||||
* 行内操作按钮列表,由 useTableButtons({ row: [...] }) 生成
|
||||
*
|
||||
* 每个按钮字段:
|
||||
* key / label / icon / auth / onClick — 同 toolbarButtons
|
||||
* color - 'danger' 文字变红,用于删除等危险操作
|
||||
* cssStyle - 默认 { marginRight: '10px', cursor: 'pointer' }
|
||||
*/
|
||||
rowButtons: { type: Array, default: () => [] },
|
||||
|
||||
/**
|
||||
* 分页参数,传此 prop 才显示分页组件
|
||||
*
|
||||
* { current: 1, size: 10, total: 0 }
|
||||
*
|
||||
* total 通常在 getList 接口返回 res.count
|
||||
*/
|
||||
pagination: { type: Object, default: null },
|
||||
|
||||
/**
|
||||
* 额外透传给 el-table 的原生属性,如 { stripe: true, size: 'medium' }
|
||||
*/
|
||||
tableAttrs: { type: Object, default: () => ({}) },
|
||||
|
||||
/**
|
||||
* 额外透传给 el-table 的原生事件,如 { 'sort-change': handleSort }
|
||||
*/
|
||||
tableListeners: { type: Object, default: () => ({}) },
|
||||
|
||||
/**
|
||||
* 帮助文档的跳转 URL。传了才显示工具栏右侧的问号按钮,点击新窗口打开
|
||||
* 例:'/docs/factory-area-help.html'
|
||||
*/
|
||||
helpUrl: { type: String, default: '' },
|
||||
|
||||
/**
|
||||
* 帮助按钮的文字,由调用方负责 i18n 翻译
|
||||
* 默认 '帮助'
|
||||
*/
|
||||
helpText: { type: String, default: '帮助' }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tableSelected: [], // 当前选中的行数据
|
||||
computedHeight: null // autoHeight 时动态计算的高度
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 最终使用的表格高度:
|
||||
* - 用户明确传了 height(非 'auto')→ 直接用
|
||||
* - autoHeight=true 或 height='auto' → 用动态计算的 computedHeight
|
||||
* - 否则 undefined(el-table 按内容自适应)
|
||||
*/
|
||||
tableHeight () {
|
||||
if (this.height && this.height !== 'auto') return this.height
|
||||
if (this.autoHeight || this.height === 'auto') return this.computedHeight || undefined
|
||||
return undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 过滤后的工具栏按钮(排除无权限的)
|
||||
*/
|
||||
visibleToolbarButtons () {
|
||||
return this.toolbarButtons.filter(b => b.hasPermission !== false)
|
||||
},
|
||||
|
||||
/**
|
||||
* 过滤后的行内操作按钮(排除无权限的)
|
||||
*/
|
||||
visibleRowButtons () {
|
||||
return this.rowButtons.filter(b => b.hasPermission !== false)
|
||||
},
|
||||
|
||||
/**
|
||||
* 选中的行数
|
||||
*/
|
||||
selectedCount () {
|
||||
return this.tableSelected.length
|
||||
},
|
||||
|
||||
/**
|
||||
* 表头样式:浅灰背景 + 黑色加粗文字
|
||||
*/
|
||||
headerCellStyle () {
|
||||
return () => 'background-color: #F8F8F8; color: #000000; font-weight: 500;'
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.autoHeight || this.height === 'auto') {
|
||||
this.startAutoHeight()
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.stopAutoHeight()
|
||||
},
|
||||
methods: {
|
||||
/* ============ 列处理 ============ */
|
||||
|
||||
colAttrs (col) {
|
||||
const attrs = { ...col }
|
||||
delete attrs.idx
|
||||
delete attrs.slot
|
||||
delete attrs.headerSlot
|
||||
return attrs
|
||||
},
|
||||
|
||||
/* ============ 表格事件 ============ */
|
||||
|
||||
onSelectionChange (val) {
|
||||
this.tableSelected = val
|
||||
this.$emit('selection-change', val)
|
||||
},
|
||||
|
||||
/* ============ 帮助按钮 ============ */
|
||||
|
||||
openHelp () {
|
||||
if (this.helpUrl) {
|
||||
window.open(this.helpUrl, '_blank')
|
||||
}
|
||||
},
|
||||
|
||||
/* ============ 分页事件 ============ */
|
||||
|
||||
onSizeChange (size) {
|
||||
this.$emit('page-change', {
|
||||
current: 1,
|
||||
size,
|
||||
total: this.pagination.total
|
||||
})
|
||||
},
|
||||
|
||||
onCurrentChange (current) {
|
||||
this.$emit('page-change', {
|
||||
current,
|
||||
size: this.pagination.size,
|
||||
total: this.pagination.total
|
||||
})
|
||||
},
|
||||
|
||||
/* ============ 自适应高度 ============ */
|
||||
|
||||
/**
|
||||
* 启动表格高度自适应
|
||||
*
|
||||
* 原理:
|
||||
* 1. CSS flex 布局让 page-table 占满父容器 100% 高度
|
||||
* 2. 工具栏和分页各占自然高度(flex-shrink: 0)
|
||||
* 3. tableWrapper 中间区域自动填充剩余空间(flex: 1 + min-height: 0)
|
||||
* 4. ResizeObserver 监听 tableWrapper 的实际渲染高度,赋给 el-table
|
||||
*
|
||||
* 这样 el-table 永远精确等于可用空间,不会产生外层滚动条
|
||||
*/
|
||||
startAutoHeight () {
|
||||
this.stopAutoHeight()
|
||||
|
||||
this._resizeObserver = new ResizeObserver(() => {
|
||||
this.$nextTick(() => {
|
||||
const wrapper = this.$refs.tableWrapper
|
||||
if (wrapper) {
|
||||
this.computedHeight = wrapper.clientHeight
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (this.$refs.tableWrapper) {
|
||||
this._resizeObserver.observe(this.$refs.tableWrapper)
|
||||
this.$nextTick(() => {
|
||||
const wrapper = this.$refs.tableWrapper
|
||||
if (wrapper) {
|
||||
this.computedHeight = wrapper.clientHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
stopAutoHeight () {
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.disconnect()
|
||||
this._resizeObserver = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
/* 自适应高度模式:占满父容器 100%,禁止自身溢出 */
|
||||
.page-table--auto {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.page-table__toolbar {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.page-table__toolbar-left {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.page-table__help-btn {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
.page-table__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.page-table__footer {
|
||||
flex-shrink: 0;
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.action-btn {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.action-btn--danger {
|
||||
color: #F56C6C;
|
||||
}
|
||||
</style>
|
||||
28
src/composables/useI18n.js
Normal file
28
src/composables/useI18n.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* i18n Mixin 工厂:消除每个页面重复写 tkey() 方法
|
||||
*
|
||||
* @param {string} prefix 页面的 i18n key 前缀,如 'page.production_master_data.factory_model.factory_area'
|
||||
* @returns {Object} Vue mixin
|
||||
*
|
||||
* @example
|
||||
* import { i18nMixin } from '@/composables/useI18n'
|
||||
* export default {
|
||||
* mixins: [i18nMixin('page.production_master_data.factory_model.factory_area')],
|
||||
* // 模板中当前页面用 $t(key('code'))
|
||||
* // 公共翻译用 $t(ckey('help')) → 'page.common.help'
|
||||
* }
|
||||
*/
|
||||
export function i18nMixin (prefix) {
|
||||
return {
|
||||
methods: {
|
||||
key (suffix) {
|
||||
return prefix + '.' + suffix
|
||||
},
|
||||
ckey (suffix) {
|
||||
return 'page.common.' + suffix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default i18nMixin
|
||||
48
src/composables/useTableButtons.js
Normal file
48
src/composables/useTableButtons.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 按钮定义工具:消除 buttonList / tableButtonList 重复定义
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Array} options.toolbar 顶部工具栏按钮
|
||||
* @param {Array} options.row 行内操作按钮
|
||||
* @param {Function} permissionCheck 权限校验函数,默认 $permission
|
||||
* @returns {{ toolbarButtons: Array, rowButtons: Array }}
|
||||
*
|
||||
* @example
|
||||
* import useTableButtons from '@/composables/useTableButtons'
|
||||
* // 在组件 data() 或 created() 中调用
|
||||
* const { toolbarButtons, rowButtons } = useTableButtons({
|
||||
* toolbar: [{ key: 'add', label: '新建', type: 'primary', auth: '/xxx/create', onClick: this.openDialog }],
|
||||
* row: [{ key: 'edit', label: '编辑', auth: '/xxx/edit', onClick: this.handleEdit }],
|
||||
* }, this.$permission)
|
||||
*/
|
||||
export function useTableButtons (options = {}, permissionCheck) {
|
||||
const check = permissionCheck || (() => true)
|
||||
|
||||
const toolbarButtons = (options.toolbar || []).map(btn => ({
|
||||
key: btn.key || btn.label,
|
||||
label: btn.label,
|
||||
icon: btn.icon,
|
||||
type: btn.type || '',
|
||||
size: btn.size || 'mini',
|
||||
auth: btn.auth,
|
||||
cssStyle: btn.cssStyle || {},
|
||||
onClick: btn.onClick,
|
||||
hasPermission: btn.auth ? check(btn.auth) : true
|
||||
}))
|
||||
|
||||
const rowButtons = (options.row || []).map(btn => ({
|
||||
key: btn.key || btn.label,
|
||||
label: btn.label,
|
||||
icon: btn.icon,
|
||||
color: btn.color || '',
|
||||
cssStyle: btn.cssStyle || { marginRight: '10px', cursor: 'pointer' },
|
||||
auth: btn.auth,
|
||||
confirm: btn.confirm || false,
|
||||
onClick: btn.onClick,
|
||||
hasPermission: btn.auth ? check(btn.auth) : true
|
||||
}))
|
||||
|
||||
return { toolbarButtons, rowButtons }
|
||||
}
|
||||
|
||||
export default useTableButtons
|
||||
46
src/composables/useTableColumns.js
Normal file
46
src/composables/useTableColumns.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 列定义工具:消除手动分配 idx,支持 _actions 约定
|
||||
*
|
||||
* @param {Array<Object>} rawColumns 列配置数组
|
||||
* @param {Object} options
|
||||
* @param {number} options.selectionWidth 复选框列宽度,默认 55,传 0 不显示
|
||||
* @param {number} options.indexWidth 序号列宽度,默认 60,传 0 不显示
|
||||
* @returns {Array<Object>} 补齐 idx 后的列数组
|
||||
*
|
||||
* @example
|
||||
* const columns = useTableColumns([
|
||||
* { prop: 'code', label: '编码', minWidth: 120 },
|
||||
* { prop: 'name', label: '名称' },
|
||||
* { prop: '_actions', label: '操作', width: 180, fixed: 'right' },
|
||||
* ])
|
||||
*/
|
||||
export function useTableColumns (rawColumns, options = {}) {
|
||||
const { selectionWidth = 55, indexWidth = 0 } = options
|
||||
|
||||
const result = []
|
||||
|
||||
if (selectionWidth > 0) {
|
||||
result.push({ idx: 0, type: 'selection', width: selectionWidth })
|
||||
}
|
||||
if (indexWidth > 0) {
|
||||
result.push({ idx: result.length, type: 'index', width: indexWidth, label: '#' })
|
||||
}
|
||||
|
||||
let slotIdx = 100
|
||||
rawColumns.forEach((col, i) => {
|
||||
const clean = { ...col }
|
||||
if (clean.prop === '_actions') {
|
||||
clean.slot = '_actions'
|
||||
clean.idx = slotIdx++
|
||||
} else if (clean.slot || clean.headerSlot) {
|
||||
clean.idx = slotIdx++
|
||||
} else {
|
||||
clean.idx = result.length
|
||||
}
|
||||
result.push(clean)
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default useTableColumns
|
||||
@@ -1,4 +1,5 @@
|
||||
import util from '@/libs/util.js'
|
||||
import { frameInRoutes } from '@/router/routes'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
@@ -8,9 +9,43 @@ export default {
|
||||
} else if (/^https:\/\/|http:\/\//.test(index)) {
|
||||
util.open(index)
|
||||
} else {
|
||||
this.$router.push({
|
||||
path: index
|
||||
})
|
||||
this.getRouterAuthPath(index, indexPath)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 点击菜单时进行权限过滤,查找第一个有权限的子路由
|
||||
* @param {String} index 选中菜单项的 index (path)
|
||||
* @param {Array} indexPath 选中菜单项的 index path
|
||||
*/
|
||||
getRouterAuthPath (index, indexPath) {
|
||||
// 首页或子级路由直接访问
|
||||
if (index === '/index' || !indexPath || indexPath.length > 1) {
|
||||
this.$router.push({ path: index })
|
||||
return
|
||||
}
|
||||
|
||||
// 查找一级路由下是否存在子路由
|
||||
let router = null
|
||||
for (const value of frameInRoutes) {
|
||||
if (value.path === index) {
|
||||
router = value.children
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 不存在子路由则直接进入
|
||||
if (!router) {
|
||||
this.$router.push({ path: index })
|
||||
return
|
||||
}
|
||||
|
||||
// 存在子路由时,跳转第一个有权限的子页面
|
||||
for (const value of router) {
|
||||
const newPath = index + '/' + value.path
|
||||
if (this.$permission(newPath)) {
|
||||
this.$router.push({ path: newPath })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,35 @@ const util = {
|
||||
log
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平数组转树形结构
|
||||
* @param {Array} data 扁平数组
|
||||
* @param {String} key 主键字段名
|
||||
* @param {String} pid 父节点字段名
|
||||
*/
|
||||
util.formatDataToTree = (data, key = 'menu_id', pid = 'parent_id') => {
|
||||
if (!data || data.length <= 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const map = {}
|
||||
data.forEach(value => { map[value[key]] = { ...value } })
|
||||
|
||||
const tree = []
|
||||
data.forEach(item => {
|
||||
const id = item[key]
|
||||
if (map[id][pid] && map[map[id][pid]]) {
|
||||
if (!map[map[id][pid]].children) {
|
||||
map[map[id][pid]].children = []
|
||||
}
|
||||
map[map[id][pid]].children.push(map[id])
|
||||
} else {
|
||||
tree.push(map[id])
|
||||
}
|
||||
})
|
||||
return tree
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新标题
|
||||
* @param {String} title 标题
|
||||
|
||||
@@ -2,12 +2,60 @@
|
||||
"_element": "en",
|
||||
"_name": "English",
|
||||
"page": {
|
||||
"common": {
|
||||
"help": "Help"
|
||||
},
|
||||
"demo": {
|
||||
"playground": {
|
||||
"locales": {
|
||||
"text": "D2Admin is a fully open source and free enterprise back-end product front-end integration solution, using the latest front-end technology stack, has prepared most of the project preparations, and with a lot of sample code to help the management system agile development."
|
||||
}
|
||||
}
|
||||
},
|
||||
"production_master_data": {
|
||||
"factory_model": {
|
||||
"factory_area": {
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"enter_code": "Please enter area code",
|
||||
"enter_name": "Please enter area name",
|
||||
"operation_success": "Operation succeeded",
|
||||
"create_success": "Created successfully",
|
||||
"edit_success": "Updated successfully",
|
||||
"delete_success": "Deleted successfully",
|
||||
"sort": "No.",
|
||||
"code": "Area Code",
|
||||
"name": "Area Name",
|
||||
"remark": "Remark",
|
||||
"operation": "Actions",
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"remark_required": "Please enter remark",
|
||||
"remark_length": "Length should be 1 to 100 characters",
|
||||
"add_title": "Add Area",
|
||||
"edit_title": "Edit Area",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"tip": "Tip",
|
||||
"confirm_delete": "Are you sure to delete?",
|
||||
"validation_fail": "Validation failed",
|
||||
"please_enter": "Please enter {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"login": {
|
||||
"time_is_most_precious": "Time is the most precious of all wealth",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"login": "Login",
|
||||
"please_enter_username": "Please enter username",
|
||||
"please_enter_password": "Please enter password",
|
||||
"dev_version": "Development Version",
|
||||
"test_version": "Test Version",
|
||||
"form_validation_failed": "Form validation failed, please check"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,60 @@
|
||||
"_element": "zh-CN",
|
||||
"_name": "简体中文",
|
||||
"page": {
|
||||
"common": {
|
||||
"help": "帮 助"
|
||||
},
|
||||
"demo": {
|
||||
"playground": {
|
||||
"locales": {
|
||||
"text": "D2Admin 是一个完全 开源免费 的企业中后台产品前端集成方案,使用最新的前端技术栈,已经做好大部分项目前期准备工作,并且带有大量示例代码,助力管理系统敏捷开发。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"production_master_data": {
|
||||
"factory_model": {
|
||||
"factory_area": {
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"enter_code": "请输入所区编码",
|
||||
"enter_name": "请输入所区名称",
|
||||
"operation_success": "操作成功",
|
||||
"create_success": "新增成功",
|
||||
"edit_success": "编辑成功",
|
||||
"delete_success": "删除成功",
|
||||
"sort": "序号",
|
||||
"code": "所区编码",
|
||||
"name": "所区名称",
|
||||
"remark": "备注",
|
||||
"operation": "操作",
|
||||
"add": "新 增",
|
||||
"edit": "编 辑",
|
||||
"delete": "删 除",
|
||||
"remark_required": "请输入备注",
|
||||
"remark_length": "长度在 1 到 100 个字符",
|
||||
"add_title": "新增所区",
|
||||
"edit_title": "编辑所区",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"tip": "提示",
|
||||
"confirm_delete": "确定要执行该操作吗?",
|
||||
"validation_fail": "校验失败",
|
||||
"please_enter": "请输入{name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"login": {
|
||||
"time_is_most_precious": "时间是一切财富中最宝贵的财富",
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"login": "登录",
|
||||
"please_enter_username": "请输入用户名",
|
||||
"please_enter_password": "请输入密码",
|
||||
"dev_version": "开发版本",
|
||||
"test_version": "测试版本",
|
||||
"form_validation_failed": "表单校验失败,请检查"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
src/main.js
29
src/main.js
@@ -9,12 +9,20 @@ import store from '@/store/index'
|
||||
|
||||
// 菜单和路由设置
|
||||
import router from './router'
|
||||
import { menuHeader, menuAside } from '@/menu'
|
||||
import { frameInRoutes } from '@/router/routes'
|
||||
|
||||
// 核心插件
|
||||
Vue.use(d2Admin)
|
||||
|
||||
// 权限控制指令
|
||||
Vue.directive('permission', {
|
||||
bind: (el, binding) => {
|
||||
if (!Vue.prototype.$permission(binding.value)) {
|
||||
el.parentNode ? el.parentNode.removeChild(el) : el.style.display = 'none'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
@@ -23,31 +31,16 @@ new Vue({
|
||||
created () {
|
||||
// 处理路由 得到每一级的路由设置
|
||||
this.$store.commit('d2admin/page/init', frameInRoutes)
|
||||
// 设置顶栏菜单
|
||||
this.$store.commit('d2admin/menu/headerSet', menuHeader)
|
||||
// 初始化菜单搜索功能
|
||||
this.$store.commit('d2admin/search/init', menuHeader)
|
||||
// 静态菜单和搜索由 account.load → sourceDataLoad 从后端动态加载
|
||||
},
|
||||
mounted () {
|
||||
// 展示系统信息
|
||||
this.$store.commit('d2admin/releases/versionShow')
|
||||
// 用户登录后从数据库加载一系列的设置
|
||||
// 用户登录后从数据库加载一系列的设置(含菜单)
|
||||
this.$store.dispatch('d2admin/account/load')
|
||||
// 获取并记录用户 UA
|
||||
this.$store.commit('d2admin/ua/get')
|
||||
// 初始化全屏监听
|
||||
this.$store.dispatch('d2admin/fullscreen/listen')
|
||||
},
|
||||
watch: {
|
||||
// 检测路由变化切换侧边栏内容
|
||||
'$route.matched': {
|
||||
handler (matched) {
|
||||
if (matched.length > 0) {
|
||||
const _side = menuAside.filter(menu => menu.path === matched[0].path)
|
||||
this.$store.commit('d2admin/menu/asideSet', _side.length > 0 ? _side[0].children : [])
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}).$mount('#app')
|
||||
|
||||
@@ -1,42 +1,52 @@
|
||||
import { uniqueId } from 'lodash'
|
||||
|
||||
// 插件
|
||||
import demoPlugins from './modules/demo-plugins'
|
||||
// 组件
|
||||
import demoComponents from './modules/demo-components'
|
||||
// 功能
|
||||
import demoPlayground from './modules/demo-playground'
|
||||
import util from '@/libs/util'
|
||||
|
||||
/**
|
||||
* @description 给菜单数据补充上 path 字段
|
||||
* @description https://github.com/d2-projects/d2-admin/issues/209
|
||||
* @param {Array} menu 原始的菜单数据
|
||||
* 将后端返回的扁平菜单数组转为 { header: [], aside: [] } 树结构
|
||||
* @param {Array} arr 后端返回的扁平菜单数据
|
||||
*/
|
||||
function supplementPath (menu) {
|
||||
return menu.map(e => ({
|
||||
...e,
|
||||
path: e.path || uniqueId('d2-menu-empty-'),
|
||||
...e.children ? {
|
||||
children: supplementPath(e.children)
|
||||
} : {}
|
||||
}))
|
||||
function getMenuData (arr) {
|
||||
const tree = { header: [], aside: [] }
|
||||
|
||||
arr.forEach(value => {
|
||||
if (!value.is_navi) {
|
||||
return
|
||||
}
|
||||
|
||||
const menuItem = {
|
||||
path: value.url || ('d2-menu-empty-' + value.menu_id),
|
||||
title: value.name,
|
||||
icon: value.icon,
|
||||
type: value.type,
|
||||
params: value.params
|
||||
}
|
||||
|
||||
// parent_id 为 0 的节点放入顶栏
|
||||
if (value.parent_id === 0) {
|
||||
tree.header.push({ ...menuItem })
|
||||
}
|
||||
|
||||
// 所有节点保留 menu_id / parent_id 用于构建侧栏树形结构
|
||||
menuItem.menu_id = value.menu_id
|
||||
menuItem.parent_id = value.parent_id
|
||||
tree.aside.push(menuItem)
|
||||
})
|
||||
|
||||
// 扁平 aside 数组转为嵌套树
|
||||
tree.aside = util.formatDataToTree(tree.aside)
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
// 菜单 侧边栏
|
||||
export const menuAside = supplementPath([
|
||||
demoComponents,
|
||||
demoPlugins,
|
||||
demoPlayground
|
||||
])
|
||||
|
||||
// 菜单 顶栏
|
||||
export const menuHeader = supplementPath([
|
||||
{
|
||||
path: '/index',
|
||||
title: '首页',
|
||||
icon: 'home'
|
||||
},
|
||||
demoPlayground,
|
||||
demoComponents,
|
||||
demoPlugins
|
||||
])
|
||||
export default {
|
||||
/**
|
||||
* 由 sourceDataLoad 调用,将菜单源数据写入 store
|
||||
* @param {Object} vm vuex store 实例 (this)
|
||||
* @param {Array} source 后端返回的扁平菜单数组
|
||||
*/
|
||||
install (vm, source) {
|
||||
vm.commit('d2admin/menu/headerAuth', source)
|
||||
const { header, aside } = getMenuData(source)
|
||||
vm.commit('d2admin/menu/headerSet', header)
|
||||
vm.commit('d2admin/menu/asideSet', aside)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
export default {
|
||||
path: '/demo/components',
|
||||
title: '组件',
|
||||
icon: 'puzzle-piece',
|
||||
children: [
|
||||
{ path: '/demo/components/index', title: '扩展组件', icon: 'home' },
|
||||
{
|
||||
path: '/demo/components/container',
|
||||
title: '布局容器',
|
||||
icon: 'window-restore',
|
||||
children: [
|
||||
{
|
||||
title: '填充型',
|
||||
children: [
|
||||
{ path: '/demo/components/container/full', title: '基础', icon: '' },
|
||||
{ path: '/demo/components/container/full-slot', title: '插槽', icon: '' },
|
||||
{ path: '/demo/components/container/full-bs', title: '滚动优化', icon: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '隐形模式',
|
||||
children: [
|
||||
{ path: '/demo/components/container/ghost', title: '基础', icon: '' },
|
||||
{ path: '/demo/components/container/ghost-slot', title: '插槽', icon: '' },
|
||||
{ path: '/demo/components/container/ghost-bs', title: '滚动优化', icon: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '卡片型',
|
||||
children: [
|
||||
{ path: '/demo/components/container/card', title: '基础', icon: '' },
|
||||
{ path: '/demo/components/container/card-slot', title: '插槽', icon: '' },
|
||||
{ path: '/demo/components/container/card-bs', title: '滚动优化', icon: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '方法',
|
||||
children: [
|
||||
{ path: '/demo/components/container/api?bs=false', title: '滚动控制', icon: '' },
|
||||
{ path: '/demo/components/container/api?bs=true', title: '滚动控制 BS', icon: '' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/components/layout/grid',
|
||||
title: '高级布局',
|
||||
icon: 'tasks',
|
||||
children: [
|
||||
{ path: '/demo/components/layout/grid', title: '拖拽位置和大小' },
|
||||
{ path: '/demo/components/layout/splitpane', title: '区域划分' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/components/editor',
|
||||
title: '编辑器',
|
||||
icon: 'pencil-square-o',
|
||||
children: [
|
||||
{ path: '/demo/components/editor-ueditor', title: 'UEditor', icon: '' },
|
||||
{ path: '/demo/components/editor-quill', title: 'Quill', icon: '' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/components/icon',
|
||||
title: '图标',
|
||||
icon: 'star',
|
||||
children: [
|
||||
{ path: '/demo/components/icon/icon', title: '图标组件' },
|
||||
{ path: '/demo/components/icon/icon-svg', title: 'svg 图标组件' },
|
||||
{ path: '/demo/components/icon/select', title: '图标选择器' },
|
||||
{ path: '/demo/components/icon/select-svg', title: 'svg 图标选择器' },
|
||||
{ path: '/demo/components/icon/list', title: 'FontAwesome' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/components/markdown',
|
||||
title: 'markdown 解析',
|
||||
icon: 'file-text-o',
|
||||
children: [
|
||||
{ path: '/demo/components/markdown/source', title: '指定资源' },
|
||||
{ path: '/demo/components/markdown/url', title: '异步加载文件' }
|
||||
]
|
||||
},
|
||||
{ path: '/demo/components/countup', title: '数字动画', icon: 'motorcycle' },
|
||||
{ path: '/demo/components/highlight', title: '代码高亮显示', icon: 'code' },
|
||||
{ path: '/demo/components/json-tree', title: 'JSON 展示', icon: 'sitemap' }
|
||||
]
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
export default {
|
||||
path: '/demo/playground',
|
||||
title: '功能',
|
||||
icon: 'flask',
|
||||
children: [
|
||||
{ path: '/demo/playground/index', title: '功能', icon: 'home' },
|
||||
{
|
||||
title: 'svg 菜单图标',
|
||||
iconSvg: 'd2-admin',
|
||||
children: [
|
||||
{ title: 'add', iconSvg: 'add' },
|
||||
{ title: 'alarm', iconSvg: 'alarm' },
|
||||
{ title: 'camera', iconSvg: 'camera' },
|
||||
{ title: 'history', iconSvg: 'history' },
|
||||
{ title: 'like', iconSvg: 'like' },
|
||||
{ title: 'love', iconSvg: 'love' },
|
||||
{ title: 'message', iconSvg: 'message' },
|
||||
{ title: 'notice', iconSvg: 'notice' },
|
||||
{ title: 'search', iconSvg: 'search' },
|
||||
{ title: 'share', iconSvg: 'share' },
|
||||
{ title: 'star', iconSvg: 'star' },
|
||||
{ title: 'user', iconSvg: 'user' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '空菜单演示',
|
||||
icon: 'folder-o',
|
||||
children: [
|
||||
{
|
||||
title: '正在开发 1',
|
||||
children: [
|
||||
{ title: '正在开发 1-1' },
|
||||
{ title: '正在开发 1-2' }
|
||||
]
|
||||
},
|
||||
{ title: '正在开发 2' },
|
||||
{ title: '正在开发 3' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/playground/frame',
|
||||
title: '内嵌网页',
|
||||
icon: 'globe',
|
||||
children: [
|
||||
{ path: '/demo/playground/frame/d2-doc', title: 'D2Admin 中文文档', iconSvg: 'd2-admin' },
|
||||
{ path: '/demo/playground/frame/html', title: '静态 HTML', icon: 'code' },
|
||||
{ path: '/demo/playground/frame/report', title: '构建分析', icon: 'pie-chart' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '新窗口打开链接',
|
||||
icon: 'link',
|
||||
children: [
|
||||
{ path: 'https://github.com/d2-projects/d2-admin', title: 'D2Admin Github', icon: 'github' },
|
||||
{ path: 'https://juejin.im/user/57a48b632e958a006691b946/posts', title: '掘金', icon: 'globe' },
|
||||
{ path: 'https://my.oschina.net/u/3871516', title: '开源中国', icon: 'globe' },
|
||||
{ path: 'https://www.zhihu.com/people/fairy-ever/activities', title: '知乎', icon: 'globe' },
|
||||
{ path: 'https://segmentfault.com/blog/liyang-note-book', title: 'segmentfault 专栏', icon: 'globe' },
|
||||
{ path: 'http://www.fairyever.com/', title: 'www.fairyever.com', icon: 'globe' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/playground/store',
|
||||
title: '全局状态管理',
|
||||
icon: 'bolt',
|
||||
children: [
|
||||
{ path: '/demo/playground/store/page', title: '多标签页控制', icon: 'window-restore' },
|
||||
{ path: '/demo/playground/store/menu', title: '菜单控制', icon: 'bars' },
|
||||
{ path: '/demo/playground/store/size', title: '全局尺寸', icon: 'font' },
|
||||
{ path: '/demo/playground/store/ua', title: '浏览器信息', icon: 'info-circle' },
|
||||
{ path: '/demo/playground/store/gray', title: '灰度模式', icon: 'eye' },
|
||||
{ path: '/demo/playground/store/fullscreen', title: '全屏', icon: 'arrows-alt' },
|
||||
{ path: '/demo/playground/store/theme', title: '主题', icon: 'diamond' },
|
||||
{ path: '/demo/playground/store/transition', title: '页面过渡开关', icon: 'toggle-on' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/playground/page-cache',
|
||||
title: '页面缓存',
|
||||
icon: 'hdd-o',
|
||||
children: [
|
||||
{ path: '/demo/playground/page-cache/on', title: '开启缓存' },
|
||||
{ path: '/demo/playground/page-cache/off', title: '关闭缓存' },
|
||||
{ path: '/demo/playground/page-cache/params/1', title: '带参路由缓存 1' },
|
||||
{ path: '/demo/playground/page-cache/params/2', title: '带参路由缓存 2' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/playground/page-argu',
|
||||
title: '参数传递和留存',
|
||||
icon: 'assistive-listening-systems',
|
||||
children: [
|
||||
{ path: '/demo/playground/page-argu/send', title: '发送' },
|
||||
{ path: '/demo/playground/page-argu/get/username-from-menu?userid=userid-from-menu', title: '接收' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/playground/db',
|
||||
title: '数据持久化',
|
||||
icon: 'database',
|
||||
children: [
|
||||
{ path: '/demo/playground/db/all', title: '总览', icon: 'table' },
|
||||
{ path: '/demo/playground/db/public', title: '公共存储', icon: 'users' },
|
||||
{ path: '/demo/playground/db/user', title: '私有数据', icon: 'user' },
|
||||
{ path: '/demo/playground/db/page-public', title: '路由存储', icon: 'file-o' },
|
||||
{ path: '/demo/playground/db/page-user', title: '私有路由存储', icon: 'file-o' },
|
||||
{ path: '/demo/playground/db/page-snapshot-public', title: '路由快照', icon: 'file' },
|
||||
{ path: '/demo/playground/db/page-snapshot-user', title: '私有路由快照', icon: 'file' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/playground/log',
|
||||
title: '日志',
|
||||
icon: 'bullseye',
|
||||
children: [
|
||||
{ path: '/demo/playground/log/log', title: '日志记录', icon: 'dot-circle-o' },
|
||||
{ path: '/demo/playground/log/error', title: '错误捕捉', icon: 'bug' },
|
||||
{ path: '/demo/playground/log/ajax', title: 'Ajax 错误', icon: 'bug' },
|
||||
{ path: '/demo/playground/log/console', title: '控制台日志', icon: 'lightbulb-o' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/playground/add-routes',
|
||||
title: '动态添加路由',
|
||||
icon: 'plus-square',
|
||||
children: [
|
||||
{ path: '/demo/playground/add-routes/routes', title: '添加页面', icon: 'file-o' }
|
||||
]
|
||||
},
|
||||
{ path: '/demo/playground/env', title: '环境信息', icon: 'exclamation-circle' },
|
||||
{ path: '/demo/playground/locales', title: '国际化', icon: 'language' }
|
||||
]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
export default {
|
||||
path: '/demo/plugins',
|
||||
title: '插件',
|
||||
icon: 'plug',
|
||||
children: [
|
||||
{ path: '/demo/plugins/index', title: '插件', icon: 'home' },
|
||||
{
|
||||
path: '/demo/plugins/import',
|
||||
title: '导入',
|
||||
icon: 'download',
|
||||
children: [
|
||||
{ path: '/demo/plugins/import/csv', title: 'csv' },
|
||||
{ path: '/demo/plugins/import/xlsx', title: 'xlsx' }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/demo/plugins/export',
|
||||
title: '导出',
|
||||
icon: 'upload',
|
||||
children: [
|
||||
{ path: '/demo/plugins/export/table', title: '表格' },
|
||||
{ path: '/demo/plugins/export/txt', title: '文本' }
|
||||
]
|
||||
},
|
||||
{ path: '/demo/plugins/clipboard-polyfill', title: '剪贴板访问', icon: 'clipboard' },
|
||||
{ path: '/demo/plugins/day', title: '日期计算', icon: 'clock-o' },
|
||||
{ path: '/demo/plugins/js-cookie', title: 'Cookie 读写', icon: 'asterisk' }
|
||||
]
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import '@/assets/svg-icons'
|
||||
// 国际化
|
||||
import i18n from '@/i18n.js'
|
||||
|
||||
import store from '@/store'
|
||||
|
||||
// 功能插件
|
||||
import pluginError from '@/plugin/error'
|
||||
import pluginLog from '@/plugin/log'
|
||||
@@ -28,6 +30,25 @@ export default {
|
||||
Vue.prototype.$version = process.env.VUE_APP_VERSION
|
||||
// 构建时间
|
||||
Vue.prototype.$buildTime = process.env.VUE_APP_BUILD_TIME
|
||||
|
||||
// 权限检查方法
|
||||
Vue.prototype.$permission = (value, type = 'menu') => {
|
||||
let path = ''
|
||||
const auth = store.state.d2admin.menu.authKey
|
||||
|
||||
switch (type) {
|
||||
case 'menu':
|
||||
path = value
|
||||
break
|
||||
case 'router':
|
||||
path = value.name.replace(/-/g, '/')
|
||||
path.slice(0, 1) !== '/' && (path = '/' + path)
|
||||
break
|
||||
}
|
||||
|
||||
return !!(path && Object.prototype.hasOwnProperty.call(auth, path))
|
||||
}
|
||||
|
||||
// Element
|
||||
Vue.use(ElementUI, {
|
||||
i18n: (key, value) => i18n.t(key, value)
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
|
||||
// 由于懒加载页面太多的话会造成webpack热更新太慢,所以开发环境不使用懒加载,只有生产环境使用懒加载
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
|
||||
const meta = { auth: true }
|
||||
|
||||
export default {
|
||||
path: '/demo/components',
|
||||
name: 'demo-components',
|
||||
meta,
|
||||
redirect: { name: 'demo-components-index' },
|
||||
component: layoutHeaderAside,
|
||||
children: [
|
||||
{ path: 'container/full', name: 'demo-components-container-full', component: _import('demo/components/container/full.vue'), meta: { ...meta, title: '布局组件 填充' } },
|
||||
{ path: 'container/full-slot', name: 'demo-components-container-full-slot', component: _import('demo/components/container/full-slot.vue'), meta: { ...meta, title: '布局组件 填充 插槽' } },
|
||||
{ path: 'container/full-bs', name: 'demo-components-container-full-bs', component: _import('demo/components/container/full-bs.vue'), meta: { ...meta, title: '布局组件 填充 滚动优化' } },
|
||||
{ path: 'container/ghost', name: 'demo-components-container-ghost', component: _import('demo/components/container/ghost.vue'), meta: { ...meta, title: '布局组件 隐形' } },
|
||||
{ path: 'container/ghost-slot', name: 'demo-components-container-ghost-slot', component: _import('demo/components/container/ghost-slot.vue'), meta: { ...meta, title: '布局组件 隐形 插槽' } },
|
||||
{ path: 'container/ghost-bs', name: 'demo-components-container-ghost-bs', component: _import('demo/components/container/ghost-bs.vue'), meta: { ...meta, title: '布局组件 隐形 滚动优化' } },
|
||||
{ path: 'container/card', name: 'demo-components-container-card', component: _import('demo/components/container/card.vue'), meta: { ...meta, title: '布局组件 卡片' } },
|
||||
{ path: 'container/card-slot', name: 'demo-components-container-card-slot', component: _import('demo/components/container/card-slot.vue'), meta: { ...meta, title: '布局组件 卡片 插槽' } },
|
||||
{ path: 'container/card-bs', name: 'demo-components-container-card-bs', component: _import('demo/components/container/card-bs.vue'), meta: { ...meta, title: '布局组件 卡片 滚动优化' } },
|
||||
{ path: 'container/api', name: 'demo-components-container-api', component: _import('demo/components/container/api.vue'), meta: { ...meta, title: '布局组件 API' } },
|
||||
{ path: 'countup', name: 'demo-components-countup', component: _import('demo/components/countup'), meta: { ...meta, title: '数字动画' } },
|
||||
{ path: 'editor-ueditor', name: 'demo-components-editor-ueditor', component: _import('demo/components/editor-ueditor'), meta: { ...meta, title: 'UEditor' } },
|
||||
{ path: 'editor-quill', name: 'demo-components-editor-quill', component: _import('demo/components/editor-quill'), meta: { ...meta, title: '富文本编辑器' } },
|
||||
{ path: 'highlight', name: 'demo-components-highlight', component: _import('demo/components/highlight'), meta: { ...meta, title: '代码高亮组件' } },
|
||||
{ path: 'icon/icon', name: 'demo-components-icon-icon', component: _import('demo/components/icon/icon.vue'), meta: { ...meta, title: '图标组件' } },
|
||||
{ path: 'icon/icon-svg', name: 'demo-components-icon-icon-svg', component: _import('demo/components/icon/icon-svg.vue'), meta: { ...meta, title: 'svg 图标' } },
|
||||
{ path: 'icon/select', name: 'demo-components-icon-select', component: _import('demo/components/icon/select.vue'), meta: { ...meta, title: '图标选择器' } },
|
||||
{ path: 'icon/select-svg', name: 'demo-components-icon-select-svg', component: _import('demo/components/icon/select-svg.vue'), meta: { ...meta, title: 'svg 图标选择器' } },
|
||||
{ path: 'icon/list', name: 'demo-components-icon-list', component: _import('demo/components/icon/list.vue'), meta: { ...meta, title: '图标列表' } },
|
||||
{ path: 'index', name: 'demo-components-index', component: _import('demo/components/index'), meta: { ...meta, title: '组件首页' } },
|
||||
{ path: 'json-tree', name: 'demo-components-json-tree', component: _import('demo/components/json-tree'), meta: { ...meta, title: 'JSON 展示' } },
|
||||
{ path: 'layout/grid', name: 'demo-components-layout-grid', component: _import('demo/components/layout/grid.vue'), meta: { ...meta, title: '拖拽网格布局' } },
|
||||
{ path: 'layout/splitpane', name: 'demo-components-layout-splitpane', component: _import('demo/components/layout/splitpane.vue'), meta: { ...meta, title: '区域布局' } },
|
||||
{ path: 'markdown/source', name: 'demo-components-markdown-source', component: _import('demo/components/markdown/source.vue'), meta: { ...meta, title: 'markdown指定资源渲染' } },
|
||||
{ path: 'markdown/url', name: 'demo-components-markdown-url', component: _import('demo/components/markdown/url.vue'), meta: { ...meta, title: 'markdown指定url渲染' } }
|
||||
]
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
|
||||
// 由于懒加载页面太多的话会造成webpack热更新太慢,所以开发环境不使用懒加载,只有生产环境使用懒加载
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
|
||||
const meta = { auth: true }
|
||||
|
||||
export default {
|
||||
path: '/demo/playground',
|
||||
name: 'demo-playground',
|
||||
meta,
|
||||
redirect: { name: 'demo-playground-index' },
|
||||
component: layoutHeaderAside,
|
||||
children: [
|
||||
{ path: 'index', name: 'demo-playground-index', component: _import('demo/playground/index'), meta: { ...meta, title: '功能首页' } },
|
||||
{ path: 'store/page', name: 'demo-playground-store-page', component: _import('demo/playground/store/page'), meta: { ...meta, cache: true, title: '多标签页控制' } },
|
||||
{ path: 'store/menu', name: 'demo-playground-store-menu', component: _import('demo/playground/store/menu'), meta: { ...meta, title: '菜单控制' } },
|
||||
{ path: 'store/size', name: 'demo-playground-store-size', component: _import('demo/playground/store/size'), meta: { ...meta, title: '全局尺寸' } },
|
||||
{ path: 'store/ua', name: 'demo-playground-store-ua', component: _import('demo/playground/store/ua'), meta: { ...meta, title: '浏览器信息' } },
|
||||
{ path: 'store/gray', name: 'demo-playground-store-gray', component: _import('demo/playground/store/gray'), meta: { ...meta, title: '灰度模式' } },
|
||||
{ path: 'store/fullscreen', name: 'demo-playground-store-fullscreen', component: _import('demo/playground/store/fullscreen'), meta: { ...meta, title: '全屏' } },
|
||||
{ path: 'store/theme', name: 'demo-playground-store-theme', component: _import('demo/playground/store/theme'), meta: { ...meta, title: '主题' } },
|
||||
{ path: 'store/transition', name: 'demo-playground-store-transition', component: _import('demo/playground/store/transition'), meta: { ...meta, title: '页面过渡开关' } },
|
||||
{ path: 'page-cache/on', name: 'demo-playground-page-cache-on', component: _import('demo/playground/page-cache/on.vue'), meta: { ...meta, cache: true, title: '开启缓存' } },
|
||||
{ path: 'page-cache/off', name: 'demo-playground-page-cache-off', component: _import('demo/playground/page-cache/off.vue'), meta: { ...meta, title: '关闭缓存' } },
|
||||
{ path: 'page-cache/params/:id', name: 'demo-playground-page-cache-params', component: _import('demo/playground/page-cache/params.vue'), meta: { ...meta, cache: true, title: '带参路由缓存' }, props: true },
|
||||
{ path: 'page-argu/send', name: 'demo-playground-page-argu-send', component: _import('demo/playground/page-argu/send.vue'), meta: { ...meta, title: '参数传递 发送' } },
|
||||
{ path: 'page-argu/get/:username', name: 'demo-playground-page-argu-get', component: _import('demo/playground/page-argu/get.vue'), meta: { ...meta, title: '参数传递 接收' } },
|
||||
{ path: 'db/all', name: 'demo-playground-db-all', component: _import('demo/playground/db/all'), meta: { ...meta, title: '总览' } },
|
||||
{ path: 'db/public', name: 'demo-playground-db-public', component: _import('demo/playground/db/public'), meta: { ...meta, title: '公共存储' } },
|
||||
{ path: 'db/user', name: 'demo-playground-db-user', component: _import('demo/playground/db/user'), meta: { ...meta, title: '私有存储' } },
|
||||
{ path: 'db/page-public', name: 'demo-playground-db-page-public', component: _import('demo/playground/db/page-public'), meta: { ...meta, title: '路由存储' } },
|
||||
{ path: 'db/page-user', name: 'demo-playground-db-page-user', component: _import('demo/playground/db/page-user'), meta: { ...meta, title: '私有路由存储' } },
|
||||
{ path: 'db/page-snapshot-public', name: 'demo-playground-db-page-snapshot-public', component: _import('demo/playground/db/page-snapshot-public'), meta: { ...meta, title: '路由快照' } },
|
||||
{ path: 'db/page-snapshot-user', name: 'demo-playground-db-page-snapshot-user', component: _import('demo/playground/db/page-snapshot-user'), meta: { ...meta, title: '私有路由快照' } },
|
||||
{ path: 'log/ajax', name: 'demo-playground-log-ajax', component: _import('demo/playground/log/ajax'), meta: { ...meta, title: 'Ajax 错误' } },
|
||||
{ path: 'log/console', name: 'demo-playground-log-console', component: _import('demo/playground/log/console'), meta: { ...meta, title: '控制台日志' } },
|
||||
{ path: 'log/error', name: 'demo-playground-log-error', component: _import('demo/playground/log/error'), meta: { ...meta, title: '错误捕捉' } },
|
||||
{ path: 'log/log', name: 'demo-playground-log-log', component: _import('demo/playground/log/log'), meta: { ...meta, title: '日志记录' } },
|
||||
{ path: 'add-routes/routes', name: 'demo-playground-add-routes-routes', component: _import('demo/playground/add-routes/routes'), meta: { ...meta, title: '添加页面' } },
|
||||
{ path: 'env', name: 'demo-playground-env', component: _import('demo/playground/env'), meta: { ...meta, title: '环境信息' } },
|
||||
{ path: 'locales', name: 'demo-playground-locales', component: _import('demo/playground/locales'), meta: { ...meta, title: '国际化' } },
|
||||
{ path: 'frame/html', name: 'demo-playground-frame-html', component: _import('demo/playground/frame/html'), meta: { ...meta, title: '静态 HTML' } },
|
||||
{ path: 'frame/report', name: 'demo-playground-frame-report', component: _import('demo/playground/frame/report'), meta: { ...meta, title: 'Size report' } },
|
||||
{ path: 'frame/d2-doc', name: 'demo-playground-frame-d2-doc', component: _import('demo/playground/frame/d2-doc'), meta: { ...meta, title: 'D2Admin 中文文档' } }
|
||||
]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
|
||||
// 由于懒加载页面太多的话会造成webpack热更新太慢,所以开发环境不使用懒加载,只有生产环境使用懒加载
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
|
||||
const meta = { auth: true }
|
||||
|
||||
export default {
|
||||
path: '/demo/plugins',
|
||||
name: 'demo-plugins',
|
||||
meta,
|
||||
redirect: { name: 'demo-plugins-index' },
|
||||
component: layoutHeaderAside,
|
||||
children: [
|
||||
{ path: 'clipboard-polyfill', name: 'demo-plugins-clipboard-polyfill', component: _import('demo/plugins/clipboard-polyfill'), meta: { ...meta, title: '剪贴板访问' } },
|
||||
{ path: 'day', name: 'demo-plugins-day', component: _import('demo/plugins/day'), meta: { ...meta, title: '日期计算' } },
|
||||
{ path: 'export/table', name: 'demo-plugins-export-table', component: _import('demo/plugins/export/table.vue'), meta: { ...meta, title: '导出表格' } },
|
||||
{ path: 'export/txt', name: 'demo-plugins-export-txt', component: _import('demo/plugins/export/txt.vue'), meta: { ...meta, title: '导出文本' } },
|
||||
{ path: 'import/csv', name: 'demo-plugins-import-csv', component: _import('demo/plugins/import/csv.vue'), meta: { ...meta, title: '导入 csv' } },
|
||||
{ path: 'import/xlsx', name: 'demo-plugins-import-xlsx', component: _import('demo/plugins/import/xlsx.vue'), meta: { ...meta, title: '导入 xlsx' } },
|
||||
{ path: 'index', name: 'demo-plugins-index', component: _import('demo/plugins/index'), meta: { ...meta, title: '插件首页' } },
|
||||
{ path: 'js-cookie', name: 'demo-plugins-js-cookie', component: _import('demo/plugins/js-cookie'), meta: { ...meta, title: 'Cookie' } }
|
||||
]
|
||||
}
|
||||
18
src/router/modules/production-master-data.js
Normal file
18
src/router/modules/production-master-data.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
|
||||
const meta = { auth: true }
|
||||
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
|
||||
export default {
|
||||
path: '/production_configuration',
|
||||
component: layoutHeaderAside,
|
||||
children: (pre => [
|
||||
{
|
||||
path: 'factory_model/factory_area',
|
||||
name: `${pre}factory_model-factory_area`,
|
||||
meta: { ...meta, cache: true, title: '工厂区域' },
|
||||
component: _import('production-master-data/factory-model/factory-area')
|
||||
}
|
||||
])('production_configuration-')
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import playground from './modules/playground'
|
||||
import plugins from './modules/plugins'
|
||||
import components from './modules/components'
|
||||
|
||||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
import productionMasterData from './modules/production-master-data'
|
||||
|
||||
// 由于懒加载页面太多的话会造成webpack热更新太慢,所以开发环境不使用懒加载,只有生产环境使用懒加载
|
||||
const _import = require('@/libs/util.import.' + process.env.NODE_ENV)
|
||||
@@ -52,9 +49,7 @@ const frameIn = [
|
||||
}
|
||||
]
|
||||
},
|
||||
playground,
|
||||
plugins,
|
||||
components
|
||||
productionMasterData
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import util from '@/libs/util.js'
|
||||
import { loginAdminUser, logoutAdminUser } from '@/api/auth'
|
||||
|
||||
import { MessageBox, Loading } from 'element-ui'
|
||||
import router from '@/router'
|
||||
import { SYS_USER_LOGIN } from '@/api/sys.user.js'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@@ -17,16 +18,24 @@ export default {
|
||||
username = '',
|
||||
password = ''
|
||||
} = {}) {
|
||||
const res = await SYS_USER_LOGIN({ username, password })
|
||||
// 设置 cookie 一定要存 uuid 和 token 两个 cookie
|
||||
// 整个系统依赖这两个数据进行校验和存储
|
||||
// uuid 是用户身份唯一标识 用户注册的时候确定 并且不可改变 不可重复
|
||||
// token 代表用户当前登录状态 建议在网络请求中携带 token
|
||||
// 如有必要 token 需要定时更新,默认保存一天
|
||||
util.cookies.set('uuid', res.uuid)
|
||||
util.cookies.set('token', res.token)
|
||||
// 新的写法
|
||||
const res = await loginAdminUser({ username, password })
|
||||
// 设置用户数据
|
||||
const cookieSetting = { expires: 365 }
|
||||
util.cookies.set('uuid', res.userInfo.username, cookieSetting)
|
||||
util.cookies.set('token', res.token, cookieSetting)
|
||||
util.cookies.set('block', 'false')
|
||||
|
||||
// 存储用户数据到本地
|
||||
localStorage.setItem('user_id', res.userInfo.user_id)
|
||||
|
||||
// 设置 vuex 用户信息
|
||||
await dispatch('d2admin/user/set', { name: res.name }, { root: true })
|
||||
await dispatch('d2admin/user/set', {
|
||||
name: res.userInfo.username,
|
||||
admin: res.userInfo,
|
||||
token: res.token
|
||||
}, { root: true })
|
||||
|
||||
// 用户登录后从持久化数据加载一系列的设置
|
||||
await dispatch('load')
|
||||
},
|
||||
@@ -35,34 +44,59 @@ export default {
|
||||
* @param {Object} context
|
||||
* @param {Object} payload confirm {Boolean} 是否需要确认
|
||||
*/
|
||||
logout ({ commit, dispatch }, { confirm = false } = {}) {
|
||||
/**
|
||||
* @description 注销
|
||||
*/
|
||||
async function logout () {
|
||||
// 删除cookie
|
||||
util.cookies.remove('token')
|
||||
// 清空 vuex 用户信息
|
||||
await dispatch('d2admin/user/set', {}, { root: true })
|
||||
util.cookies.remove('uuid')
|
||||
// 跳转路由
|
||||
router.push({ name: 'login' })
|
||||
}
|
||||
// 判断是否需要确认
|
||||
if (confirm) {
|
||||
commit('d2admin/gray/set', true, { root: true })
|
||||
MessageBox.confirm('确定要注销当前用户吗', '注销用户', { type: 'warning' })
|
||||
.then(() => {
|
||||
commit('d2admin/gray/set', false, { root: true })
|
||||
logout()
|
||||
logout ({ dispatch }, { confirm = false } = {}) {
|
||||
// 加载遮罩
|
||||
let loading = null
|
||||
// 实际注销操作
|
||||
function doLogout () {
|
||||
logoutAdminUser()
|
||||
.finally(() => {
|
||||
// 删除sourceData
|
||||
util.cookies.set('block', 'true')
|
||||
dispatch('d2admin/db/set', {
|
||||
dbName: 'database',
|
||||
path: '$menu.sourceData',
|
||||
value: [],
|
||||
user: true
|
||||
}, { root: true })
|
||||
|
||||
// 删除info
|
||||
dispatch('d2admin/user/set', {}, { root: true })
|
||||
|
||||
// 删除cookie
|
||||
util.cookies.remove('token')
|
||||
util.cookies.remove('uuid')
|
||||
|
||||
// 刷新页面
|
||||
loading && loading.close()
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
.catch(() => {
|
||||
commit('d2admin/gray/set', false, { root: true })
|
||||
Message({ message: '取消注销操作' })
|
||||
})
|
||||
} else {
|
||||
logout()
|
||||
}
|
||||
|
||||
if (!confirm) {
|
||||
doLogout()
|
||||
return
|
||||
}
|
||||
|
||||
MessageBox.confirm('确定要执行注销操作吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
closeOnClickModal: false
|
||||
})
|
||||
.then(() => {
|
||||
loading = Loading.service({
|
||||
lock: true,
|
||||
text: 'Loading',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
doLogout()
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
},
|
||||
/**
|
||||
* @description 用户登录后从持久化数据加载一系列的设置
|
||||
@@ -83,6 +117,8 @@ export default {
|
||||
await dispatch('d2admin/size/load', null, { root: true })
|
||||
// 持久化数据加载颜色设置
|
||||
await dispatch('d2admin/color/load', null, { root: true })
|
||||
// DB -> store 持久化数据读取菜单源数据
|
||||
await dispatch('d2admin/menu/sourceDataLoad', null, { root: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// 设置文件
|
||||
import setting from '@/setting.js'
|
||||
import util from '@/libs/util'
|
||||
import menu from '@/menu'
|
||||
import { getMenuAll } from '@/api/menu'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@@ -11,18 +13,15 @@ export default {
|
||||
// 侧边栏收缩
|
||||
asideCollapse: setting.menu.asideCollapse,
|
||||
// 侧边栏折叠动画
|
||||
asideTransition: setting.menu.asideTransition
|
||||
asideTransition: setting.menu.asideTransition,
|
||||
// 权限字典 { '/path': '菜单名' }
|
||||
authKey: {},
|
||||
// 菜单源数据
|
||||
sourceData: []
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 设置侧边栏展开或者收缩
|
||||
* @param {Object} context
|
||||
* @param {Boolean} collapse is collapse
|
||||
*/
|
||||
async asideCollapseSet ({ state, dispatch }, collapse) {
|
||||
// store 赋值
|
||||
state.asideCollapse = collapse
|
||||
// 持久化
|
||||
await dispatch('d2admin/db/set', {
|
||||
dbName: 'sys',
|
||||
path: 'menu.asideCollapse',
|
||||
@@ -30,14 +29,8 @@ export default {
|
||||
user: true
|
||||
}, { root: true })
|
||||
},
|
||||
/**
|
||||
* 切换侧边栏展开和收缩
|
||||
* @param {Object} context
|
||||
*/
|
||||
async asideCollapseToggle ({ state, dispatch }) {
|
||||
// store 赋值
|
||||
state.asideCollapse = !state.asideCollapse
|
||||
// 持久化
|
||||
await dispatch('d2admin/db/set', {
|
||||
dbName: 'sys',
|
||||
path: 'menu.asideCollapse',
|
||||
@@ -45,15 +38,8 @@ export default {
|
||||
user: true
|
||||
}, { root: true })
|
||||
},
|
||||
/**
|
||||
* 设置侧边栏折叠动画
|
||||
* @param {Object} context
|
||||
* @param {Boolean} transition is transition
|
||||
*/
|
||||
async asideTransitionSet ({ state, dispatch }, transition) {
|
||||
// store 赋值
|
||||
state.asideTransition = transition
|
||||
// 持久化
|
||||
await dispatch('d2admin/db/set', {
|
||||
dbName: 'sys',
|
||||
path: 'menu.asideTransition',
|
||||
@@ -61,14 +47,8 @@ export default {
|
||||
user: true
|
||||
}, { root: true })
|
||||
},
|
||||
/**
|
||||
* 切换侧边栏折叠动画
|
||||
* @param {Object} context
|
||||
*/
|
||||
async asideTransitionToggle ({ state, dispatch }) {
|
||||
// store 赋值
|
||||
state.asideTransition = !state.asideTransition
|
||||
// 持久化
|
||||
await dispatch('d2admin/db/set', {
|
||||
dbName: 'sys',
|
||||
path: 'menu.asideTransition',
|
||||
@@ -76,39 +56,58 @@ export default {
|
||||
user: true
|
||||
}, { root: true })
|
||||
},
|
||||
/**
|
||||
* 持久化数据加载侧边栏设置
|
||||
* @param {Object} context
|
||||
*/
|
||||
async asideLoad ({ state, dispatch }) {
|
||||
// store 赋值
|
||||
const menu = await dispatch('d2admin/db/get', {
|
||||
const menuSetting = await dispatch('d2admin/db/get', {
|
||||
dbName: 'sys',
|
||||
path: 'menu',
|
||||
defaultValue: setting.menu,
|
||||
user: true
|
||||
}, { root: true })
|
||||
state.asideCollapse = menu.asideCollapse !== undefined ? menu.asideCollapse : setting.menu.asideCollapse
|
||||
state.asideTransition = menu.asideTransition !== undefined ? menu.asideTransition : setting.menu.asideTransition
|
||||
state.asideCollapse = menuSetting.asideCollapse !== undefined ? menuSetting.asideCollapse : setting.menu.asideCollapse
|
||||
state.asideTransition = menuSetting.asideTransition !== undefined ? menuSetting.asideTransition : setting.menu.asideTransition
|
||||
},
|
||||
async sourceDataLoad ({ state, commit, dispatch }) {
|
||||
// 优先从 localStorage 读取上次缓存的菜单数据
|
||||
state.sourceData = await dispatch('d2admin/db/get', {
|
||||
dbName: 'database',
|
||||
path: '$menu.sourceData',
|
||||
defaultValue: [],
|
||||
user: true
|
||||
}, { root: true })
|
||||
|
||||
// 缓存为空且已登录时,从后端获取菜单
|
||||
if (!state.sourceData.length && util.cookies.get('token')) {
|
||||
const res = await getMenuAll()
|
||||
state.sourceData = res || []
|
||||
await dispatch('d2admin/db/set', {
|
||||
dbName: 'database',
|
||||
path: '$menu.sourceData',
|
||||
value: state.sourceData,
|
||||
user: true
|
||||
}, { root: true })
|
||||
}
|
||||
|
||||
// 构建菜单树和权限字典
|
||||
menu.install(this, state.sourceData)
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
/**
|
||||
* @description 设置顶栏菜单
|
||||
* @param {Object} state state
|
||||
* @param {Array} menu menu setting
|
||||
*/
|
||||
headerAuth (state, source) {
|
||||
if (!source || source.length <= 0) {
|
||||
return
|
||||
}
|
||||
const auth = {}
|
||||
source.forEach(value => {
|
||||
if (value.url) {
|
||||
auth[value.url] = value.name
|
||||
}
|
||||
})
|
||||
state.authKey = auth
|
||||
},
|
||||
headerSet (state, menu) {
|
||||
// store 赋值
|
||||
state.header = menu
|
||||
},
|
||||
/**
|
||||
* @description 设置侧边栏菜单
|
||||
* @param {Object} state state
|
||||
* @param {Array} menu menu setting
|
||||
*/
|
||||
asideSet (state, menu) {
|
||||
// store 赋值
|
||||
state.aside = menu
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
<template>
|
||||
<d2-container
|
||||
ref="container"
|
||||
:type="containerType"
|
||||
:better-scroll="betterScroll"
|
||||
:scroll-delay="scrollDelay"
|
||||
@scroll="({x, y}) => { scrollTop = y }">
|
||||
<template slot="header">
|
||||
<el-form
|
||||
:inline="true"
|
||||
size="mini">
|
||||
<el-form-item
|
||||
label="布局类型"
|
||||
class="d2-mb-0">
|
||||
<el-radio-group v-model="containerType">
|
||||
<el-radio-button label="full"></el-radio-button>
|
||||
<el-radio-button label="card"></el-radio-button>
|
||||
<el-radio-button label="ghost"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="滚动距离"
|
||||
class="d2-mb-0">
|
||||
<el-input
|
||||
:value="scrollTop"
|
||||
style="width: 130px;">
|
||||
<template slot="append">px</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="!betterScroll"
|
||||
label="事件延迟(ms)"
|
||||
class="d2-mb-0">
|
||||
<el-input-number
|
||||
v-model="scrollDelay"
|
||||
:min="10"
|
||||
:max="2000"
|
||||
:step="100"
|
||||
style="width: 110px;"/>
|
||||
</el-form-item>
|
||||
<el-form-item class="d2-mb-0">
|
||||
<el-button
|
||||
v-if="scrollTop >= 55"
|
||||
type="primary"
|
||||
@click="$refs.container.scrollToTop">
|
||||
回到顶部
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<el-alert
|
||||
type="success"
|
||||
:title="`${betterScroll ? '此示例开启了 BetterScroll ' : ''}请向下滚动`"
|
||||
class="d2-mb-10"
|
||||
center/>
|
||||
<d2-demo-article
|
||||
v-for="i in 10"
|
||||
:key="i"
|
||||
:style="articleStyle"
|
||||
long/>
|
||||
<el-form
|
||||
slot="footer"
|
||||
:inline="true"
|
||||
size="mini">
|
||||
<el-form-item class="d2-mb-0">
|
||||
<el-button @click="$refs.container.scrollBy(0, 30)">相对滚动 (0, 30) 像素</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item class="d2-mb-0">
|
||||
<el-button @click="$refs.container.scrollTo(0, 100)">滚动到 (0, 100) 像素位置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item class="d2-mb-0">
|
||||
<el-button @click="$refs.container.scrollTop(100)">滚动到垂直位置 100</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
containerType: 'full',
|
||||
scrollDelay: 10,
|
||||
scrollTop: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否开启 better scroll
|
||||
betterScroll () {
|
||||
return this.$route.query.bs === 'true'
|
||||
},
|
||||
// 根据滚动位置返回文章的样式
|
||||
articleStyle () {
|
||||
return {
|
||||
opacity: this.scrollTop > 55 ? '1' : '.1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card" better-scroll>
|
||||
<template slot="header">Header</template>
|
||||
<d2-demo-article/>
|
||||
<template slot="footer">Footer</template>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<template slot="header">Header</template>
|
||||
<d2-demo-article/>
|
||||
<template slot="footer">Footer</template>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<d2-demo-article/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,50 +0,0 @@
|
||||
<template>
|
||||
<div class="d2-demo-article">
|
||||
<div v-if="!long" class="d2-demo-article__control">
|
||||
<el-switch
|
||||
v-model="isLong"
|
||||
active-text="长内容"
|
||||
inactive-text="短内容"/>
|
||||
</div>
|
||||
<d2-markdown v-show="isLong" :source="sourceLong"/>
|
||||
<d2-markdown v-show="!isLong" :source="sourceShort"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import sourceLong from '../md/long.md'
|
||||
import sourceShort from '../md/short.md'
|
||||
export default {
|
||||
props: {
|
||||
// 指定为长文本
|
||||
long: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sourceLong,
|
||||
sourceShort,
|
||||
isLong: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.isLong = this.long
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.d2-demo-article {
|
||||
transition: opacity .3s;
|
||||
.d2-demo-article__control {
|
||||
padding: 8px 16px;
|
||||
margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
background-color: #f4f4f5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<d2-container better-scroll>
|
||||
<template slot="header">Header</template>
|
||||
<d2-demo-article/>
|
||||
<template slot="footer">Header</template>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">Header</template>
|
||||
<d2-demo-article/>
|
||||
<template slot="footer">Footer</template>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,14 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<d2-demo-article/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="ghost" better-scroll>
|
||||
<template slot="header">Header</template>
|
||||
<div class="d2-pt d2-pb">
|
||||
<el-card shadow="never" class="d2-card" style="width: 400px;">
|
||||
<d2-demo-article/>
|
||||
</el-card>
|
||||
</div>
|
||||
<template slot="footer">Footer</template>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="ghost">
|
||||
<template slot="header">Header</template>
|
||||
<div class="d2-pt d2-pb">
|
||||
<el-card shadow="never" class="d2-card" style="width: 400px;">
|
||||
<d2-demo-article/>
|
||||
</el-card>
|
||||
</div>
|
||||
<template slot="footer">Footer</template>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="ghost">
|
||||
<div class="d2-pt d2-pb">
|
||||
<el-card shadow="never" class="d2-card" style="width: 400px;">
|
||||
<d2-demo-article/>
|
||||
</el-card>
|
||||
</div>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import d2DemoArticle from './components/d2-demo-article'
|
||||
export default {
|
||||
components: {
|
||||
'd2-demo-article': d2DemoArticle
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,53 +0,0 @@
|
||||
## vue.js
|
||||
|
||||
**易用**
|
||||
|
||||
已经会了 HTML、CSS、JavaScript?即刻阅读指南开始构建应用!
|
||||
|
||||
**灵活**
|
||||
|
||||
不断繁荣的生态系统,可以在一个库和一套完整框架之间自如伸缩。
|
||||
|
||||
**高效**
|
||||
|
||||
20kB min+gzip 运行大小
|
||||
|
||||
超快虚拟 DOM
|
||||
|
||||
最省心的优化
|
||||
|
||||
**Vue.js 是什么**
|
||||
|
||||
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
|
||||
|
||||
如果你已经是有经验的前端开发者,想知道 Vue 与其它库/框架有哪些区别,请查看对比其它框架。
|
||||
|
||||
## Element
|
||||
|
||||
Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库
|
||||
|
||||
**一致性** Consistency
|
||||
|
||||
- 与现实生活一致:与现实生活的流程、逻辑保持一致,遵循用户习惯的语言和概念;
|
||||
|
||||
- 在界面中一致:所有的元素和结构需保持一致,比如:设计样式、图标和文本、元素的位置等。
|
||||
|
||||
**反馈** Feedback
|
||||
|
||||
- 控制反馈:通过界面样式和交互动效让用户可以清晰的感知自己的操作;
|
||||
|
||||
- 页面反馈:操作后,通过页面元素的变化清晰地展现当前状态。
|
||||
|
||||
**效率** Efficiency
|
||||
|
||||
- 简化流程:设计简洁直观的操作流程;
|
||||
|
||||
- 清晰明确:语言表达清晰且表意明确,让用户快速理解进而作出决策;
|
||||
|
||||
- 帮助用户识别:界面简单直白,让用户快速识别而非回忆,减少用户记忆负担。
|
||||
|
||||
**可控** Controllability
|
||||
|
||||
- 用户决策:根据场景可给予用户操作建议或安全提示,但不能代替用户进行决策;
|
||||
|
||||
- 结果可控:用户可以自由的进行操作,包括撤销、回退和终止当前操作等。
|
||||
@@ -1,17 +0,0 @@
|
||||
## vue.js
|
||||
|
||||
**易用**
|
||||
|
||||
已经会了 HTML、CSS、JavaScript?即刻阅读指南开始构建应用!
|
||||
|
||||
**灵活**
|
||||
|
||||
不断繁荣的生态系统,可以在一个库和一套完整框架之间自如伸缩。
|
||||
|
||||
**高效**
|
||||
|
||||
20kB min+gzip 运行大小
|
||||
|
||||
超快虚拟 DOM
|
||||
|
||||
最省心的优化
|
||||
@@ -1,94 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card" class="page">
|
||||
<template slot="header">数字动画组件</template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="d2-card d2-mb">
|
||||
<p slot="title">只设置目标数字</p>
|
||||
<div class="group">
|
||||
<d2-count-up :end="100"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="d2-card d2-mb">
|
||||
<p slot="title">设置起止数值</p>
|
||||
<div class="group">
|
||||
<d2-count-up :start="14" :end="100"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="d2-card d2-mb">
|
||||
<p slot="title">小数位数</p>
|
||||
<div class="group">
|
||||
<d2-count-up :end="100" :decimals="2"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="d2-card d2-mb">
|
||||
<p slot="title">动画时长</p>
|
||||
<div class="group">
|
||||
<d2-count-up :end="100" :duration="6"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="d2-card">
|
||||
<p slot="title">回调函数</p>
|
||||
<div class="group">
|
||||
<d2-count-up :end="100" :callback="() => {className = 'end'}" :class="className"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="never" class="d2-card d2-mb-0">
|
||||
<p slot="title">结束一秒后更新数值</p>
|
||||
<div class="group">
|
||||
<d2-count-up :end="end" :callback="update"/>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
// 回调函数使用
|
||||
className: '',
|
||||
// 更新数值用
|
||||
end: 50
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update () {
|
||||
setTimeout(() => {
|
||||
this.end = 100
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
.group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
span {
|
||||
font-size: 60px;
|
||||
&.end {
|
||||
padding: 0px 20px;
|
||||
border-radius: 4px;
|
||||
background-color: $color-success;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<d2-quill
|
||||
style="min-height: 200px; margin-bottom: 20px;"
|
||||
v-model="value"
|
||||
@text-change="textChangeHandler"
|
||||
@selection-change="selectionChangeHandler"
|
||||
@editor-change="editorChangeHandler"/>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleAddRow">
|
||||
添加一行
|
||||
</el-button>
|
||||
<el-card shadow="never" class="d2-card d2-mt">
|
||||
<d2-highlight :code="value" format-html/>
|
||||
</el-card>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import value from './value'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAddRow () {
|
||||
this.value += '<p style="color: #409EFF;">我是新增的行</p>'
|
||||
},
|
||||
textChangeHandler (delta, oldDelta, source) {
|
||||
// console.group('QuillEditor textChangeHandler')
|
||||
// console.log(delta, oldDelta, source)
|
||||
// console.groupEnd()
|
||||
},
|
||||
selectionChangeHandler (range, oldRange, source) {
|
||||
// console.group('QuillEditor selectionChangeHandler')
|
||||
// console.log(range, oldRange, source)
|
||||
// console.groupEnd()
|
||||
},
|
||||
editorChangeHandler (eventName, ...args) {
|
||||
// console.group('QuillEditor editorChangeHandler')
|
||||
// console.log(eventName, args)
|
||||
// console.groupEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
export default `<p>
|
||||
<a href="https://github.com/d2-projects/d2-admin" target="_blank" class="ql-size-large" style="color: rgb(65, 184, 131);"><strong>D2 Admin</strong></a>
|
||||
<strong style="color: rgb(65, 184, 131);"></strong>
|
||||
by
|
||||
<a href="https://cn.vuejs.org/" target="_blank" class="ql-size-large" style="color: rgb(65, 184, 131);"><strong>vue.js</strong></a>
|
||||
</p>`
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="14">
|
||||
<d2-ueditor v-model="text"/>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-card v-if="text" shadow="never" style="border: 1px solid #d4d4d4;">
|
||||
<template slot="header">Result</template>
|
||||
<div v-html="text" style="margin: -20px 0px;"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template slot="footer">
|
||||
<el-button type="primary" @click="text += text">
|
||||
<d2-icon name="copy"/> 当前内容 x2
|
||||
</el-button>
|
||||
<el-button type="danger" @click="text = ''">
|
||||
<d2-icon name="trash-o"/> 清空
|
||||
</el-button>
|
||||
</template>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
text: '<p>Hello World</p>'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,8 +0,0 @@
|
||||
export default `body {
|
||||
background-color: aliceblue;
|
||||
height: 100%;
|
||||
}
|
||||
.my-card {
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
}`
|
||||
@@ -1,8 +0,0 @@
|
||||
export default `<ul>
|
||||
<li class="li-1"><p>Hello</p></li>
|
||||
<li>
|
||||
<span style="color: red;">
|
||||
Hello
|
||||
</span>
|
||||
</li>
|
||||
</ul>`
|
||||
@@ -1,3 +0,0 @@
|
||||
export default `[].forEach.call($$("*"), a => {
|
||||
a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)
|
||||
})`
|
||||
@@ -1,8 +0,0 @@
|
||||
export default `body {
|
||||
background-color: aliceblue;
|
||||
height: 100%;
|
||||
.my-card {
|
||||
height: 300px;
|
||||
width: 300px;
|
||||
}
|
||||
}`
|
||||
@@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">代码高亮组件</template>
|
||||
<el-card shadow="never" class="d2-mb">
|
||||
<p slot="title">javascript</p>
|
||||
<d2-highlight :code="codeJavascript"/>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="d2-mb">
|
||||
<p slot="title">css</p>
|
||||
<d2-highlight :code="codeCSS"/>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="d2-mb">
|
||||
<p slot="title">scss</p>
|
||||
<d2-highlight :code="codeSCSS"/>
|
||||
</el-card>
|
||||
<el-card shadow="never">
|
||||
<p slot="title">html</p>
|
||||
<d2-highlight :code="codeHTML"/>
|
||||
</el-card>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import codeJavascript from './code/javascript'
|
||||
import codeCSS from './code/css'
|
||||
import codeSCSS from './code/scss'
|
||||
import codeHTML from './code/html'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
codeJavascript,
|
||||
codeCSS,
|
||||
codeSCSS,
|
||||
codeHTML
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-popover
|
||||
ref="pop"
|
||||
placement="right"
|
||||
:title="icon"
|
||||
width="300"
|
||||
trigger="click">
|
||||
<div class="icon-group">
|
||||
<i :class="'fa fa-' + icon"></i>
|
||||
</div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8">
|
||||
<el-tooltip effect="dark" :content="iconClass" placement="top">
|
||||
<el-button @click="copy(iconClass)" style="width: 100%;">Class</el-button>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tooltip effect="dark" :content="iconHtml" placement="top">
|
||||
<el-button @click="copy(iconHtml)" style="width: 100%;">HTML</el-button>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-tooltip effect="dark" :content="iconComponent" placement="top">
|
||||
<el-button @click="copy(iconComponent)" style="width: 100%;">组件</el-button>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
<span v-popover:pop>
|
||||
<el-tag type="info" class="icon-tag">
|
||||
<i :class="'fa fa-' + icon"></i>
|
||||
</el-tag>
|
||||
<span style="font-size: 10px;">{{icon}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as clipboard from 'clipboard-polyfill'
|
||||
export default {
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconClass () {
|
||||
return `fa fa-${this.icon}`
|
||||
},
|
||||
iconHtml () {
|
||||
return `<i class="fa fa-${this.icon}" aria-hidden="true"></i>`
|
||||
},
|
||||
iconComponent () {
|
||||
return `<d2-icon name="${this.icon}"/>`
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copy (text) {
|
||||
clipboard.writeText(text)
|
||||
this.$message({
|
||||
message: `${text} 复制到剪贴板`,
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-group {
|
||||
text-align: center;
|
||||
font-size: 200px;
|
||||
}
|
||||
.icon-tag {
|
||||
width: 32px;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">SVG图标组件</template>
|
||||
<el-row>
|
||||
<el-col class="icon-card" :span="4" v-for="(icon, index) in $IconSvg" :key="index">
|
||||
<d2-icon-svg class="icon" :name="icon"/>
|
||||
<div class="icon-title">
|
||||
<span>{{icon}}</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-card {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 150px;
|
||||
&:hover {
|
||||
.icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.icon-title {
|
||||
color: $color-text-main;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
transition: all .3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon-title {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
color: $color-text-normal;
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">图标组件</template>
|
||||
<d2-icon class="d2-mr-10"/>
|
||||
<d2-icon name="github" class="d2-mr-10"/>
|
||||
<d2-icon name="github" style="font-size: 100px;" class="d2-mr-10"/>
|
||||
<d2-icon name="github" class="icon-demo"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-demo {
|
||||
transition: all .3s;
|
||||
font-size: 100px;
|
||||
color: #409EFF;
|
||||
@extend %unable-select;
|
||||
&:hover{
|
||||
color: #F56C6C;
|
||||
transform: scale(1.2) rotate(30deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,46 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-radio-group v-model="showIndex" size="mini">
|
||||
<el-radio-button
|
||||
v-for="(item, index) in radioOptions"
|
||||
:key="index"
|
||||
:label="item.value">
|
||||
{{item.label}}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
<el-row style="margin: -10px;">
|
||||
<el-alert title="点击图标复制代码" type="info" class="d2-m-10" style="width: auto;"/>
|
||||
<el-col v-for="(iconItem, iconIndex) in iconShow.icon" :key="iconIndex" :span="6" class="d2-p-10">
|
||||
<d2-icon-cell :icon="iconItem"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import icon from './data/index'
|
||||
export default {
|
||||
components: {
|
||||
'd2-icon-cell': () => import('./components/d2-icon-cell')
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icon,
|
||||
showIndex: 12
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconShow () {
|
||||
return this.icon[this.showIndex]
|
||||
},
|
||||
radioOptions () {
|
||||
return this.icon.map((e, index) => ({
|
||||
label: e.title,
|
||||
value: index
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">svg 图标选择器</template>
|
||||
<div class="d2-mb">
|
||||
<p class="d2-mt-0 d2-mb-10">一般用法 | {{icon || '未选择'}}</p>
|
||||
<d2-icon-svg-select v-model="icon"/>
|
||||
</div>
|
||||
<div class="d2-mb">
|
||||
<p class="d2-mt-0 d2-mb-10">用户可以输入 | {{icon2 || '未选择'}}</p>
|
||||
<d2-icon-svg-select v-model="icon2" :user-input="true"/>
|
||||
</div>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icon: '',
|
||||
icon2: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">图标选择器</template>
|
||||
<div class="title-group">
|
||||
<p class="title">一般用法</p>
|
||||
<p class="sub-title">
|
||||
<template v-if="icon">
|
||||
选择的图标 {{icon}}
|
||||
<i :class="'fa fa-' + icon"></i>
|
||||
</template>
|
||||
<template v-else>未选择</template>
|
||||
</p>
|
||||
</div>
|
||||
<d2-icon-select v-model="icon"/>
|
||||
<div class="title-group">
|
||||
<p class="title">用户可以输入</p>
|
||||
<p class="sub-title">
|
||||
<template v-if="icon2">
|
||||
选择的图标 {{icon2}}
|
||||
<i :class="'fa fa-' + icon2"></i>
|
||||
</template>
|
||||
<template v-else>未选择</template>
|
||||
</p>
|
||||
</div>
|
||||
<d2-icon-select v-model="icon2" :user-input="true"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icon: '',
|
||||
icon2: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title-group {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
&:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.title {
|
||||
margin: 0px;
|
||||
}
|
||||
.sub-title {
|
||||
margin: 0px;
|
||||
color: $color-text-sub;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="ghost">
|
||||
<d2-module-index-banner slot="header" v-bind="banner"/>
|
||||
<d2-module-index-menu :menu="menu"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import menu from '@/menu/modules/demo-components'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
menu,
|
||||
banner: {
|
||||
title: 'COMPONENTS',
|
||||
subTitle: 'D2Admin 为你提供了一些上手即用的组件'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<tree-view :data="packJson" :options="options"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import vueJsonTreeView from 'vue-json-tree-view'
|
||||
import packJson from '../../../../../package.json'
|
||||
Vue.use(vueJsonTreeView)
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
options: {
|
||||
maxDepth: 10,
|
||||
rootObjectKey: 'package.json',
|
||||
modifiable: false
|
||||
},
|
||||
packJson
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,122 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="full" class="page">
|
||||
<d2-grid-layout
|
||||
v-bind="layout"
|
||||
@layout-updated="layoutUpdatedHandler">
|
||||
<d2-grid-item
|
||||
v-for="(item, index) in layout.layout"
|
||||
:key="index"
|
||||
v-bind="item"
|
||||
@resize="resizeHandler"
|
||||
@move="moveHandler"
|
||||
@resized="resizedHandler"
|
||||
@moved="movedHandler">
|
||||
<el-card shadow="never" class="page_card">
|
||||
<el-tag size="mini" type="info" slot="header">Card {{item.i}}</el-tag>
|
||||
<template v-if="item.i === '0'">
|
||||
<div class="d2-mb">拖拽卡片调整位置</div>
|
||||
<div class="d2-mb">拖拽卡片右下角的手柄调整卡片大小</div>
|
||||
<div class="d2-mb">在控制台打印出数据变化</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</d2-grid-item>
|
||||
</d2-grid-layout>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { GridLayout, GridItem } from 'vue-grid-layout'
|
||||
Vue.component('d2-grid-layout', GridLayout)
|
||||
Vue.component('d2-grid-item', GridItem)
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
layout: {
|
||||
layout: [
|
||||
{ x: 0, y: 0, w: 4, h: 10, i: '0' },
|
||||
{ x: 4, y: 0, w: 2, h: 5, i: '1' },
|
||||
{ x: 6, y: 0, w: 4, h: 5, i: '2' },
|
||||
{ x: 10, y: 0, w: 2, h: 10, i: '3' },
|
||||
{ x: 4, y: 5, w: 4, h: 5, i: '4' },
|
||||
{ x: 8, y: 5, w: 2, h: 5, i: '5' },
|
||||
{ x: 0, y: 10, w: 8, h: 5, i: '6' },
|
||||
{ x: 8, y: 10, w: 4, h: 5, i: '7' }
|
||||
],
|
||||
colNum: 12,
|
||||
rowHeight: 30,
|
||||
isDraggable: true,
|
||||
isResizable: true,
|
||||
isMirrored: false,
|
||||
verticalCompact: true,
|
||||
margin: [10, 10],
|
||||
useCssTransforms: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 加载完成后显示提示
|
||||
this.showInfo()
|
||||
},
|
||||
methods: {
|
||||
log (arg1 = 'log', ...logs) {
|
||||
if (logs.length === 0) {
|
||||
console.log(arg1)
|
||||
} else {
|
||||
console.group(arg1)
|
||||
logs.forEach(e => {
|
||||
console.log(e)
|
||||
})
|
||||
console.groupEnd()
|
||||
}
|
||||
},
|
||||
// 显示提示
|
||||
showInfo () {
|
||||
this.$notify({
|
||||
title: '提示',
|
||||
message: '你可以按住卡片拖拽改变位置;或者在每个卡片的右下角拖动,调整卡片大小'
|
||||
})
|
||||
},
|
||||
// 测试代码
|
||||
layoutUpdatedHandler (newLayout) {
|
||||
console.group('layoutUpdatedHandler')
|
||||
newLayout.forEach(e => {
|
||||
console.log(`{'x': ${e.x}, 'y': ${e.y}, 'w': ${e.w}, 'h': ${e.h}, 'i': '${e.i}'},`)
|
||||
})
|
||||
console.groupEnd()
|
||||
},
|
||||
resizeHandler (i, newH, newW) {
|
||||
this.log('resizeHandler', `i: ${i}, newH: ${newH}, newW: ${newW}`)
|
||||
},
|
||||
moveHandler (i, newX, newY) {
|
||||
this.log('moveHandler', `i: ${i}, newX: ${newX}, newY: ${newY}`)
|
||||
},
|
||||
resizedHandler (i, newH, newW, newHPx, newWPx) {
|
||||
this.log('resizedHandler', `i: ${i}, newH: ${newH}, newW: ${newW}, newHPx: ${newHPx}, newWPx: ${newWPx}`)
|
||||
},
|
||||
movedHandler (i, newX, newY) {
|
||||
this.log('movedHandler', `i: ${i}, newX: ${newX}, newY: ${newY}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
.vue-grid-layout {
|
||||
background-color: $color-bg;
|
||||
border-radius: 4px;
|
||||
margin: -10px;
|
||||
.page_card {
|
||||
height: 100%;
|
||||
@extend %unable-select;
|
||||
}
|
||||
.vue-resizable-handle {
|
||||
opacity: .3;
|
||||
&:hover{
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<template slot="header">区域划分</template>
|
||||
<div style="height: 400px; margin: -16px;">
|
||||
<SplitPane :min-percent='20' :default-percent='30' split="vertical">
|
||||
<template slot="paneL"><div style="margin: 10px;">左</div></template>
|
||||
<template slot="paneR">
|
||||
<SplitPane split="horizontal">
|
||||
<template slot="paneL"><div style="margin: 10px;">右上</div></template>
|
||||
<template slot="paneR"><div style="margin: 10px;">右下</div></template>
|
||||
</SplitPane>
|
||||
</template>
|
||||
</SplitPane>
|
||||
</div>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import SplitPane from 'vue-splitpane'
|
||||
Vue.component('SplitPane', SplitPane)
|
||||
export default {
|
||||
mounted () {
|
||||
// 加载完成后显示提示
|
||||
this.showInfo()
|
||||
},
|
||||
methods: {
|
||||
// 显示提示
|
||||
showInfo () {
|
||||
this.$notify({
|
||||
title: '提示',
|
||||
message: '在横向或者纵向的分割线上拖拽调整分区大小'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,38 +0,0 @@
|
||||
# 一级标题
|
||||
|
||||
| ID | Name | Email |
|
||||
| --- | --- | --- |
|
||||
| 0001 | FairyEver | 1711467488@qq.com |
|
||||
|
||||
```
|
||||
// 注释
|
||||
[].forEach.call($$("*"), a => {
|
||||
a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)
|
||||
})
|
||||
```
|
||||
|
||||
```
|
||||
<div>
|
||||
<p>Hello World</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
```
|
||||
body {
|
||||
background-color: #333;
|
||||
}
|
||||
```
|
||||
|
||||
一般引用
|
||||
|
||||
> 引用文字
|
||||
|
||||
分享一个我很早前的一副设计作品 [in Lofter](http://fairyever.lofter.com/post/16ff00_6796fe8) 借此演示百度云链接的显示优化
|
||||
|
||||
> https://pan.baidu.com/s/1kW6uUwB
|
||||
|
||||
设计源文件
|
||||
|
||||
> 链接: https://pan.baidu.com/s/1ggFW21l 密码: 877y
|
||||
|
||||
[https://github.com/d2-projects](https://github.com/d2-projects)
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">指定资源</template>
|
||||
<d2-markdown :source="doc" highlight/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import doc from './md/doc.md'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
doc
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">异步加载文件</template>
|
||||
<d2-markdown url="markdown/demo.md"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<p>src/views/demo/playground/add-routes/alternates/1.vue</p>
|
||||
</d2-container>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<p>src/views/demo/playground/add-routes/alternates/2.vue</p>
|
||||
</d2-container>
|
||||
</template>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<p>src/views/demo/playground/add-routes/alternates/3.vue</p>
|
||||
</d2-container>
|
||||
</template>
|
||||
@@ -1,109 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<el-alert
|
||||
title="由于演示功能特殊,请注意在需要时刷新您的浏览器(以重置路由设置)查看效果"
|
||||
type="warning"
|
||||
show-icon/>
|
||||
<d2-highlight :code="dataView"/>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="创建路由(你可以假设上面是接口数据)">
|
||||
<el-button-group>
|
||||
<el-button
|
||||
v-for="item in setting"
|
||||
:key="item.component"
|
||||
@click="onClick(item)">
|
||||
{{item.title}}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import { frameInRoutes } from '@/router/routes'
|
||||
import layoutHeaderAside from '@/layout/header-aside'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
title: '',
|
||||
setting: [
|
||||
{ title: '追加页面 1', name: 'add-routes-1', path: 'add-routes/1', component: '1' },
|
||||
{ title: '追加页面 2', name: 'add-routes-2', path: 'add-routes/2', component: '2' },
|
||||
{ title: '追加页面 3', name: 'add-routes-3', path: 'add-routes/3', component: '3' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('d2admin/menu', [
|
||||
'header'
|
||||
]),
|
||||
dataView () {
|
||||
return JSON.stringify(this.setting, null, 2)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
pageInit: 'd2admin/page/init',
|
||||
headerSet: 'd2admin/menu/headerSet'
|
||||
}),
|
||||
onClick ({ title, name, path, component }) {
|
||||
// vue router 的设计暂时不能支持在路由示例上访问动态添加的路由
|
||||
// 目前可行的解决方法是自行维护一个存储路由数据的位置
|
||||
// https://github.com/vuejs/vue-router/issues/1234
|
||||
// https://github.com/vuejs/vue-router/issues/1859
|
||||
// https://github.com/vuejs/vue-router/issues/1955
|
||||
// https://github.com/vuejs/vue-router/issues/2454
|
||||
// https://github.com/vuejs/vue-router/issues/2280
|
||||
// 所以暂时先不做对路由已经存在的判断
|
||||
const route = [
|
||||
{
|
||||
path: '/demo/playground',
|
||||
component: layoutHeaderAside,
|
||||
children: [
|
||||
{
|
||||
path,
|
||||
name,
|
||||
component: () => import('@/views/demo/playground/add-routes/alternates/' + component + '.vue'),
|
||||
meta: {
|
||||
title
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
this.$router.addRoutes(route)
|
||||
// 更新标签页池
|
||||
this.pageInit([
|
||||
...frameInRoutes,
|
||||
...route
|
||||
])
|
||||
// 演示更新菜单
|
||||
const menuGroup = {
|
||||
title: '临时菜单',
|
||||
icon: 'plus-square',
|
||||
children: []
|
||||
}
|
||||
const menu = {
|
||||
path: `/demo/playground/${path}`,
|
||||
title,
|
||||
icon: 'file-o'
|
||||
}
|
||||
const header = cloneDeep(this.header)
|
||||
const menuGroupIndex = header.findIndex(e => e.title === menuGroup.title)
|
||||
// 如果顶栏菜单已经有这个组,就在组里添加项目,反之新建一个菜单组
|
||||
if (menuGroupIndex >= 0) {
|
||||
header[menuGroupIndex].children.push(menu)
|
||||
} else {
|
||||
menuGroup.children.push(menu)
|
||||
header.push(menuGroup)
|
||||
}
|
||||
this.headerSet(header)
|
||||
// 跳转
|
||||
this.$router.push({ name })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
title="这个页面展示的是全部数据的存储结构,包括系统区域和存储区域,涵盖所有用户,也就是整个 D2Admin 的数据存储结构"/>
|
||||
</template>
|
||||
<d2-highlight :code="dbData"/>
|
||||
<el-button slot="footer" type="primary" @click="load">
|
||||
重新获取本地数据
|
||||
</el-button>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import util from '@/libs/util'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
dbData: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
load () {
|
||||
this.dbData = JSON.stringify(util.db.value(), null, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,127 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
title="路由存储指当前路由的存储区域,
|
||||
不同路由之间存储不会相互干扰,
|
||||
使用 await this.$store.dispatch('d2admin/db/databasePage') 获得存储实例进行操作,
|
||||
不同路由条件下获取的存储实例指向位置不同,
|
||||
可以指定路由区分依据 name | path | fullPath,
|
||||
默认根据路由的 name 区分不同的路由"/>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p class="d2-mt-0">增加不重复字段</p>
|
||||
<el-button @click="handleSetRandom">增加</el-button>
|
||||
<p>增加自定义字段</p>
|
||||
<el-input v-model="keyNameToSet" placeholder="字段名" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-input v-model="valueToSet" placeholder="值" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-button @click="handleSet">增加</el-button>
|
||||
<p>删除字段</p>
|
||||
<el-select
|
||||
v-model="keyNameToDelete"
|
||||
placeholder="请选择要删除的 key">
|
||||
<el-option
|
||||
v-for="item in keyNameList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<p>清空当前用户数据</p>
|
||||
<el-button @click="handleClear">清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<d2-highlight :code="dataDisplay"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uniqueId } from 'lodash'
|
||||
import { mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
dataDisplay: '',
|
||||
keyNameToSet: '',
|
||||
valueToSet: '',
|
||||
keyNameList: [],
|
||||
keyNameToDelete: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyNameToDelete (value) {
|
||||
if (value) {
|
||||
this.handleDelete(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('d2admin/db', [
|
||||
'databasePage',
|
||||
'databasePageClear'
|
||||
]),
|
||||
/**
|
||||
* 加载本地数据
|
||||
*/
|
||||
async load () {
|
||||
const db = await this.databasePage()
|
||||
this.dataDisplay = JSON.stringify(db.value(), null, 2)
|
||||
this.keyNameList = Object.keys(db.value()).map(k => ({
|
||||
value: k,
|
||||
label: k
|
||||
}))
|
||||
},
|
||||
/**
|
||||
* 删除一个字段
|
||||
*/
|
||||
async handleDelete (name) {
|
||||
const db = await this.databasePage()
|
||||
db
|
||||
.unset(name)
|
||||
.write()
|
||||
this.load()
|
||||
this.keyNameToDelete = ''
|
||||
},
|
||||
/**
|
||||
* 清空当前用户的数据
|
||||
*/
|
||||
async handleClear () {
|
||||
await this.databasePageClear()
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个数据
|
||||
*/
|
||||
async handleSet () {
|
||||
if (this.keyNameToSet === '') {
|
||||
this.$message.error('字段名不能为空')
|
||||
return
|
||||
}
|
||||
const db = await this.databasePage()
|
||||
db
|
||||
.set(this.keyNameToSet, this.valueToSet)
|
||||
.write()
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个随机数据
|
||||
*/
|
||||
async handleSetRandom () {
|
||||
const id = uniqueId()
|
||||
const db = await this.databasePage()
|
||||
db
|
||||
.set(`uniqueKey${id}`, `value${id}`)
|
||||
.write()
|
||||
this.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,114 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
title="路由快照相当于路由存储一种快捷操作,
|
||||
会将传入的 vue instance 携带的 $data 全部持久化,
|
||||
下面的表单来自 Element 的表单示例,
|
||||
在 D2Admin 的本页示例中你可以随意填写这个表单,
|
||||
表单内容会自动实时持久化,
|
||||
无论是切换标签页、重新打开标签页、刷新浏览器、重开浏览器、重开浏览器标签页等,
|
||||
该页面数据都会自动恢复到上次填写的状态,
|
||||
这些都只需要你使用 D2Admin 提供的两个方法,
|
||||
总共只需要多写十几行代码"/>
|
||||
</template>
|
||||
<el-form ref="form" :model="form" label-width="80px" style="max-width: 600px; margin: 0px auto;">
|
||||
<el-form-item label="活动名称">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动区域">
|
||||
<el-select v-model="form.region" placeholder="请选择活动区域">
|
||||
<el-option label="区域一" value="shanghai"></el-option>
|
||||
<el-option label="区域二" value="beijing"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动时间">
|
||||
<el-col :span="11">
|
||||
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"></el-date-picker>
|
||||
</el-col>
|
||||
<el-col :span="2" style="text-align: center;">-</el-col>
|
||||
<el-col :span="11">
|
||||
<el-time-picker type="fixed-time" placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item label="即时配送">
|
||||
<el-switch v-model="form.delivery"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动性质">
|
||||
<el-checkbox-group v-model="form.type">
|
||||
<el-checkbox label="线上活动" name="type"></el-checkbox>
|
||||
<el-checkbox label="地推活动" name="type"></el-checkbox>
|
||||
<el-checkbox label="线下活动" name="type"></el-checkbox>
|
||||
<el-checkbox label="品牌曝光" name="type"></el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="特殊资源">
|
||||
<el-radio-group v-model="form.resource">
|
||||
<el-radio label="线上品牌商赞助"></el-radio>
|
||||
<el-radio label="线下场地免费"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动形式">
|
||||
<el-input type="textarea" v-model="form.desc"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary">立即创建</el-button>
|
||||
<el-button>取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button
|
||||
slot="footer"
|
||||
type="danger"
|
||||
@click="handleClear">
|
||||
<d2-icon name="trash-o"/>
|
||||
删除当前页面快照
|
||||
</el-button>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
name: '',
|
||||
region: '',
|
||||
date1: '',
|
||||
date2: '',
|
||||
delivery: false,
|
||||
type: [],
|
||||
resource: '',
|
||||
desc: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
const data = await this.pageGet({ instance: this })
|
||||
for (const key in data) {
|
||||
this[key] = data[key]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$data: {
|
||||
handler () {
|
||||
this.pageSet({ instance: this })
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('d2admin/db', [
|
||||
'pageSet',
|
||||
'pageGet',
|
||||
'pageClear'
|
||||
]),
|
||||
async handleClear () {
|
||||
await this.pageClear({ instance: this })
|
||||
this.$message.success('此页面快照已经删除,请重新进入该页面或者关闭选项卡重新打开')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,114 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
title="私有路由快照相当于私有路由存储一种快捷操作,
|
||||
会将传入的 vue instance 携带的 $data 全部根据用户区分持久化,
|
||||
下面的表单来自 Element 的表单示例,
|
||||
在 D2Admin 的本页示例中你可以随意填写这个表单,
|
||||
表单内容会自动实时持久化,
|
||||
无论是切换标签页、重新打开标签页、刷新浏览器、重开浏览器、重开浏览器标签页等,
|
||||
该页面数据都会自动恢复到上次填写的状态,
|
||||
这些都只需要你使用 D2Admin 提供的两个方法,
|
||||
总共只需要多写十几行代码"/>
|
||||
</template>
|
||||
<el-form ref="form" :model="form" label-width="80px" style="max-width: 600px; margin: 0px auto;">
|
||||
<el-form-item label="活动名称">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动区域">
|
||||
<el-select v-model="form.region" placeholder="请选择活动区域">
|
||||
<el-option label="区域一" value="shanghai"></el-option>
|
||||
<el-option label="区域二" value="beijing"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动时间">
|
||||
<el-col :span="11">
|
||||
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 100%;"></el-date-picker>
|
||||
</el-col>
|
||||
<el-col :span="2" style="text-align: center;">-</el-col>
|
||||
<el-col :span="11">
|
||||
<el-time-picker type="fixed-time" placeholder="选择时间" v-model="form.date2" style="width: 100%;"></el-time-picker>
|
||||
</el-col>
|
||||
</el-form-item>
|
||||
<el-form-item label="即时配送">
|
||||
<el-switch v-model="form.delivery"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动性质">
|
||||
<el-checkbox-group v-model="form.type">
|
||||
<el-checkbox label="线上活动" name="type"></el-checkbox>
|
||||
<el-checkbox label="地推活动" name="type"></el-checkbox>
|
||||
<el-checkbox label="线下活动" name="type"></el-checkbox>
|
||||
<el-checkbox label="品牌曝光" name="type"></el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="特殊资源">
|
||||
<el-radio-group v-model="form.resource">
|
||||
<el-radio label="线上品牌商赞助"></el-radio>
|
||||
<el-radio label="线下场地免费"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="活动形式">
|
||||
<el-input type="textarea" v-model="form.desc"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary">立即创建</el-button>
|
||||
<el-button>取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button
|
||||
slot="footer"
|
||||
type="danger"
|
||||
@click="handleClear">
|
||||
<d2-icon name="trash-o"/>
|
||||
删除当前页面快照
|
||||
</el-button>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
name: '',
|
||||
region: '',
|
||||
date1: '',
|
||||
date2: '',
|
||||
delivery: false,
|
||||
type: [],
|
||||
resource: '',
|
||||
desc: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
const data = await this.pageGet({ instance: this, user: true })
|
||||
for (const key in data) {
|
||||
this[key] = data[key]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$data: {
|
||||
handler () {
|
||||
this.pageSet({ instance: this, user: true })
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('d2admin/db', [
|
||||
'pageSet',
|
||||
'pageGet',
|
||||
'pageClear'
|
||||
]),
|
||||
async handleClear () {
|
||||
await this.pageClear({ instance: this, user: true })
|
||||
this.$message.success('此页面快照已经删除,请重新进入该页面或者关闭选项卡重新打开')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,139 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
title="私有路由存储指当前路由的存储区域,
|
||||
并且同时还根据用户区分,
|
||||
相当于结合了 “路由存储” 和 “私有存储”,
|
||||
不同路由以及不同用户之间存储不会相互干扰,
|
||||
使用 await this.$store.dispatch('d2admin/db/databasePage', { user: true }) 获得存储实例进行操作,
|
||||
不同路由和用户条件下获取的存储实例指向位置不同,
|
||||
可以指定路由区分依据 name | path | fullPath,
|
||||
默认根据路由的 name 区分不同的路由"/>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p class="d2-mt-0">增加不重复字段</p>
|
||||
<el-button @click="handleSetRandom">增加</el-button>
|
||||
<p>增加自定义字段</p>
|
||||
<el-input v-model="keyNameToSet" placeholder="字段名" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-input v-model="valueToSet" placeholder="值" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-button @click="handleSet">增加</el-button>
|
||||
<p>删除字段</p>
|
||||
<el-select
|
||||
v-model="keyNameToDelete"
|
||||
placeholder="请选择要删除的 key">
|
||||
<el-option
|
||||
v-for="item in keyNameList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<p>清空当前用户数据</p>
|
||||
<el-button @click="handleClear">清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<d2-highlight :code="dataDisplay"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uniqueId } from 'lodash'
|
||||
import { mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
dataDisplay: '',
|
||||
keyNameToSet: '',
|
||||
valueToSet: '',
|
||||
keyNameList: [],
|
||||
keyNameToDelete: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyNameToDelete (value) {
|
||||
if (value) {
|
||||
this.handleDelete(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('d2admin/db', [
|
||||
'databasePage',
|
||||
'databasePageClear'
|
||||
]),
|
||||
/**
|
||||
* 加载本地数据
|
||||
*/
|
||||
async load () {
|
||||
const db = await this.databasePage({
|
||||
user: true
|
||||
})
|
||||
this.dataDisplay = JSON.stringify(db.value(), null, 2)
|
||||
this.keyNameList = Object.keys(db.value()).map(k => ({
|
||||
value: k,
|
||||
label: k
|
||||
}))
|
||||
},
|
||||
/**
|
||||
* 删除一个字段
|
||||
*/
|
||||
async handleDelete (name) {
|
||||
const db = await this.databasePage({
|
||||
user: true
|
||||
})
|
||||
db
|
||||
.unset(name)
|
||||
.write()
|
||||
this.load()
|
||||
this.keyNameToDelete = ''
|
||||
},
|
||||
/**
|
||||
* 清空当前用户的数据
|
||||
*/
|
||||
async handleClear () {
|
||||
await this.databasePageClear({
|
||||
user: true
|
||||
})
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个数据
|
||||
*/
|
||||
async handleSet () {
|
||||
if (this.keyNameToSet === '') {
|
||||
this.$message.error('字段名不能为空')
|
||||
return
|
||||
}
|
||||
const db = await this.databasePage({
|
||||
user: true
|
||||
})
|
||||
db
|
||||
.set(this.keyNameToSet, this.valueToSet)
|
||||
.write()
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个随机数据
|
||||
*/
|
||||
async handleSetRandom () {
|
||||
const id = uniqueId()
|
||||
const db = await this.databasePage({
|
||||
user: true
|
||||
})
|
||||
db
|
||||
.set(`uniqueKey${id}`, `value${id}`)
|
||||
.write()
|
||||
this.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,123 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
title="公用存储指所有用户共用的存储区域,
|
||||
使用 await this.$store.dispatch('d2admin/db/database') 获得存储实例进行操作"/>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p class="d2-mt-0">增加不重复字段</p>
|
||||
<el-button @click="handleSetRandom">增加</el-button>
|
||||
<p>增加自定义字段</p>
|
||||
<el-input v-model="keyNameToSet" placeholder="字段名" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-input v-model="valueToSet" placeholder="值" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-button @click="handleSet">增加</el-button>
|
||||
<p>删除字段</p>
|
||||
<el-select
|
||||
v-model="keyNameToDelete"
|
||||
placeholder="请选择要删除的 key">
|
||||
<el-option
|
||||
v-for="item in keyNameList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<p>清空当前用户数据</p>
|
||||
<el-button @click="handleClear">清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<d2-highlight :code="dataDisplay"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uniqueId } from 'lodash'
|
||||
import { mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
dataDisplay: '',
|
||||
keyNameToSet: '',
|
||||
valueToSet: '',
|
||||
keyNameList: [],
|
||||
keyNameToDelete: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyNameToDelete (value) {
|
||||
if (value) {
|
||||
this.handleDelete(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('d2admin/db', [
|
||||
'database',
|
||||
'databaseClear'
|
||||
]),
|
||||
/**
|
||||
* 加载本地数据
|
||||
*/
|
||||
async load () {
|
||||
const db = await this.database()
|
||||
this.dataDisplay = JSON.stringify(db.value(), null, 2)
|
||||
this.keyNameList = Object.keys(db.value()).map(k => ({
|
||||
value: k,
|
||||
label: k
|
||||
}))
|
||||
},
|
||||
/**
|
||||
* 删除一个字段
|
||||
*/
|
||||
async handleDelete (name) {
|
||||
const db = await this.database()
|
||||
db
|
||||
.unset(name)
|
||||
.write()
|
||||
this.load()
|
||||
this.keyNameToDelete = ''
|
||||
},
|
||||
/**
|
||||
* 清空当前用户的数据
|
||||
*/
|
||||
async handleClear () {
|
||||
await this.databaseClear()
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个数据
|
||||
*/
|
||||
async handleSet () {
|
||||
if (this.keyNameToSet === '') {
|
||||
this.$message.error('字段名不能为空')
|
||||
return
|
||||
}
|
||||
const db = await this.database()
|
||||
db
|
||||
.set(this.keyNameToSet, this.valueToSet)
|
||||
.write()
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个随机数据
|
||||
*/
|
||||
async handleSetRandom () {
|
||||
const id = uniqueId()
|
||||
const db = await this.database()
|
||||
db
|
||||
.set(`uniqueKey${id}`, `value${id}`)
|
||||
.write()
|
||||
this.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,125 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
title="私有存储指当前用户专用的存储区域,
|
||||
不同用户之间存储不会相互干扰,
|
||||
使用 await this.$store.dispatch('d2admin/db/database', { user: true }) 获得存储实例进行操作,
|
||||
不同用户条件下获取的存储实例指向位置不同"/>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<p class="d2-mt-0">增加不重复字段</p>
|
||||
<el-button @click="handleSetRandom">增加</el-button>
|
||||
<p>增加自定义字段</p>
|
||||
<el-input v-model="keyNameToSet" placeholder="字段名" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-input v-model="valueToSet" placeholder="值" class="d2-mr-5" style="width: 100px;"/>
|
||||
<el-button @click="handleSet">增加</el-button>
|
||||
<p>删除字段</p>
|
||||
<el-select
|
||||
v-model="keyNameToDelete"
|
||||
placeholder="请选择要删除的 key">
|
||||
<el-option
|
||||
v-for="item in keyNameList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<p>清空当前用户数据</p>
|
||||
<el-button @click="handleClear">清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<d2-highlight :code="dataDisplay"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uniqueId } from 'lodash'
|
||||
import { mapActions } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
dataDisplay: '',
|
||||
keyNameToSet: '',
|
||||
valueToSet: '',
|
||||
keyNameList: [],
|
||||
keyNameToDelete: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
keyNameToDelete (value) {
|
||||
if (value) {
|
||||
this.handleDelete(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
...mapActions('d2admin/db', [
|
||||
'database',
|
||||
'databaseClear'
|
||||
]),
|
||||
/**
|
||||
* 加载本地数据
|
||||
*/
|
||||
async load () {
|
||||
const db = await this.database({ user: true })
|
||||
this.dataDisplay = JSON.stringify(db.value(), null, 2)
|
||||
this.keyNameList = Object.keys(db.value()).map(k => ({
|
||||
value: k,
|
||||
label: k
|
||||
}))
|
||||
},
|
||||
/**
|
||||
* 删除一个字段
|
||||
*/
|
||||
async handleDelete (name) {
|
||||
const db = await this.database({ user: true })
|
||||
db
|
||||
.unset(name)
|
||||
.write()
|
||||
this.load()
|
||||
this.keyNameToDelete = ''
|
||||
},
|
||||
/**
|
||||
* 清空当前用户的数据
|
||||
*/
|
||||
async handleClear () {
|
||||
await this.databaseClear({ user: true })
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个数据
|
||||
*/
|
||||
async handleSet () {
|
||||
if (this.keyNameToSet === '') {
|
||||
this.$message.error('字段名不能为空')
|
||||
return
|
||||
}
|
||||
const db = await this.database({ user: true })
|
||||
db
|
||||
.set(this.keyNameToSet, this.valueToSet)
|
||||
.write()
|
||||
this.load()
|
||||
},
|
||||
/**
|
||||
* 添加一个随机数据
|
||||
*/
|
||||
async handleSetRandom () {
|
||||
const id = uniqueId()
|
||||
const db = await this.database({ user: true })
|
||||
db
|
||||
.set(`uniqueKey${id}`, `value${id}`)
|
||||
.write()
|
||||
this.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
16
src/views/demo/playground/env/index.vue
vendored
16
src/views/demo/playground/env/index.vue
vendored
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="card">
|
||||
<template slot="header">process.env</template>
|
||||
<d2-highlight :code="env"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
env: JSON.stringify(process.env, null, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<d2-container-frame src="https://d2.pub/doc/d2-admin"/>
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<d2-container-frame :src="`${$baseUrl}html/demo.html`"/>
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<d2-container-frame :src="`${$baseUrl}report.html`"/>
|
||||
</template>
|
||||
@@ -1,21 +0,0 @@
|
||||
<template>
|
||||
<d2-container type="ghost">
|
||||
<d2-module-index-banner slot="header" v-bind="banner"/>
|
||||
<d2-module-index-menu :menu="menu"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import menu from '@/menu/modules/demo-playground'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
menu,
|
||||
banner: {
|
||||
title: 'PLAYGROUND',
|
||||
subTitle: '在这里可以测试一些 D2Admin 的系统功能'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<p class="d2-mt-0">
|
||||
<el-radio-group v-model="$i18n.locale">
|
||||
<el-radio-button
|
||||
v-for="language in $languages"
|
||||
:key="language.value"
|
||||
:label="language.value">
|
||||
{{ language.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</p>
|
||||
<el-alert
|
||||
:title="$t('page.demo.playground.locales.text')"
|
||||
type="success">
|
||||
</el-alert>
|
||||
<d2-link-btn
|
||||
slot="footer"
|
||||
title="文档"
|
||||
link="https://d2.pub/doc/d2-admin/locales"/>
|
||||
</d2-container>
|
||||
</template>
|
||||
@@ -1,20 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">Ajax 错误</template>
|
||||
<p class="d2-mt-0">请打开浏览器控制台,然后点击下面的按钮,尝试访问一个不存在的网络地址</p>
|
||||
<el-button type="danger" @click="handleClick">请求错误的地址</el-button>
|
||||
<p>此错误已经被记录在日志页面,并在页面右上"日志按钮"区域显示提示信息</p>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DEMO_LOG_AJAX } from '@/api/demo.js'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
handleClick () {
|
||||
DEMO_LOG_AJAX()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB |
@@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<d2-container class="page">
|
||||
<p class="d2-mt-0">$log.capsule</p>
|
||||
<el-button size="small" type="primary" @click="$log.capsule('title', 'primary')">
|
||||
$log.capsule('title', 'primary')
|
||||
</el-button>
|
||||
<el-button size="small" type="success" @click="$log.capsule('title', 'success', 'success')">
|
||||
$log.capsule('title', 'success', 'success')
|
||||
</el-button>
|
||||
<el-button size="small" type="warning" @click="$log.capsule('title', 'warning', 'warning')">
|
||||
$log.capsule('title', 'warning', 'warning')
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="$log.capsule('title', 'danger', 'danger')">
|
||||
$log.capsule('title', 'danger', 'danger')
|
||||
</el-button>
|
||||
<p>$log.colorful</p>
|
||||
<el-button size="small" @click="handleColorful">
|
||||
colorful
|
||||
</el-button>
|
||||
<p>$log.default | primary | success | warning | danger</p>
|
||||
<el-button size="small" @click="$log.default('default style')">
|
||||
$log.default('default style')
|
||||
</el-button>
|
||||
<el-button size="small" type="primary" @click="$log.primary('primary style')">
|
||||
$log.primary('primary style')
|
||||
</el-button>
|
||||
<el-button size="small" type="success" @click="$log.success('success style')">
|
||||
$log.success('success style')
|
||||
</el-button>
|
||||
<el-button size="small" type="warning" @click="$log.warning('warning style')">
|
||||
$log.warning('warning style')
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="$log.danger('danger style')">
|
||||
$log.danger('danger style')
|
||||
</el-button>
|
||||
<p>效果 ( Chrome )</p>
|
||||
<img
|
||||
class="page__image-demo"
|
||||
src="./image/demo.png">
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
handleColorful () {
|
||||
this.$log.colorful([
|
||||
{ text: 'H', type: 'default' },
|
||||
{ text: 'e', type: 'primary' },
|
||||
{ text: 'l', type: 'success' },
|
||||
{ text: 'l', type: 'warning' },
|
||||
{ text: 'o', type: 'danger' }
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
.page__image-demo {
|
||||
border-radius: 4px;
|
||||
width: 260px;
|
||||
border: 1px solid $color-border-1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">捕获错误信息</template>
|
||||
<p class="d2-mt-0">请打开浏览器控制台,然后点击下面的按钮</p>
|
||||
<el-button type="danger" @click="handleNewError">触发一个错误</el-button>
|
||||
<p>此错误已经被记录在日志页面,并在页面右上"日志按钮"区域显示提示信息</p>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
handleNewError () {
|
||||
console.log(a) // eslint-disable-line
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,28 +0,0 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template slot="header">记录日志内容</template>
|
||||
<p class="d2-mt-0">在下方输入你要记录的日志,然后点击记录按钮</p>
|
||||
<el-input
|
||||
v-model="text"
|
||||
placeholder="日志内容"
|
||||
class="d2-mr-10"
|
||||
style="width: 200px;"/>
|
||||
<el-button type="primary" @click="handleAdd">记录</el-button>
|
||||
<p>此信息已经被记录在日志页面,并在页面右上"日志按钮"区域显示提示信息</p>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
text: 'some text'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAdd () {
|
||||
this.$log.push(this.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user