docs: Add Chinese documents
1. Add Chinese development documents 2. Add Chinese test document Change-Id: I29cd91f47069c9e64928800094282d4cbc977920
1
.gitignore
vendored
@ -3,7 +3,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
package-lock.json
|
package-lock.json
|
||||||
docs/
|
|
||||||
.vscode
|
.vscode
|
||||||
test/e2e/videos
|
test/e2e/videos
|
||||||
test/e2e/screenshots
|
test/e2e/screenshots
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
coverage
|
coverage
|
||||||
|
docs
|
||||||
|
94
docs/zh/develop/1-ready-to-work.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/1-ready-to-work.md)
|
||||||
|
|
||||||
|
# 开发前准备
|
||||||
|
|
||||||
|
- node 环境
|
||||||
|
- package.json 中要求:`"node": ">=10.22.0"`
|
||||||
|
- 验证 nodejs 版本
|
||||||
|
|
||||||
|
```shell
|
||||||
|
node -v
|
||||||
|
```
|
||||||
|
|
||||||
|
- yarn
|
||||||
|
- 安装 yarn
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install -g yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
- 安装依赖包
|
||||||
|
- 在项目根目录下执行,即`package.json`同级,需要耐心等待安装完成
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
- 准备好可用的后端
|
||||||
|
- 准备好可访问的后端,举个例子:https://172.20.154.250
|
||||||
|
- 修改`config/webpack.dev.js`中的相应配置:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (API === 'mock' || API === 'dev') {
|
||||||
|
devServer.proxy = {
|
||||||
|
'/api': {
|
||||||
|
target: 'https://172.20.154.250',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 配置访问的 host 与 port
|
||||||
|
- 修改`devServer.host`与`devServer.port`
|
||||||
|
- 修改`config/webpack.dev.js`中的相应配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const devServer = {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
// host: 'localhost',
|
||||||
|
port: 8088,
|
||||||
|
contentBase: root('dist'),
|
||||||
|
historyApiFallback: true,
|
||||||
|
compress: true,
|
||||||
|
hot: true,
|
||||||
|
inline: true,
|
||||||
|
disableHostCheck: true,
|
||||||
|
// progress: true
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- 搭建完成
|
||||||
|
- 在项目根目录下执行,即`package.json`同级
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
- 使用`config/webpack.dev.js`中配置的`host`与`port`访问即可,如`http://localhost:8088`
|
||||||
|
- 开发使用的前端实时更新环境搞定。
|
||||||
|
|
||||||
|
# 生产环境使用的前端包
|
||||||
|
|
||||||
|
- 具备符合要求的`nodejs`与`yarn`
|
||||||
|
- 在项目根目录下执行,即`package.json`同级
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run build
|
||||||
|
```
|
||||||
|
|
||||||
|
- 打包后的文件在`dist`目录,交给部署相关人员即可。
|
||||||
|
|
||||||
|
# 测试使用的前端包
|
||||||
|
|
||||||
|
- 具备符合要求的`nodejs`与`yarn`
|
||||||
|
- 在项目根目录下执行,即`package.json`同级
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run build:test
|
||||||
|
```
|
||||||
|
|
||||||
|
- 打包后的文件在`dist`目录
|
||||||
|
- 注意!!!这个测试包为了测出代码覆盖率的
|
||||||
|
- 建议使用 nginx,以完成带有代码覆盖率的 E2E 测试。
|
380
docs/zh/develop/2-catalog-introduction.md
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/2-catalog-introduction.md)
|
||||||
|
|
||||||
|
# 一级目录简介
|
||||||
|
|
||||||
|
- `Gruntfile.js`:用于收集 i18n
|
||||||
|
- `LICENSE`: 该项目使用 Apache License
|
||||||
|
- `Makefile`:
|
||||||
|
- `README.md`: 前端启动的简单说明,详细信息请参考 docs 文档
|
||||||
|
- `config`目录: webpack 配置,其内包含公用、开发环境、测试环境、生成环境下的 webpack 配置
|
||||||
|
- `cypress.json`: e2e 测试的配置文件
|
||||||
|
- `docker`: 内含开发环境、生成环境、测试环境使用的 docker 配置
|
||||||
|
- `docs`目录: 文档介绍,包含中文、英文、开发说明文档、测试说明文档,其中 en 文档暂缺失
|
||||||
|
- `jest.config.js`: 单元测试的配置文件
|
||||||
|
- `jsconfig.json`: js 代码的配置文件
|
||||||
|
- `package.json`: 安装包、命令等配置文件
|
||||||
|
- `yarn.lock`: 包的版本锁定文件
|
||||||
|
- `.babelrc`: bebel 配置文件
|
||||||
|
- `.dockerignore`: docker 忽略的文件配置
|
||||||
|
- `.eslintignore`: eslint 忽略的文件配置
|
||||||
|
- `.eslint`: eslint 配置
|
||||||
|
- `.gitignore`: git 忽悠的文件配置
|
||||||
|
- `.gitreview`: gitreview 配置
|
||||||
|
- `.prettierignore`: prettier 忽略的文件配置
|
||||||
|
- `.prettierrc`: prettier 的配置
|
||||||
|
- `src`目录: 开发代码所在文件夹!!!
|
||||||
|
- `test`目录: 测试代码所在文件夹!!!包含 e2e 测试代码及单元测试的基础代码
|
||||||
|
- `tools`目录: 其他工具文件夹,内含 git 工具
|
||||||
|
|
||||||
|
# src 目录介绍
|
||||||
|
|
||||||
|
- `src/components`目录:公共组件
|
||||||
|
- `src/api`目录:API,暂未使用
|
||||||
|
- `src/asset`目录:images, template 等静态文件
|
||||||
|
- `src/containers`目录:
|
||||||
|
- 带状态的组件
|
||||||
|
- 基础类
|
||||||
|
- [BaseList](3-1-BaseList-introduction.md)
|
||||||
|
- [BaseDetail](3-3-BaseDetail-introduction.md)
|
||||||
|
- [BaseForm](3-6-FormAction-introduction.md)
|
||||||
|
- [BaseModalAction](3-7-ModalAction-introduction.md)
|
||||||
|
- [BaseConfirmAction](3-8-ConfirmAction-introduction.md)
|
||||||
|
- [BaseStepAction](3-9-StepAction-introduction.md)
|
||||||
|
- `src/core`目录:
|
||||||
|
- `index.js`: 入口文件
|
||||||
|
- `routes.js`: 按模块的路由配置
|
||||||
|
- `i18n.js`
|
||||||
|
- `App.jsx`
|
||||||
|
- `src/layouts`目录:
|
||||||
|
- 定义所有整体页面布局的组件
|
||||||
|
- 空白布局 BlankLayout
|
||||||
|
- 登录页使用的布局 UserLayout
|
||||||
|
- 内容页使用的布局 BaseLayout(列表、详情、表单等使用)
|
||||||
|
- `menu.jsx`: 控制台使用的菜单配置
|
||||||
|
- `admin-menu.jsx`: 管理平台使用的菜单配置
|
||||||
|
- `src/locales`目录: i18n
|
||||||
|
- `src/resources`目录:
|
||||||
|
- 定义各资源被公用的状态 / 搜索项
|
||||||
|
- 定义各资源被公用的表格列
|
||||||
|
- 定义各资源的复用函数
|
||||||
|
- `src/stores`目录:
|
||||||
|
- 对资源的数据获取、操作等
|
||||||
|
- 按照资源名小写字母加连字符命名
|
||||||
|
- 目录分为两级:例如 `nova/instances.js`, `cinder/volume.js`
|
||||||
|
- `src/utils`目录:
|
||||||
|
- 公共函数(时间处理、正则、cookie、localStorage、......)
|
||||||
|
- 对应的单元测试,以 test.js 或 spec.js 结尾
|
||||||
|
- `src/styles`目录: 基础样式、公用样式、样式变量等
|
||||||
|
- `src/pages`目录:
|
||||||
|
- 按照页面层级结构递进(按照:菜单项--二级菜单)
|
||||||
|
- 所有目录命名均为小写加连字符命名, 目录包含两个文件夹 `containers` 和 `routers`, 一个文件 `App.js`
|
||||||
|
- `containers`下存放二级目录对应的页面
|
||||||
|
- `routes`用于配置路由
|
||||||
|
|
||||||
|
# src/pages 目录介绍
|
||||||
|
|
||||||
|
- 以一级、二级菜单划分目录,一级菜单列在`src/pages`下,其对应的二级菜单页面位于`src/pages/xxx/containers`下,以“计算-云主机”为例,“计算”对应于`src/pages/compute`目录,“云主机”对应于`src/pages/compute/containers/Instance`目录
|
||||||
|
- `src/pages/compute/containers/Instance/index.jsx`: 云主机列表页,继承于[BaseList 组件](3-1-BaseList-introduction.md)(带有 Tab
|
||||||
|
的页面,继承 TabBaseList 组件即可)
|
||||||
|
- `src/pages/compute/containers/Instance/Detail`目录
|
||||||
|
- 云主机详情页
|
||||||
|
- `index.jsx`继承于[BaseDetail 组件](3-3-BaseDetail-introduction.md)
|
||||||
|
- `src/pages/compute/containers/Instance/actions`目录
|
||||||
|
- 云主机的操作
|
||||||
|
- `Lock.jsx` 锁定云主机,继承于[BaseConfirmAction](3-8-ConfirmAction-introduction.md)
|
||||||
|
- `AttachInterface.jsx` 继承于[BaseModalAction](3-7-ModalAction-introduction.md)
|
||||||
|
- `StepCreate/index.jsx`,继承于[BaseStepAction](3-9-StepAction-introduction.md)
|
||||||
|
- `src/pages/compute/routes`目录:
|
||||||
|
- `index.js`,配置路由
|
||||||
|
- 约定以路由中是否含有“-admin”来判定是管理平台还是控制台
|
||||||
|
|
||||||
|
# test 目录介绍
|
||||||
|
|
||||||
|
[简体中文](/docs/zh/test/2-catalog-introduction.md) | [English](/docs/en/test/2-catalog-introduction.md)
|
||||||
|
|
||||||
|
# 目录简介-图像版
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── Gruntfile.js (用于收集i18n)
|
||||||
|
├── LICENSE
|
||||||
|
├── Makefile
|
||||||
|
├── README.md
|
||||||
|
├── config
|
||||||
|
│ ├── theme.js
|
||||||
|
│ ├── webpack.common.js
|
||||||
|
│ ├── webpack.dev.js (开发时使用的webpack配置)
|
||||||
|
│ ├── webpack.e2e.js (e2e测试时使用的webpack配置,能生成用于检测覆盖率的包)
|
||||||
|
│ └── webpack.prod.js (生成环境使用的webpack打包配置)
|
||||||
|
├── cypress.json (e2e的配置)
|
||||||
|
├── docker
|
||||||
|
│ ├── dev.dockerfile
|
||||||
|
│ ├── nginx.conf
|
||||||
|
│ ├── prod.dockerfile
|
||||||
|
│ └── test.dockerfile
|
||||||
|
├── docs (文档)
|
||||||
|
├── jest.config.js (单元测试配置)
|
||||||
|
├── jsconfig.json
|
||||||
|
├── package.json
|
||||||
|
├── src
|
||||||
|
│ ├── api (api汇总,暂未使用)
|
||||||
|
│ ├── asset
|
||||||
|
│ │ ├── image (图片放置位置)
|
||||||
|
│ │ └── template
|
||||||
|
│ │ └── index.html
|
||||||
|
│ ├── components (公用组件)
|
||||||
|
│ ├── containers
|
||||||
|
│ │ ├── Action
|
||||||
|
│ │ │ ├── ConfirmAction (确认型的action基类)
|
||||||
|
│ │ │ ├── FormAction (单页的action基类)
|
||||||
|
│ │ │ ├── ModalAction (弹窗型的action基类)
|
||||||
|
│ │ │ ├── StepAction (分多步的单页action,例如:创建云主机)
|
||||||
|
│ │ │ └── index.jsx
|
||||||
|
│ │ ├── BaseDetail (带有详情信息的详情页基类)
|
||||||
|
│ │ ├── List (列表页的基类,例如:云主机)
|
||||||
|
│ │ ├── TabDetail (带有tab切换的详情页的基类,例如:云主机详情)
|
||||||
|
│ │ └── TabList (带有tab切换的列表页)
|
||||||
|
│ ├── core
|
||||||
|
│ │ ├── App.jsx
|
||||||
|
│ │ ├── i18n.js
|
||||||
|
│ │ ├── index.jsx (入口)
|
||||||
|
│ │ └── routes.js (按模块的路由配置)
|
||||||
|
│ ├── layouts
|
||||||
|
│ │ ├── Base (登录后使用的布局)
|
||||||
|
│ │ ├── Blank (空白布局)
|
||||||
|
│ │ ├── User (登录使用的布局)
|
||||||
|
│ │ ├── admin-menu.jsx (管理平台使用的菜单配置)
|
||||||
|
│ │ └── menu.jsx (控制台使用的菜单配置)
|
||||||
|
│ ├── locales (翻译)
|
||||||
|
│ │ ├── en.json
|
||||||
|
│ │ ├── index.js
|
||||||
|
│ │ └── zh.json
|
||||||
|
│ ├── pages (页面-目录结构按照:菜单项--二级菜单 分配,其中二级菜单的页面放在containers文件夹下)
|
||||||
|
│ │ ├── base
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ ├── 404 (404页面)
|
||||||
|
│ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ ├── AdminOverview (管理平台首页)
|
||||||
|
│ │ │ │ │ ├── components
|
||||||
|
│ │ │ │ │ │ ├── ComputeService.jsx
|
||||||
|
│ │ │ │ │ │ ├── NetworkService.jsx
|
||||||
|
│ │ │ │ │ │ ├── PlatformInfo.jsx
|
||||||
|
│ │ │ │ │ │ ├── ResourceOverview.jsx
|
||||||
|
│ │ │ │ │ │ └── VirtualResource.jsx
|
||||||
|
│ │ │ │ │ ├── index.jsx
|
||||||
|
│ │ │ │ │ └── style.less
|
||||||
|
│ │ │ │ └── Overview (控制台首页)
|
||||||
|
│ │ │ │ ├── components
|
||||||
|
│ │ │ │ │ ├── ProjectInfo.jsx
|
||||||
|
│ │ │ │ │ ├── QuotaOverview.jsx
|
||||||
|
│ │ │ │ │ └── ResourceStatistic.jsx
|
||||||
|
│ │ │ │ ├── index.jsx
|
||||||
|
│ │ │ │ └── style.less
|
||||||
|
│ │ │ └── routes (路由配置)
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ ├── compute
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ ├── BareMetalNode (裸机配置)
|
||||||
|
│ │ │ │ ├── Flavor (云主机类型)
|
||||||
|
│ │ │ │ ├── HostAggregate (主机集合)
|
||||||
|
│ │ │ │ │ ├── Aggregate (主机集合)
|
||||||
|
│ │ │ │ │ ├── AvailabilityZone (可用域)
|
||||||
|
│ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ ├── Hypervisors (虚拟机管理器)
|
||||||
|
│ │ │ │ │ ├── ComputeHost (计算节点)
|
||||||
|
│ │ │ │ │ ├── Hypervisor (虚拟机管理器)
|
||||||
|
│ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ ├── Image (镜像)
|
||||||
|
│ │ │ │ ├── Instance (云主机)
|
||||||
|
│ │ │ │ │ ├── Detail (详情页)
|
||||||
|
│ │ │ │ │ │ ├── BaseDetail (基础信息)
|
||||||
|
│ │ │ │ │ │ ├── SecurityGroup (安全组)
|
||||||
|
│ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ ├── actions (操作)
|
||||||
|
│ │ │ │ │ │ ├── AssociateFip.jsx (绑定浮动IP)
|
||||||
|
│ │ │ │ │ │ ├── AttachInterface.jsx (挂载网卡)
|
||||||
|
│ │ │ │ │ │ ├── AttachIsoVolume.jsx (挂载ISO光盘)
|
||||||
|
│ │ │ │ │ │ ├── AttachVolume.jsx (挂载云硬盘)
|
||||||
|
│ │ │ │ │ │ ├── ChangePassword.jsx (修改密码)
|
||||||
|
│ │ │ │ │ │ ├── Console.jsx (控制台)
|
||||||
|
│ │ │ │ │ │ ├── CreateImage.jsx (创建镜像)
|
||||||
|
│ │ │ │ │ │ ├── CreateIronic (创建裸机-分步型Form)
|
||||||
|
│ │ │ │ │ │ │ ├── BaseStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── ConfirmStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── NetworkStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── SystemStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── index.jsx
|
||||||
|
│ │ │ │ │ │ │ └── index.less
|
||||||
|
│ │ │ │ │ │ ├── CreateSnapshot.jsx (创建快照)
|
||||||
|
│ │ │ │ │ │ ├── Delete.jsx (删除云主机)
|
||||||
|
│ │ │ │ │ │ ├── DeleteIronic.jsx (删除裸机实例)
|
||||||
|
│ │ │ │ │ │ ├── DetachInterface.jsx (卸载网卡)
|
||||||
|
│ │ │ │ │ │ ├── DetachIsoVolume.jsx (卸载ISO镜像)
|
||||||
|
│ │ │ │ │ │ ├── DetachVolume.jsx (卸载云硬盘)
|
||||||
|
│ │ │ │ │ │ ├── DisassociateFip.jsx (解绑浮动IP)
|
||||||
|
│ │ │ │ │ │ ├── Edit.jsx (编辑云主机)
|
||||||
|
│ │ │ │ │ │ ├── ExtendRootVolume.jsx (扩容根磁盘)
|
||||||
|
│ │ │ │ │ │ ├── LiveMigrate.jsx (热迁移)
|
||||||
|
│ │ │ │ │ │ ├── Lock.jsx (锁定云主机)
|
||||||
|
│ │ │ │ │ │ ├── ManageSecurityGroup.jsx (管理安全组)
|
||||||
|
│ │ │ │ │ │ ├── Migrate.jsx (迁移)
|
||||||
|
│ │ │ │ │ │ ├── Pause.jsx (暂停云主机)
|
||||||
|
│ │ │ │ │ │ ├── Reboot.jsx (重启云主机)
|
||||||
|
│ │ │ │ │ │ ├── Rebuild.jsx (重建云主机)
|
||||||
|
│ │ │ │ │ │ ├── RebuildSelect.jsx (选镜像重建云主机)
|
||||||
|
│ │ │ │ │ │ ├── Resize.jsx (修改配置)
|
||||||
|
│ │ │ │ │ │ ├── ResizeOnline.jsx (在线修改配置)
|
||||||
|
│ │ │ │ │ │ ├── Resume.jsx (恢复云主机)
|
||||||
|
│ │ │ │ │ │ ├── Shelve.jsx (归档云主机)
|
||||||
|
│ │ │ │ │ │ ├── SoftDelete.jsx (软删除云主机)
|
||||||
|
│ │ │ │ │ │ ├── SoftReboot.jsx (软重启云主机)
|
||||||
|
│ │ │ │ │ │ ├── Start.jsx (启动云主机)
|
||||||
|
│ │ │ │ │ │ ├── StepCreate (创建云主机-分步创建)
|
||||||
|
│ │ │ │ │ │ │ ├── BaseStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── ConfirmStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── NetworkStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── SystemStep
|
||||||
|
│ │ │ │ │ │ │ │ └── index.jsx
|
||||||
|
│ │ │ │ │ │ │ ├── index.jsx
|
||||||
|
│ │ │ │ │ │ │ └── index.less
|
||||||
|
│ │ │ │ │ │ ├── Stop.jsx (关闭云主机)
|
||||||
|
│ │ │ │ │ │ ├── Suspend.jsx (挂起云主机)
|
||||||
|
│ │ │ │ │ │ ├── Unlock.jsx (解锁云主机)
|
||||||
|
│ │ │ │ │ │ ├── Unpause.jsx (恢复暂停的云主机)
|
||||||
|
│ │ │ │ │ │ ├── Unshelve.jsx (恢复归档的云主机)
|
||||||
|
│ │ │ │ │ │ ├── index.jsx
|
||||||
|
│ │ │ │ │ │ └── index.less
|
||||||
|
│ │ │ │ │ ├── components (组件)
|
||||||
|
│ │ │ │ │ │ ├── FlavorSelectTable.jsx
|
||||||
|
│ │ │ │ │ │ └── index.less
|
||||||
|
│ │ │ │ │ ├── index.jsx
|
||||||
|
│ │ │ │ │ └── index.less
|
||||||
|
│ │ │ │ ├── Keypair (密钥)
|
||||||
|
│ │ │ │ └── ServerGroup (云主机组)
|
||||||
|
│ │ │ └── routes (计算菜单下的路由配置)
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ ├── configuration (平台配置)
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ ├── Metadata (元数据定义)
|
||||||
|
│ │ │ │ ├── Setting (系统配置)
|
||||||
|
│ │ │ │ └── SystemInfo (系统信息)
|
||||||
|
│ │ │ └── routes (平台配置菜单下的路由配置)
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ ├── heat (资源编排)
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ └── Stack (堆栈)
|
||||||
|
│ │ │ └── routes (资源编排菜单下的路由配置)
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ ├── identity (身份管理)
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ ├── Domain (域)
|
||||||
|
│ │ │ │ ├── Project (项目)
|
||||||
|
│ │ │ │ ├── Role (角色)
|
||||||
|
│ │ │ │ ├── User (用户)
|
||||||
|
│ │ │ │ └── UserGroup (用户组)
|
||||||
|
│ │ │ └── routes (路由配置)
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ ├── management (运维管理)
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ └── RecycleBin (回收站)
|
||||||
|
│ │ │ └── routes (路由配置)
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ ├── network (网络)
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ ├── FloatingIp (浮动IP)
|
||||||
|
│ │ │ │ ├── LoadBalancers (负载均衡)
|
||||||
|
│ │ │ │ ├── Network (网络)
|
||||||
|
│ │ │ │ ├── QoSPolicy (Qos策略)
|
||||||
|
│ │ │ │ ├── Router (路由器)
|
||||||
|
│ │ │ │ ├── SecurityGroup (安全组)
|
||||||
|
│ │ │ │ ├── Topology (网络拓扑)
|
||||||
|
│ │ │ │ ├── VPN (VPN)
|
||||||
|
│ │ │ │ └── VirtualAdapter (虚拟网卡)
|
||||||
|
│ │ │ └── routes (路由配置)
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ ├── storage (存储)
|
||||||
|
│ │ │ ├── App.jsx
|
||||||
|
│ │ │ ├── containers
|
||||||
|
│ │ │ │ ├── Backup (备份)
|
||||||
|
│ │ │ │ ├── Snapshot (云硬盘快照)
|
||||||
|
│ │ │ │ ├── Storage (存储后端)
|
||||||
|
│ │ │ │ ├── Volume (云硬盘)
|
||||||
|
│ │ │ │ └── VolumeType (云硬盘类型)
|
||||||
|
│ │ │ │ ├── QosSpec (QoS)
|
||||||
|
│ │ │ │ ├── VolumeType (云硬盘类型)
|
||||||
|
│ │ │ │ └── index.jsx
|
||||||
|
│ │ │ └── routes ()
|
||||||
|
│ │ │ └── index.js
|
||||||
|
│ │ └── user (登录页面)
|
||||||
|
│ │ ├── App.jsx
|
||||||
|
│ │ ├── containers
|
||||||
|
│ │ │ ├── ChangePassword (修改密码--根据系统配置)
|
||||||
|
│ │ │ │ ├── index.jsx
|
||||||
|
│ │ │ │ └── index.less
|
||||||
|
│ │ │ └── Login (登录)
|
||||||
|
│ │ │ ├── index.jsx
|
||||||
|
│ │ │ └── index.less
|
||||||
|
│ │ └── routes (路由配置)
|
||||||
|
│ │ └── index.js
|
||||||
|
│ ├── resources (存放各资源的自身使用的公用函数,状态等)
|
||||||
|
│ ├── stores (数据处理,按资源类型划分文件夹)
|
||||||
|
│ │ ├── base-list.js (列表数据的基类)
|
||||||
|
│ │ ├── base.js (数据操作的基类)
|
||||||
|
│ │ ├── cinder
|
||||||
|
│ │ ├── glance
|
||||||
|
│ │ ├── heat
|
||||||
|
│ │ ├── ironic
|
||||||
|
│ │ ├── keystone
|
||||||
|
│ │ ├── neutron
|
||||||
|
│ │ ├── nova
|
||||||
|
│ │ ├── octavia
|
||||||
|
│ │ ├── overview-admin.js
|
||||||
|
│ │ ├── project.js
|
||||||
|
│ │ ├── root.js
|
||||||
|
│ │ └── skyline
|
||||||
|
│ ├── styles (公用样式)
|
||||||
|
│ │ ├── base.less
|
||||||
|
│ │ ├── main.less
|
||||||
|
│ │ ├── reset.less
|
||||||
|
│ │ └── variables.less
|
||||||
|
│ └── utils (基础函数)
|
||||||
|
│ ├── RouterConfig.jsx
|
||||||
|
│ ├── constants.js
|
||||||
|
│ ├── cookie.js
|
||||||
|
│ ├── file.js
|
||||||
|
│ ├── file.spec.js
|
||||||
|
│ ├── index.js
|
||||||
|
│ ├── index.test.js (单元测试)
|
||||||
|
│ ├── local-storage.js
|
||||||
|
│ ├── local-storage.spec.js (单元测试)
|
||||||
|
│ ├── request.js
|
||||||
|
│ ├── table.jsx
|
||||||
|
│ ├── time.js
|
||||||
|
│ ├── time.spec.js
|
||||||
|
│ ├── translate.js
|
||||||
|
│ ├── translate.spec.js
|
||||||
|
│ ├── validate.js
|
||||||
|
│ ├── yaml.js
|
||||||
|
│ └── yaml.spec.js
|
||||||
|
├── test
|
||||||
|
│ ├── e2e (E2E测试)
|
||||||
|
│ └── unit (单元测试)
|
||||||
|
├── tools
|
||||||
|
│ └── git_config
|
||||||
|
│ └── commit_message.txt
|
||||||
|
└── yarn.lock
|
||||||
|
```
|
114
docs/zh/develop/3-0-how-to-develop.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-0-how-to-develop.md)
|
||||||
|
|
||||||
|
# 开发一个新的资源列表页
|
||||||
|
|
||||||
|
- 步骤 1:确认代码位置及目录结构
|
||||||
|
- 按照预想的在菜单项中的位置,放置在 Containers 下
|
||||||
|
- 以云主机为例,对应的菜单项为`计算-云主机`,那么创建文件夹`src/pages/compute/containers/Instance`,创建文件`src/pages/compute/containers/Instance/index.jsx`
|
||||||
|
- 步骤 2:编写 Store 代码
|
||||||
|
- 参考[3-5-BaseStore-introduction](3-5-BaseStore-introduction.md),复写相应的函数
|
||||||
|
- 步骤 3:编写列表页代码
|
||||||
|
- 参考[3-1-BaseList-introduction](3-1-BaseList-introduction.md),复写相应的函数
|
||||||
|
- 步骤 4:配置路由
|
||||||
|
- 参考[3-13-Route-introduction](3-13-Route-introduction.md)
|
||||||
|
- 在步骤 1 中的父级目录的`routes/index.js`文件中,配置路由
|
||||||
|
- 如果是全新的模块,还需要在`src/pages/storage/routes/index.js`中导入
|
||||||
|
- 步骤 5:配置菜单
|
||||||
|
- 参考[3-12-Menu-introduction](3-12-Menu-introduction.md)
|
||||||
|
- 配置控制台的菜单项,在`src/layouts/menu.jsx`中配置
|
||||||
|
- 配置管理平台的菜单项,在`src/layouts/admin-menu.jsx`中配置
|
||||||
|
- 步骤 6:国际化
|
||||||
|
- 参考[3-14-I18n-introduction](3-14-I18n-introduction.md),完成相应翻译
|
||||||
|
- 如果,产品需求的列表页面是含有`Tab`的页面,则可参考[3-2-BaseTabList-introduction](3-2-BaseTabList-introduction.md),通常`index.jsx`内配置`Tab`,可参考镜像页面代码`src/pages/compute/containers/Image/index.jsx`
|
||||||
|
|
||||||
|
# 开发一个新的资源详情页
|
||||||
|
|
||||||
|
- 步骤 1:确认代码位置及目录结构
|
||||||
|
- 按照预想的在菜单项中的位置,放置在 Containers 下
|
||||||
|
- 以云主机为例,对应的菜单项为`计算-云主机`,创建文件`src/pages/compute/containers/Instance/Detail/index.jsx`,`src/pages/compute/containers/Instance/Detail/BaseDetail.jsx`
|
||||||
|
- 步骤 2:编写 Store 代码
|
||||||
|
- 参考[3-5-BaseStore-introduction](3-5-BaseStore-introduction.md),复写相应的函数
|
||||||
|
- 步骤 3:编写详情页代码
|
||||||
|
- 参考[3-3-BaseDetail-introduction](3-3-BaseDetail-introduction.md),复写相应的函数
|
||||||
|
- 步骤 4:编写详情页-详情 Tab 代码
|
||||||
|
- 参考[3-4-BaseDetailInfo-introduction](3-4-BaseDetailInfo-introduction.md),复写相应的函数
|
||||||
|
- 步骤 5:配置路由
|
||||||
|
- 参考[3-13-Route-introduction](3-13-Route-introduction.md)
|
||||||
|
- 在步骤 1 中的父级目录的`routes/index.js`文件中,配置路由
|
||||||
|
- 如果是全新的模块,还需要在`src/pages/storage/routes/index.js`中导入
|
||||||
|
- 步骤 6:配置菜单
|
||||||
|
- 参考[3-12-Menu-introduction](3-12-Menu-introduction.md)
|
||||||
|
- 配置控制台的菜单项,在`src/layouts/menu.jsx`中配置
|
||||||
|
- 配置管理平台的菜单项,在`src/layouts/admin-menu.jsx`中配置
|
||||||
|
- 步骤 7:国际化
|
||||||
|
- 参考[3-14-I18n-introduction](3-14-I18n-introduction.md),完成相应翻译
|
||||||
|
|
||||||
|
# 开发一个新的操作
|
||||||
|
|
||||||
|
## 开发一个页面级的操作
|
||||||
|
|
||||||
|
- 步骤 1:确认代码位置及目录结构
|
||||||
|
- 按照预想的在菜单项中的位置,放置在 Containers 下
|
||||||
|
- 以云硬盘为例,对应的菜单项为`存储-云硬盘-云硬盘创建`,创建文件`src/pages/storage/containers/Volume/actions/Create/index.jsx`
|
||||||
|
- 步骤 2:编写 Store 代码
|
||||||
|
- 参考[3-5-BaseStore-introduction](3-5-BaseStore-introduction.md),复写或新增相应的函数
|
||||||
|
- 步骤 3:编写 FormAction 代码
|
||||||
|
- 参考[3-6-FormAction-introduction](3-6-FormAction-introduction.md),复写相应的函数
|
||||||
|
- 步骤 4:配置 Action
|
||||||
|
- 参考[3-11-Action-introduction](3-11-Action-introduction.md),配置到相应为位置
|
||||||
|
- 步骤 5:配置路由
|
||||||
|
- 参考[3-13-Route-introduction](3-13-Route-introduction.md),配置对应的路由
|
||||||
|
- 步骤 6:配置菜单
|
||||||
|
- 参考[3-12-Menu-introduction](3-12-Menu-introduction.md)
|
||||||
|
- 配置控制台的菜单项,在`src/layouts/menu.jsx`中配置
|
||||||
|
- 配置管理平台的菜单项,在`src/layouts/admin-menu.jsx`中配置
|
||||||
|
- 步骤 7:国际化
|
||||||
|
- 参考[3-14-I18n-introduction](3-14-I18n-introduction.md),完成相应翻译
|
||||||
|
|
||||||
|
## 开发一个确认型的操作
|
||||||
|
|
||||||
|
- 步骤 1:确认代码位置及目录结构
|
||||||
|
- 按照预想的在菜单项中的位置,放置在 Containers 下
|
||||||
|
- 以云硬盘为例,对应的菜单项为`存储-云硬盘-删除云硬盘`,创建文件`src/pages/storage/containers/Volume/actions/Delete.jsx`
|
||||||
|
- 步骤 2:编写 Store 代码
|
||||||
|
- 参考[3-5-BaseStore-introduction](3-5-BaseStore-introduction.md),复写或新增相应的函数
|
||||||
|
- 步骤 3:编写 ConfirmAction 代码
|
||||||
|
- 参考[3-8-ConfirmAction-introduction](3-8-ConfirmAction-introduction.md),复写相应的函数
|
||||||
|
- 步骤 4:配置 Action
|
||||||
|
- 参考[3-11-Action-introduction](3-11-Action-introduction.md),配置到相应为位置
|
||||||
|
- 步骤 5:国际化
|
||||||
|
- 参考[3-14-I18n-introduction](3-14-I18n-introduction.md),完成相应翻译
|
||||||
|
|
||||||
|
## 开发一个弹窗型的操作
|
||||||
|
|
||||||
|
- 步骤 1:确认代码位置及目录结构
|
||||||
|
- 按照预想的在菜单项中的位置,放置在 Containers 下
|
||||||
|
- 以云硬盘为例,对应的菜单项为`存储-云硬盘-编辑`,创建文件`src/pages/storage/containers/Volume/actions/Edit.jsx`
|
||||||
|
- 步骤 2:编写 Store 代码
|
||||||
|
- 参考[3-5-BaseStore-introduction](3-5-BaseStore-introduction.md),复写或新增相应的函数
|
||||||
|
- 步骤 3:编写 ModalAction 代码
|
||||||
|
- 参考[3-7-ModalAction-introduction](3-7-ModalAction-introduction.md),复写相应的函数
|
||||||
|
- 步骤 4:配置 Action
|
||||||
|
- 参考[3-11-Action-introduction](3-11-Action-introduction.md),配置到相应为位置
|
||||||
|
- 步骤 5:国际化
|
||||||
|
- 参考[3-14-I18n-introduction](3-14-I18n-introduction.md),完成相应翻译
|
||||||
|
|
||||||
|
## 开发一个分步骤的页面级的操作
|
||||||
|
|
||||||
|
- 步骤 1:确认代码位置及目录结构
|
||||||
|
- 按照预想的在菜单项中的位置,放置在 Containers 下
|
||||||
|
- 以云硬盘为例,对应的菜单项为`计算-云主机-创建`,创建文件`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`
|
||||||
|
- 步骤 2:编写 Store 代码
|
||||||
|
- 参考[3-5-BaseStore-introduction](3-5-BaseStore-introduction.md),复写或新增相应的函数
|
||||||
|
- 步骤 3:编写 StepAction 代码
|
||||||
|
- 参考[3-9-StepAction-introduction](3-9-StepAction-introduction.md),复写相应的函数
|
||||||
|
- 步骤 4:配置 Action
|
||||||
|
- 参考[3-11-Action-introduction](3-11-Action-introduction.md),配置到相应为位置
|
||||||
|
- 步骤 5:配置路由
|
||||||
|
- 参考[3-13-Route-introduction](3-13-Route-introduction.md),配置对应的路由
|
||||||
|
- 步骤 6:配置菜单
|
||||||
|
- 参考[3-12-Menu-introduction](3-12-Menu-introduction.md)
|
||||||
|
- 配置控制台的菜单项,在`src/layouts/menu.jsx`中配置
|
||||||
|
- 配置管理平台的菜单项,在`src/layouts/admin-menu.jsx`中配置
|
||||||
|
- 步骤 7:国际化
|
||||||
|
- 参考[3-14-I18n-introduction](3-14-I18n-introduction.md),完成相应翻译
|
563
docs/zh/develop/3-1-BaseList-introduction.md
Normal file
@ -0,0 +1,563 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-1-BaseList-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
- 各资源列表页的基类
|
||||||
|
|
||||||
|
![列表页](/docs/zh/develop/images/list/volumes.png)
|
||||||
|
|
||||||
|
- 支持数据分页
|
||||||
|
|
||||||
|
![列表页分页](/docs/zh/develop/images/list/pagination.png)
|
||||||
|
|
||||||
|
- 支持搜索
|
||||||
|
|
||||||
|
![列表页搜索](/docs/zh/develop/images/list/search.png)
|
||||||
|
|
||||||
|
- 支持手动刷新数据
|
||||||
|
|
||||||
|
![列表页刷新](/docs/zh/develop/images/list/fresh.png)
|
||||||
|
|
||||||
|
- 支持数据下载
|
||||||
|
|
||||||
|
![列表页下载](/docs/zh/develop/images/list/download.png)
|
||||||
|
|
||||||
|
- 支持批量操作
|
||||||
|
|
||||||
|
![列表页批量](/docs/zh/develop/images/list/batch.png)
|
||||||
|
|
||||||
|
- 具有自动刷新数据的功能(每隔 60 秒自动刷新列表数据,用户无操作的情况下,30 分钟后不再自动刷新,可暂停自动刷新功能)
|
||||||
|
|
||||||
|
![列表页自动刷新](/docs/zh/develop/images/list/stop-auto-refresh.png)
|
||||||
|
|
||||||
|
- 可配置列表表头
|
||||||
|
|
||||||
|
![列表页自动刷新](/docs/zh/develop/images/list/hide.png)
|
||||||
|
|
||||||
|
- 各资源列表页通过复写函数即可完成
|
||||||
|
|
||||||
|
# BaseList 代码文件
|
||||||
|
|
||||||
|
- `src/containers/List/index.jsx`
|
||||||
|
|
||||||
|
# BaseList 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 资源列表继承于 BaseList 组件
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 通常需要复写的属性与函数,主要包含:
|
||||||
|
- 页面的权限
|
||||||
|
- 页面的资源名称
|
||||||
|
- 表格的列的配置
|
||||||
|
- 表格的搜索项
|
||||||
|
- 表格的操作项等
|
||||||
|
- 表格对应的`store`
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 资源数据分页使用前端分页还是后端分页
|
||||||
|
- 资源数据排序使用前端排序还是后端排序
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 当前页是否是详情页中的资源列表
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 处理下载
|
||||||
|
- 处理自动刷新
|
||||||
|
- 隐藏/展示某些表格列
|
||||||
|
- 处理搜索
|
||||||
|
- 处理分页信息变动后的数据请求与展示
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 通常需要复写的属性与函数
|
||||||
|
|
||||||
|
- `policy`:
|
||||||
|
- 必须复写该函数
|
||||||
|
- 页面对应的权限,如果权限验证不通过,则无法请求数据。
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get policy() {
|
||||||
|
return 'volume:get_all';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- 必须复写该函数
|
||||||
|
- 页面资源对应的名称。
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get name() {
|
||||||
|
return t('volumes');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `actionConfigs`
|
||||||
|
- 配置资源的各种操作
|
||||||
|
- 主按钮操作,如:创建
|
||||||
|
- 批量操作
|
||||||
|
- 每一行数据的操作
|
||||||
|
- 配置定义在资源的 actions 目录下
|
||||||
|
- 以密钥`src/pages/compute/containers/Keypair/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import actionConfigs from './actions';
|
||||||
|
get actionConfigs() {
|
||||||
|
return actionConfigs;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `searchFilters`
|
||||||
|
- 配置资源的搜索项
|
||||||
|
- 支持基于字符串的搜索
|
||||||
|
- 支持选择的搜索,如:基于状态的搜索
|
||||||
|
- 支持多种搜索条件均需满足的搜索
|
||||||
|
- 返回配置的数组,每个配置代表一个搜索条件
|
||||||
|
- 每个配置需要满足一下条件:
|
||||||
|
- `label`,必须项,搜索的标题
|
||||||
|
- `name`,必须项,该搜索项对应的参数`Key`
|
||||||
|
- `options`,可选项
|
||||||
|
- 如果不设置`options`属性,表示,该搜索是基于输入字符串的搜索,如:对名称的搜索
|
||||||
|
- 如设置`options`属性,则在页面内需要从`options`中选择
|
||||||
|
- `options`的格式:
|
||||||
|
- `key`: 必须项,`option`对应的值
|
||||||
|
- `label`:必须项,`option`对应的文字,即页面上看到的内容
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get searchFilters() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('Name'),
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Status'),
|
||||||
|
name: 'status',
|
||||||
|
options: ['available', 'in-use', 'error'].map((it) => ({
|
||||||
|
key: it,
|
||||||
|
label: volumeStatus[it],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Shared'),
|
||||||
|
name: 'multiattach',
|
||||||
|
options: yesNoOptions,
|
||||||
|
},];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getColumns`
|
||||||
|
- 返回列表表格的配置信息列表
|
||||||
|
- 每个配置项的设置:
|
||||||
|
- `title`,必须项,表头的标题
|
||||||
|
- `dataIndex`,必须项,对应的后端数据的 key 值
|
||||||
|
- `hidden`,可选项,该列是否可隐藏,默认值为`false`
|
||||||
|
- `sorter`,该列是否可排序,默认可排序
|
||||||
|
- `stringify`,可选项,下载到`csv`中时,该列中数据显示的内容,因为有些列有额外的样式或是 UI 处理,会导致对该列的转字符串的结果出现问题,此时需要编写该函数
|
||||||
|
- `render`,可选项,默认是基于`dataIndex`来展示内容,使用该属性,可基于`render`的结果渲染表格内容
|
||||||
|
- `valueRender`,可选项,使用已有的函数自动处理数据
|
||||||
|
- `sinceTime`,处理时间,显示成"XX 小时前"
|
||||||
|
- `keepTime`,显示剩余时间
|
||||||
|
- `yesNo`,处理`Boolean`值,显示成“是”、“否”
|
||||||
|
- `GBValue`,处理大小,显示成"XXXGB"
|
||||||
|
- `noValue`,没有值时,显示成“-”
|
||||||
|
- `bytes`,处理大小
|
||||||
|
- `uppercase`,大写
|
||||||
|
- `formatSize`,处理大小,显示如“2.32 GB”,“56.68 MB”
|
||||||
|
- `toLocalTime`,处理时间,显示如“2021-06-17 04:13:07”
|
||||||
|
- `toLocalTimeMoment`,处理时间,显示如“2021-06-17 04:13:07”
|
||||||
|
- `linkPrefix`,可选项,当`dataIndex=name`时,`linkPrefix`属性用于处理名称对应的链接的前缀
|
||||||
|
- 以镜像`src/pages/compute/containers/Image/Image.jsx`为例
|
||||||
|
- 表格包含的列:ID/名称、项目 ID/名称(管理平台中展示)、描述、使用类型、类型、状态、可见性、硬盘格式、容量、创建于
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getColumns = () => [
|
||||||
|
{
|
||||||
|
title: t('ID/Name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
linkPrefix: `/compute/${this.getUrl('image')}/detail`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Project ID/Name'),
|
||||||
|
dataIndex: 'project_name',
|
||||||
|
hidden: !this.isAdminPage && this.tab !== 'all',
|
||||||
|
sorter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Description'),
|
||||||
|
dataIndex: 'description',
|
||||||
|
isHideable: true,
|
||||||
|
sorter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Use Type'),
|
||||||
|
dataIndex: 'usage_type',
|
||||||
|
isHideable: true,
|
||||||
|
render: (value) => imageUsage[value] || '-',
|
||||||
|
sorter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Type'),
|
||||||
|
dataIndex: 'os_distro',
|
||||||
|
isHideable: true,
|
||||||
|
render: (value) => <ImageType type={value} title={value} />,
|
||||||
|
width: 80,
|
||||||
|
sorter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Status'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
render: (value) => imageStatus[value] || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Visibility'),
|
||||||
|
dataIndex: 'visibility',
|
||||||
|
render: (value) => imageVisibility[value] || '-',
|
||||||
|
sorter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Disk Format'),
|
||||||
|
dataIndex: 'disk_format',
|
||||||
|
isHideable: true,
|
||||||
|
render: (value) => imageFormats[value] || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Size'),
|
||||||
|
dataIndex: 'size',
|
||||||
|
isHideable: true,
|
||||||
|
valueRender: 'formatSize',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Created At'),
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
isHideable: true,
|
||||||
|
valueRender: 'sinceTime',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- `init`
|
||||||
|
- 配置 Store 的函数,在这个函数中配置用于处理数据请求的 Store,以及用于下载数据的 Store
|
||||||
|
- 通常使用的是单例的 Store,但是对于某些详情页下的列表页,使用`new XXXStore()`
|
||||||
|
- `init`中可配置`this.store`与`this.downloadStore`
|
||||||
|
- `this.store`用于处理列表数据
|
||||||
|
- `this.downloadStore`用于处理下载数据
|
||||||
|
- 如果使用前端页面,只配置`this.store`即可,因为是一次性获取所有的数据,下载的数据等于列表中的数据,即这时,`this.downloadStore = this.store`
|
||||||
|
- 以项目`src/pages/identity/containers/Project/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
init() {
|
||||||
|
this.store = globalProjectStore;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 如果使用后端分页,需要分别配置`this.store`与`this.downloadStore`
|
||||||
|
- 以路由器`src/pages/network/containers/Router/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
init() {
|
||||||
|
this.store = new RouterStore();
|
||||||
|
this.downloadStore = new RouterStore();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `alsoRefreshDetail`
|
||||||
|
- 详情页中的列表数据刷新时,是否需要同步刷新详情数据
|
||||||
|
- 默认同步刷新,如不需要同步刷新,复写该函数
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get alsoRefreshDetail() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `list`
|
||||||
|
- 页面对象的 store 中的数据
|
||||||
|
- 默认值是`this.store.list`
|
||||||
|
- `rowKey`
|
||||||
|
- 列表数据的唯一标识的 Key
|
||||||
|
- 默认值是`id`
|
||||||
|
- 以密钥 Keypair `src/pages/compute/containers/Keypair/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get rowKey() {
|
||||||
|
return 'name';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `hasTab`
|
||||||
|
- 列表页是否是 Tab 下的列表页
|
||||||
|
- 默认值为`false`
|
||||||
|
- 会根据改值调整表格的高度
|
||||||
|
- 以`src/pages/configuration/containers/SystemInfo/Catalog.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get hasTab() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
![列表页Tab](/docs/zh/develop/images/list/tab-service.png)
|
||||||
|
|
||||||
|
- `hideCustom`
|
||||||
|
- 是否显示表头配置图标
|
||||||
|
- 默认值是`true`
|
||||||
|
- 以`src/pages/configuration/containers/Setting/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get hideCustom() {
|
||||||
|
return 'name';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `hideSearch`
|
||||||
|
- 是否显示搜索框
|
||||||
|
- 默认显示
|
||||||
|
- 以资源编排-堆栈-详情页-日志`src/pages/heat/containers/Stack/Detail/Event.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get hideSearch() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `hideRefresh`
|
||||||
|
- 是否显示自动刷新按钮
|
||||||
|
- 默认显示
|
||||||
|
- 如不显示,则列表不具有自动刷新数据功能
|
||||||
|
- `hideDownload`
|
||||||
|
- 是否展示下载按钮
|
||||||
|
- 默认显示
|
||||||
|
- `checkEndpoint`
|
||||||
|
- 是否需要检测 endpoint
|
||||||
|
- 默认不需要
|
||||||
|
- 某些服务可能未部署,需要二次验证,一旦检测未部署,是显示“未开放”样式页面
|
||||||
|
- 以 VPN`src/pages/network/containers/VPN/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get checkEndpoint() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `endpoint`
|
||||||
|
- 当`checkEndpoint`为`true`时使用
|
||||||
|
- 以 VPN`src/pages/network/containers/VPN/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get endpoint() {
|
||||||
|
return vpnEndpoint();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isFilterByBackend`
|
||||||
|
- 是否由后端分页
|
||||||
|
- 默认值是`false`,即使用前端分页
|
||||||
|
- 使用前端分页时,是一次性从后端获取全部数据,然后按页面内的页码、单页数量展示数据
|
||||||
|
- 使用后端分页时,以页面、单页数量向后端请求相应数量的数据
|
||||||
|
- 以路由器`src/pages/network/containers/Router/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get isFilterByBackend() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isSortByBackend`
|
||||||
|
- 是否由后端排序
|
||||||
|
- 默认值是`false`,即使用前端排序
|
||||||
|
- 使用前端排序时,基于列表内的数据大小排序(可自定义排序函数)
|
||||||
|
- 如果使用前端分页+前端排序,那么能基于所有数据排序
|
||||||
|
- 如果使用后端分页+前端排序,只能基于当前页的数据排序
|
||||||
|
- 使用后端分页时,按列表内设置的排序项、排序方向向后端请求数据
|
||||||
|
- 以路由器`src/pages/network/containers/Router/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get isSortByBackend() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 当`isSortByBackend`设置为`true`时,通常需要重写相应`store`中的`updateParamsSortPage`函数
|
||||||
|
- 以`src/stores/neutron/router.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
updateParamsSortPage = (params, sortKey, sortOrder) => {
|
||||||
|
if (sortKey && sortOrder) {
|
||||||
|
params.sort_key = sortKey;
|
||||||
|
params.sort_dir = sortOrder === 'descend' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `adminPageHasProjectFilter`
|
||||||
|
- 管理平台的搜索项中是否包含基于项目 ID 的搜索
|
||||||
|
- 默认值为`false`
|
||||||
|
- 以云主机`src/pages/compute/containers/Instance/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get adminPageHasProjectFilter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `transitionStatusList`
|
||||||
|
- 数据处于过渡状态时对应的状态值列表
|
||||||
|
- 默认值为`[]`空列表
|
||||||
|
- 数据处于过渡状态时,页面的自动刷新会加快,变为 30 秒一次
|
||||||
|
- 默认值为`false`
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const volumeTransitionStatuses = [
|
||||||
|
'creating',
|
||||||
|
'extending',
|
||||||
|
'downloading',
|
||||||
|
'attaching',
|
||||||
|
'detaching',
|
||||||
|
'deleting',
|
||||||
|
'backing-up',
|
||||||
|
'restoring-backup',
|
||||||
|
'awaiting-transfer',
|
||||||
|
'uploading',
|
||||||
|
'rollbacking',
|
||||||
|
'retyping',
|
||||||
|
];
|
||||||
|
get transitionStatusList() {
|
||||||
|
return volumeTransitionStatuses;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fetchDataByAllProjects`
|
||||||
|
- 管理平台请求数据时,是否带有`all_projects`参数
|
||||||
|
- 默认值为`true`
|
||||||
|
- 以云硬盘类型`src/pages/storage/containers/VolumeType/VolumeType/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get fetchDataByAllProjects() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fetchDataByCurrentProject`
|
||||||
|
- 控制台请求数据时,是否带有`project_id`参数
|
||||||
|
- 默认值为`false`
|
||||||
|
- 以浮动 IP`src/pages/network/containers/FloatingIp/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get fetchDataByCurrentProject() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `defaultSortKey`
|
||||||
|
- 使用后端排序时,默认的排序 Key
|
||||||
|
- 以路由器`src/pages/network/containers/Router/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get defaultSortKey() {
|
||||||
|
return 'status';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `clearListUnmount`
|
||||||
|
- 页面切换时,是否需要情况当前 store 内的 list 数据
|
||||||
|
- 一般情况,资源列表页使用的是`GlobalXXStore`,即单例的 Store,页面切换时,列表数据并不会清空,当回到该页面时,会先展示之前的数据,然后页面自动刷新获取新数据
|
||||||
|
- 默认值为`false`,页面切换时不清空数据
|
||||||
|
- `ableAutoFresh`
|
||||||
|
- 是否自动刷新
|
||||||
|
- 默认值为`true`
|
||||||
|
- `projectFilterKey`
|
||||||
|
- 请求时,project 对应的 key 值
|
||||||
|
- 默认值是`project_id`
|
||||||
|
- 以镜像`src/pages/compute/containers/Image/Image.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get projectFilterKey() {
|
||||||
|
return 'owner';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getCheckboxProps`
|
||||||
|
- 列表内的数据是否可选择,选中后可进行批量操作
|
||||||
|
- 默认都可选择
|
||||||
|
- 以云主机`src/pages/compute/containers/Instance/index.jsx`为例
|
||||||
|
- 裸机实例不可被选择
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getCheckboxProps(record) {
|
||||||
|
return {
|
||||||
|
disabled: isIronicInstance(record),
|
||||||
|
name: record.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getData`
|
||||||
|
- 处理数据请求的函数
|
||||||
|
- 默认使用`store.fetchList`或`store.fetchListByPage`方法从服务端获取数据
|
||||||
|
- 不建议复写该函数
|
||||||
|
- `fetchDataByPage`
|
||||||
|
- 采用后端分页时,处理数据请求的函数
|
||||||
|
- 默认使用`store.fetchListByPage`方法获取数据
|
||||||
|
- 不建议复写该函数
|
||||||
|
- `fetchData`
|
||||||
|
- 采用前端分页时,处理数据请求的函数
|
||||||
|
- 默认使用`store.fetchList`方法获取数据
|
||||||
|
- 不建议复写该函数
|
||||||
|
- `updateFetchParamsByPage`
|
||||||
|
- 采用后端分页时,在基类的基础上调整请求参数的函数
|
||||||
|
- 如果基类的默认参数无法满足请求时,建议通过复写该函数,并同步修改对应的`store`中的`listDidFetch`方法以完成数据请求
|
||||||
|
- `updateFetchParams`
|
||||||
|
- 采用前端分页时,在基类的基础上调整请求参数的函数
|
||||||
|
- 如果基类的默认参数无法满足请求时,建议通过复写该函数,并同步修改对应的`store`中的`listDidFetch`方法以完成数据请求
|
||||||
|
- `updateHints`
|
||||||
|
- 表格上放的提示语
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `isInDetailPage`
|
||||||
|
- 标识当前页面是否为详情页下的列表页
|
||||||
|
- `location`
|
||||||
|
- 页面的路由信息
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `hasAdminRole`
|
||||||
|
- 登录的用户角色是否具有管理员角色
|
||||||
|
- `getUrl`
|
||||||
|
- 生成页面 Url 的函数
|
||||||
|
- 如:需要给列表页的关联资源提供跳转功能,使用该函数,可以在控制台跳转到控制台的相应地址,在管理平台跳转到管理平台的相应地址
|
||||||
|
- `params`
|
||||||
|
- 路由带有的参数信息
|
||||||
|
- 一般用于生成页面请求 API 时的参数
|
||||||
|
- `routing`
|
||||||
|
- 页面对应的路由信息
|
||||||
|
- `isLoading`
|
||||||
|
- 当前页面是否在数据更新,更新时会显示 loading 样式
|
||||||
|
- `endpointError`
|
||||||
|
- 判定 Endpoint 是否有效
|
||||||
|
- `hintHeight`
|
||||||
|
- 页面内的提示的高度
|
||||||
|
- `tableTopHeight`
|
||||||
|
- 表格上方占用的高度
|
||||||
|
- 基于提示、Tab 计算
|
||||||
|
- `tableHeight`
|
||||||
|
- 表格的高度
|
||||||
|
- `currentProjectId`
|
||||||
|
- 当前登录的用户所属的项目 ID
|
||||||
|
- `defaultSortOrder`
|
||||||
|
- 使用后端排序时,默认的排序方向为降序`descend`
|
||||||
|
- `itemInTransitionFunction`
|
||||||
|
- 判定是否有数据处于过渡状态,如果有数据处于过渡状态,则自动刷新数据的时间间隔由 60 秒变为 30 秒
|
||||||
|
- `primaryActions`
|
||||||
|
- 主按钮操作列表
|
||||||
|
- `batchActions`
|
||||||
|
- 批量操作列表
|
||||||
|
- `itemActions`
|
||||||
|
- 每一行数据对应的操作列表
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- 建议查看代码理解,`src/containers/List/index.jsx`
|
1047
docs/zh/develop/3-10-FormItem-introduction.md
Normal file
141
docs/zh/develop/3-11-Action-introduction.md
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-11-Action-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
- 配置资源对应的所有操作
|
||||||
|
|
||||||
|
![操作](/docs/zh/develop/images/form/action.png)
|
||||||
|
|
||||||
|
- 按照相应的配置编写后,会在资源列表页相应的位置展示相应的操作按钮
|
||||||
|
|
||||||
|
# 代码位置
|
||||||
|
|
||||||
|
- `pages/xxxx/containers/XXXX/actions/index.jsx`
|
||||||
|
|
||||||
|
# 如何使用
|
||||||
|
|
||||||
|
- 返回一个对象,其内配置主操作按钮、批量操作按钮、行操作按钮
|
||||||
|
- 以网络`src/pages/network/containers/Network/actions/index.jsx`为例
|
||||||
|
- 配置了主按钮为创建
|
||||||
|
- 配置了批量操作为删除
|
||||||
|
- 配置了行操作为编辑、创建子网、删除
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import CreateNetwork from './CreateNetwork';
|
||||||
|
import CreateSubnet from './CreateSubnet';
|
||||||
|
import DeleteAction from './Delete';
|
||||||
|
import Edit from './Edit';
|
||||||
|
|
||||||
|
const actionConfigs = {
|
||||||
|
rowActions: {
|
||||||
|
firstAction: Edit,
|
||||||
|
moreActions: [
|
||||||
|
{
|
||||||
|
action: CreateSubnet,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: DeleteAction,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
batchActions: [DeleteAction],
|
||||||
|
primaryActions: [CreateNetwork],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default actionConfigs;
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在资源对应的列表代码中配置`actionConfigs`即可
|
||||||
|
- 以网络`src/pages/network/containers/Network/ProjectNetwork.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import actionConfigs from './actions';
|
||||||
|
get actionConfigs() {
|
||||||
|
return actionConfigs;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主操作按钮配置`primaryActions`
|
||||||
|
|
||||||
|
- 返回组件的列表
|
||||||
|
- 如果没有主按钮,可以设置为`null`或`[]`
|
||||||
|
- 如果不可操作(比如权限不够),将自动隐藏
|
||||||
|
|
||||||
|
## 批量操作按钮配置`batchActions`
|
||||||
|
|
||||||
|
- 返回组件的列表
|
||||||
|
- 如果没有主按钮,可以设置为`null`或`[]`
|
||||||
|
- 如果不可操作(比如权限不够),将自动隐藏
|
||||||
|
|
||||||
|
## 行操作按钮配置`rowActions`
|
||||||
|
|
||||||
|
- 返回一个对象,内含`firstAction`, `moreActions`对应的组件
|
||||||
|
- 批量操作按钮如果被禁用(比如)
|
||||||
|
- 可以返回一个空对象`{}`
|
||||||
|
- `firstAction`,行操作对应的第一个按钮
|
||||||
|
- 如果不可操作,按钮灰掉
|
||||||
|
- 可以是一个组件
|
||||||
|
- 可以是`null`
|
||||||
|
- 以系统信息-网络`src/pages/configuration/containers/SystemInfo/NeutronAgent/actions/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import Enable from './Enable';
|
||||||
|
import Disable from './Disable';
|
||||||
|
|
||||||
|
const actionConfigs = {
|
||||||
|
rowActions: {
|
||||||
|
firstAction: null,
|
||||||
|
moreActions: [
|
||||||
|
{
|
||||||
|
action: Enable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Disable,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
batchActions: [],
|
||||||
|
primaryActions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default actionConfigs;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `moreActions`,`更多`按钮下对应的操作组件
|
||||||
|
- 操作的数组
|
||||||
|
- 其内的操作如果不可用,将直接隐藏该操作按钮
|
||||||
|
- 支持两种格式的配置,对应了不同的展示方案
|
||||||
|
- 每个元素是个含有`action`属性的对象
|
||||||
|
|
||||||
|
![云硬盘操作](/docs/zh/develop/images/form/volume-action.png)
|
||||||
|
|
||||||
|
- 每个元数是个含有`title`、`actions`属性的对象
|
||||||
|
|
||||||
|
![云主机操作](/docs/zh/develop/images/form/instance-action.png)
|
||||||
|
|
||||||
|
- 以云主机`src/pages/compute/containers/Instance/actions/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const statusActions = [
|
||||||
|
StartAction,
|
||||||
|
StopAction,
|
||||||
|
LockAction,
|
||||||
|
UnlockAction,
|
||||||
|
RebootAction,
|
||||||
|
SoftRebootAction,
|
||||||
|
SuspendAction,
|
||||||
|
ResumeAction,
|
||||||
|
PauseAction,
|
||||||
|
UnpauseAction,
|
||||||
|
Shelve,
|
||||||
|
Unshelve,
|
||||||
|
];
|
||||||
|
const actionConfigs = {
|
||||||
|
rowActions: {
|
||||||
|
firstAction: Console,
|
||||||
|
moreActions: [
|
||||||
|
{
|
||||||
|
title: t('Instance Status'),
|
||||||
|
actions: statusActions,
|
||||||
|
},...}}
|
||||||
|
```
|
110
docs/zh/develop/3-12-Menu-introduction.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-12-Menu-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
- 点击后直接跳转到相应页面
|
||||||
|
- 配置控制台的左侧菜单项
|
||||||
|
|
||||||
|
![控制台](/docs/zh/develop/images/menu/console-menu.png)
|
||||||
|
|
||||||
|
- 配置管理平台的左右菜单项
|
||||||
|
|
||||||
|
![管理平台](/docs/zh/develop/images/menu/admin-menu.png)
|
||||||
|
|
||||||
|
- 支持一级菜单带图标
|
||||||
|
- 支持二级菜单展开
|
||||||
|
- 支持路由变更后菜单选中项自动切换
|
||||||
|
- 支持右侧内容中面包屑的自动处理
|
||||||
|
|
||||||
|
# 代码位置
|
||||||
|
|
||||||
|
- 控制台的菜单配置`src/layouts/menu.jsx`
|
||||||
|
- 管理平台的菜单配置`src/layouts/admin-menu.jsx`
|
||||||
|
|
||||||
|
# 如何使用
|
||||||
|
|
||||||
|
- 控制台与管理平台的菜单配置,采用相同的配置结构
|
||||||
|
- 返回一个 renderMenu 函数,函数返回一个配置数组
|
||||||
|
|
||||||
|
## 一级菜单的配置
|
||||||
|
|
||||||
|
- `path`
|
||||||
|
- 一级菜单对应的路由
|
||||||
|
- `name`
|
||||||
|
- 一级菜单对应的名称
|
||||||
|
- 菜单项中显示的名称
|
||||||
|
- 面包屑中一级菜单对应的名称
|
||||||
|
- `key`
|
||||||
|
- 一级菜单对应的 ID 值
|
||||||
|
- 要求具有唯一性
|
||||||
|
- `icon`
|
||||||
|
- 一级菜单对应的图标
|
||||||
|
- 菜单完全展开时,显示图标与名称
|
||||||
|
- 菜单折叠时,只显示图标
|
||||||
|
- `hasBreadcrumb`
|
||||||
|
- 页面是否展示面包屑
|
||||||
|
- 默认展示面包屑
|
||||||
|
- 以首页为例,`hasBreadcrumb: false`
|
||||||
|
- `hasChildren`
|
||||||
|
- 一级菜单是否含有子菜单
|
||||||
|
- 默认值为`true`
|
||||||
|
- 一级菜单可以不包含二级菜单,以`首页`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
path: '/base/overview',
|
||||||
|
name: t('Home'),
|
||||||
|
key: '/home',
|
||||||
|
icon: <HomeOutlined />,
|
||||||
|
hasBreadcrumb: false,
|
||||||
|
hasChildren: false,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 一级菜单默认包含二级菜单,以`计算`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
path: '/compute',
|
||||||
|
name: t('Compute'),
|
||||||
|
key: '/compute',
|
||||||
|
icon: <DesktopOutlined />,
|
||||||
|
children: [...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 二级菜单的配置
|
||||||
|
|
||||||
|
- 二级菜单配置在一级菜单的`children`中
|
||||||
|
- 详情页、创建页面等不需要展示在菜单项中的页面,配置在二级菜单的`children`中
|
||||||
|
- 以云主机类型为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
path: '/compute/flavor',
|
||||||
|
name: t('Flavor'),
|
||||||
|
key: '/compute/flavor',
|
||||||
|
level: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: /^\/compute\/flavor\/detail\/.[^/]+$/,
|
||||||
|
name: t('Flavor Detail'),
|
||||||
|
key: 'flavor-detail',
|
||||||
|
level: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
- `path`
|
||||||
|
- 菜单对应的路由
|
||||||
|
- `name`
|
||||||
|
- 菜单对应的名称
|
||||||
|
- 菜单项中显示的名称
|
||||||
|
- 面包屑中菜单对应的名称
|
||||||
|
- `key`
|
||||||
|
- 菜单对应的 ID 值
|
||||||
|
- 要求具有唯一性
|
||||||
|
- `level`
|
||||||
|
- 二级菜单的`level=1`
|
||||||
|
- 二级菜单的`children`中的菜单配置`level=2`
|
72
docs/zh/develop/3-13-Route-introduction.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-13-Route-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
- 需要独立展示的页面均需要配置路由
|
||||||
|
- 按产品的需求,菜单下的二级页面,均需要以整页方式展示,如`计算-云主机`
|
||||||
|
- 资源列表页
|
||||||
|
- 如,云主机列表页
|
||||||
|
- 注意,详情页下的相关资源列表页不需要配置路由
|
||||||
|
- 资源详情页
|
||||||
|
- 如,云主机详情页
|
||||||
|
- 整页展示的 Form 表单
|
||||||
|
- 如,创建云主机
|
||||||
|
- 某些菜单只有一级页面,如`首页`,也需要配置路由
|
||||||
|
|
||||||
|
# 如何使用
|
||||||
|
|
||||||
|
## 二级菜单对应的路由
|
||||||
|
|
||||||
|
- 按[目录介绍](2-catalog-introduction.md)中的要求,每个菜单一级页面,在`pages`下有一个独立的文件夹,其内的`containers文件夹`放置二级页面代码,`routes文件夹`配置路由
|
||||||
|
- 配置写在`pages/xxxx/routes/index.js`中
|
||||||
|
- 路由的配置需要遵守固定格式,可参考`src/pages/compute/routes/index.js`
|
||||||
|
- 是个数组
|
||||||
|
- 数组中的每个元素,要求
|
||||||
|
- `path`, 一级菜单对应的名称,如`计算`用`compute`
|
||||||
|
- `component`,布局组件
|
||||||
|
- `auth`相关的页面,如登录,使用的是`src/layouts/User/index.jsx`组件
|
||||||
|
- 登录后展示的页面,如云主机、页面等,使用的是`src/layouts/Base/index.jsx`组件
|
||||||
|
- 该布局自动处理菜单项的展示、右侧内容头部的`header`展示、右侧内容的面包屑等
|
||||||
|
- `routes`,配置的主体内容,是个数组,每个元素要求
|
||||||
|
- 以计算的路由配置`src/pages/compute/routes/index.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{ path: `${PATH}/instance`, component: Instance, exact: true },
|
||||||
|
```
|
||||||
|
|
||||||
|
- `path`, 每个整页页面对应的路径,如`compute/instance`
|
||||||
|
- `component`,页面对应的组件,即`containers`下的组件
|
||||||
|
|
||||||
|
- 对于资源型的页面,一般会配置
|
||||||
|
- 控制台访问的列表页、详情页、复杂的创建页(简单的创建一般使用弹出窗即可)
|
||||||
|
- 管理平台访问的列表页、详情页(`path`中要求包含`-admin`或`_admin`)
|
||||||
|
- 对于详情页,我们推荐使用`id`参数项
|
||||||
|
- 以云主机`src/pages/compute/routes/index.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{ path: `${PATH}/instance`, component: Instance, exact: true },
|
||||||
|
{ path: `${PATH}/instance-admin`, component: Instance, exact: true },
|
||||||
|
{
|
||||||
|
path: `${PATH}/instance/detail/:id`,
|
||||||
|
component: InstanceDetail,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${PATH}/instance-admin/detail/:id`,
|
||||||
|
component: InstanceDetail,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{ path: `${PATH}/instance/create`, component: StepCreate, exact: true },
|
||||||
|
```
|
||||||
|
|
||||||
|
## 一级菜单对应的路由
|
||||||
|
|
||||||
|
- 一级菜单需要添加在`src/core/routes.js`
|
||||||
|
- 以计算为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
path: '/compute',
|
||||||
|
component: Compute,
|
||||||
|
},
|
||||||
|
```
|
41
docs/zh/develop/3-14-I18n-introduction.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-14-I18n-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
- 框架支持国际化,默认支持英文、中文
|
||||||
|
|
||||||
|
![i18n](/docs/zh/develop/images/i18n/i18n.png)
|
||||||
|
|
||||||
|
![english](/docs/zh/develop/images/i18n/english.png)
|
||||||
|
|
||||||
|
# 代码位置
|
||||||
|
|
||||||
|
- `src/locales/index.js`
|
||||||
|
- 英文:`src/locales/en.json`
|
||||||
|
- 中文:`src/locales/zh.json`
|
||||||
|
|
||||||
|
# 如何使用
|
||||||
|
|
||||||
|
- 代码中的需要国际化展示的字符串均使用英文,使用命令行完成字符采集后,无需更新 en.json 文件,只需要修改 zh.json 中对应的中文即可完成国际化的操作
|
||||||
|
- 对于需要国际化的字符串,使用`t`函数即可
|
||||||
|
- 以`云主机`为例,对应的国际化写法为`t('instance')`
|
||||||
|
- 注意,英文是大小写相关的
|
||||||
|
- `t`函数支持带有参数的字符串
|
||||||
|
- 参数使用`{}`标识,如
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
confirmContext = () =>
|
||||||
|
t('Are you sure to { action }?', {
|
||||||
|
action: this.actionName || this.title,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- 采集
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run i18n
|
||||||
|
```
|
||||||
|
|
||||||
|
- 采集后,`en.json`与`zh.json`文件会自动更新
|
||||||
|
- 更新中文
|
||||||
|
- 采集后,直接在`zh.json`中更新相应的中文翻译即可
|
123
docs/zh/develop/3-2-BaseTabList-introduction.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-2-BaseTabList-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
- 各可切换列表页的基类
|
||||||
|
|
||||||
|
![Tab列表页](/docs/zh/develop/images/list/tab-list.png)
|
||||||
|
|
||||||
|
- 支持切换时自动处理数据展示
|
||||||
|
|
||||||
|
# BaseTabList 代码文件
|
||||||
|
|
||||||
|
- `src/containers/TabList/index.jsx`
|
||||||
|
|
||||||
|
# BaseTabList 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 带有 Tab 切换的资源列表继承于 BaseTabList
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 需要复写的属性与函数,主要包含:
|
||||||
|
- 页面内的`Tab`配置
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 页面内的权限配置
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 处理切换 Tab 时的路由变动
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 需要复写的属性与函数
|
||||||
|
|
||||||
|
- `tabs`:
|
||||||
|
- 需要复写该函数
|
||||||
|
- 用于配置页面内的 Tab
|
||||||
|
- 每个 Tab 的配置项:
|
||||||
|
- `title`,Tab 标签上的标题
|
||||||
|
- `key`,每个 Tab 的唯一标识
|
||||||
|
- `component`,每个 Tab 对应的组件,基本都是继承于`BaseList`的资源列表组件
|
||||||
|
- 返回 Tab 配置的列表
|
||||||
|
- 页面默认显示 Tab 列表中的第一个`component`
|
||||||
|
- 以镜像`src/pages/compute/containers/Image/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get tabs() {
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: t('Current Project Image'),
|
||||||
|
key: 'project',
|
||||||
|
component: Image,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Public Image'),
|
||||||
|
key: 'public',
|
||||||
|
component: Image,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Shared Image'),
|
||||||
|
key: 'shared',
|
||||||
|
component: Image,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (this.hasAdminRole) {
|
||||||
|
tabs.push({
|
||||||
|
title: t('All Image'),
|
||||||
|
key: 'all',
|
||||||
|
component: Image,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- 以下涉及到的属性与函数,一般均不需要配置
|
||||||
|
- 目前只在 VPN 页面(`src/pages/network/containers/VPN/index.jsx`)使用,该页面使用这些配置判定权限,以及判定失败时展示使用
|
||||||
|
- `name`
|
||||||
|
- 整个 Tab 页面的全称
|
||||||
|
- 以 VPN `src/pages/network/containers/VPN/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get name() {
|
||||||
|
return t('VPN');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `checkEndpoint`
|
||||||
|
- 是否需要验证该页面对应服务的 endpoint
|
||||||
|
- 默认值是`false`
|
||||||
|
- 以 VPN `src/pages/network/containers/VPN/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get checkEndpoint() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `endpoint`
|
||||||
|
- 该页面对应服务的 endpoint
|
||||||
|
- 仅在`checkEndpoint=true`时有用
|
||||||
|
- 以 VPN `src/pages/network/containers/VPN/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get endpoint() {
|
||||||
|
return vpnEndpoint();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
- `location`
|
||||||
|
- 页面的路由信息
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `hasAdminRole`
|
||||||
|
- 登录的用户角色是否具有管理员角色
|
||||||
|
- `getUrl`
|
||||||
|
- 生成页面 Url 的函数
|
||||||
|
- 如:需要给列表页的关联资源提供跳转功能,使用该函数,可以在控制台跳转到控制台的相应地址,在管理平台跳转到管理平台的相应地址
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- 建议查看代码理解,`src/containers/TabList/index.jsx`
|
259
docs/zh/develop/3-3-BaseDetail-introduction.md
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-3-BaseDetail-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
![详情页](/docs/zh/develop/images/detail/volume.png)
|
||||||
|
|
||||||
|
- 各资源详情页的基类
|
||||||
|
- 支持返回列表页
|
||||||
|
- 支持与列表页一致的数据操作
|
||||||
|
- 支持详情页头部的展示与折叠
|
||||||
|
- 支持基于 Tab 形式展示的基本信息与相关资源信息
|
||||||
|
- 支持上下分层的展示方案
|
||||||
|
- 需要复写部分函数即可完成页面的开发
|
||||||
|
|
||||||
|
# BaseDetail 代码文件
|
||||||
|
|
||||||
|
- `src/containers/TabDetail/index.jsx`
|
||||||
|
|
||||||
|
# BaseDetail 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 资源详情继承于 BaseDetail 组件
|
||||||
|
- 代码位置:`pages/xxxx/containers/XXXX/Detail/index.jsx`
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 通常需要复写的属性与函数,主要包含:
|
||||||
|
- 详情页的权限
|
||||||
|
- 详情页的资源名称
|
||||||
|
- 详情页对应的列表页
|
||||||
|
- 详情页的操作配置
|
||||||
|
- 详情页的上方信息配置
|
||||||
|
- 详情页的下方 Tab 页面配置
|
||||||
|
- 详情页对应的`store`
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 详情页操作对应的数据
|
||||||
|
- 获取详情数据的参数
|
||||||
|
- 获取详情数据的函数
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 下方列表页数据变动时详情数据的自动刷新
|
||||||
|
- 折叠/展开头部信息
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 通常需要复写的属性与函数
|
||||||
|
|
||||||
|
- `policy`:
|
||||||
|
- 必须复写该函数
|
||||||
|
- 页面对应的权限,如果权限验证不通过,则无法请求数据。
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get policy() {
|
||||||
|
return 'volume:get';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- 必须复写该函数
|
||||||
|
- 页面资源对应的名称。
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get name() {
|
||||||
|
return t('volume');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `listUrl`
|
||||||
|
- 该详情页对应的资源列表页
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get listUrl() {
|
||||||
|
return this.getUrl('/storage/volume');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `actionConfigs`
|
||||||
|
- 配置资源的各种操作
|
||||||
|
- 对数据的操作
|
||||||
|
- 配置定义在资源的 actions 目录下
|
||||||
|
- 一般直接使用与资源列表页相一致的配置即可
|
||||||
|
- 以密钥`src/pages/compute/containers/Keypair/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import actionConfigs from '../actions';
|
||||||
|
get actionConfigs() {
|
||||||
|
return actionConfigs;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `detailInfos`
|
||||||
|
- 详情页上方的信息
|
||||||
|
- 是一个配置列表
|
||||||
|
- 每个配置
|
||||||
|
- `title`,必须项,标题
|
||||||
|
- `dataIndex`,必须项,对应于数据的 Key
|
||||||
|
- `render`,可选项,默认是基于`dataIndex`来展示内容,使用该属性,可基于`render`的结果渲染表格内容
|
||||||
|
- `valueRender`,可选项,基于`dataIndex`及`valueRender`生成展示数据
|
||||||
|
- `sinceTime`,处理时间,显示成"XX 小时前"
|
||||||
|
- `keepTime`,显示剩余时间
|
||||||
|
- `yesNo`,处理`Boolean`值,显示成“是”、“否”
|
||||||
|
- `GBValue`,处理大小,显示成"XXXGB"
|
||||||
|
- `noValue`,没有值时,显示成“-”
|
||||||
|
- `bytes`,处理大小
|
||||||
|
- `uppercase`,大写
|
||||||
|
- `formatSize`,处理大小,显示如“2.32 GB”,“56.68 MB”
|
||||||
|
- `toLocalTime`,处理时间,显示如“2021-06-17 04:13:07”
|
||||||
|
- `toLocalTimeMoment`,处理时间,显示如“2021-06-17 04:13:07”
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get detailInfos() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t('Name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Description'),
|
||||||
|
dataIndex: 'description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Shared'),
|
||||||
|
dataIndex: 'multiattach',
|
||||||
|
valueRender: 'yesNo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Status'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
render: (value) => volumeStatus[value] || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Size'),
|
||||||
|
dataIndex: 'size',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Created At'),
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
valueRender: 'toLocalTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Type'),
|
||||||
|
dataIndex: 'volume_type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Encrypted'),
|
||||||
|
dataIndex: 'encrypted',
|
||||||
|
valueRender: 'yesNo',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `tabs`
|
||||||
|
- 详情页下方的 Tab 配置
|
||||||
|
- 每个 Tab 的配置项:
|
||||||
|
- `title`,Tab 标签上的标题
|
||||||
|
- `key`,每个 Tab 的唯一标识
|
||||||
|
- `component`,每个 Tab 对应的组件,基本都是继承于`BaseList`的资源列表组件
|
||||||
|
- 返回 Tab 配置的列表
|
||||||
|
- 页面默认显示 Tab 列表中的第一个`component`
|
||||||
|
- 通常,基础信息继承于`BaseDetail`类
|
||||||
|
- 通常,详情页中的资源列表页直接复用资源列表即可,只需同步处理下列表页内的参数请求即可
|
||||||
|
- 以云硬盘详情页中的备份列表`src/pages/storage/containers/Backup/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
updateFetchParamsByPage = (params) => {
|
||||||
|
if (this.isInDetailPage) {
|
||||||
|
const { id, ...rest } = params;
|
||||||
|
return {
|
||||||
|
volume_id: id,
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get tabs() {
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: t('Detail'),
|
||||||
|
key: 'base',
|
||||||
|
component: BaseDetail,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Backup'),
|
||||||
|
key: 'backup',
|
||||||
|
component: Backup,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Snapshot'),
|
||||||
|
key: 'snapshot',
|
||||||
|
component: Snapshot,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `init`
|
||||||
|
- 配置 Store 的函数,在这个函数中配置用于处理数据请求的 Store
|
||||||
|
- 一般使用的是`new XXXStore()`形式
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
init() {
|
||||||
|
this.store = new VolumeStore();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `fetchData`
|
||||||
|
- 详情页中的获取数据的函数
|
||||||
|
- 不建议重写该方法
|
||||||
|
- 默认使用`this.store.fetchDetail`获取数据
|
||||||
|
- `updateFetchParams`
|
||||||
|
- 更新数据请求的参数
|
||||||
|
- 一般配合 store 中的`detailDidFetch`使用
|
||||||
|
- 以云主机`src/pages/compute/containers/Instance/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
updateFetchParams = (params) => ({
|
||||||
|
...params,
|
||||||
|
isRecycleBinDetail: this.isRecycleBinDetail,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `params`
|
||||||
|
- 路由带有的参数信息
|
||||||
|
- 一般用于生成页面请求 API 时的参数
|
||||||
|
- `id`
|
||||||
|
- 路由信息中的`id`
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `getUrl`
|
||||||
|
- 生成页面 Url 的函数
|
||||||
|
- 如:需要给关联资源提供跳转功能,使用该函数,可以在控制台跳转到控制台的相应地址,在管理平台跳转到管理平台的相应地址
|
||||||
|
- `routing`
|
||||||
|
- 页面对应的路由信息
|
||||||
|
- `isLoading`
|
||||||
|
- 当前页面是否在数据更新,更新时会显示 loading 样式
|
||||||
|
- `tab`
|
||||||
|
- 当前展示的下方 Tab 页面信息
|
||||||
|
- `detailData`
|
||||||
|
- 页面内展示的数据信息
|
||||||
|
- 来源于`this.store.detail`
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- 建议查看代码理解,`src/containers/TabDetail/index.jsx`
|
144
docs/zh/develop/3-4-BaseDetailInfo-introduction.md
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-4-BaseDetailInfo-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
![详情信息页](/docs/zh/develop/images/detail/image-detail-info.png)
|
||||||
|
|
||||||
|
- 各资源详情页中详情 Tab 中组件的基类
|
||||||
|
- 左右结构展示
|
||||||
|
- 以 Card 的形式展示
|
||||||
|
- 以配置 Card 的方式即可完成页面内容的展示
|
||||||
|
|
||||||
|
# BaseDetailInfo 代码文件
|
||||||
|
|
||||||
|
- `src/containers/BaseDetail/index.jsx`
|
||||||
|
|
||||||
|
# BaseDetailInfo 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 资源详情信息继承于 BaseDetailInfo
|
||||||
|
- 代码位置:`pages/xxxx/containers/XXXX/Detail/BaseDetail.jsx`
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 通常需要复写的属性与函数,主要包含:
|
||||||
|
- 左侧的 Card 列表
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 右侧的 Card 列表
|
||||||
|
- 获取数据的函数
|
||||||
|
- 展示数据的来源
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## Card 的配置
|
||||||
|
|
||||||
|
- 页面中左侧、右侧的 Card 均采用相同的配置方式
|
||||||
|
- 每个 Card 的配置如下,
|
||||||
|
- `title`,必须项,Card 的标题
|
||||||
|
- `titleHelp`, 可选项,Card 的标题旁显示的提示信息
|
||||||
|
- `render`,可选项,如果存在,则基于`render`渲染 Card 的内容
|
||||||
|
- `options`,可选项,Card 中每行的配置列表,每个 option 配置如下,
|
||||||
|
- `label`,必须项,行中的标签
|
||||||
|
- `dataIndex`,必须项,对应于`this.detailData`中的 key,默认是基于`dataIndex`展示行内的数据
|
||||||
|
- `render`,可选项,可基于`render`的结果渲染行内的内容
|
||||||
|
- `valueRender`,可选项,基于`dataIndex`及`valueRender`生成行内的展示数据
|
||||||
|
- `sinceTime`,处理时间,显示成"XX 小时前"
|
||||||
|
- `keepTime`,显示剩余时间
|
||||||
|
- `yesNo`,处理`Boolean`值,显示成“是”、“否”
|
||||||
|
- `GBValue`,处理大小,显示成"XXXGB"
|
||||||
|
- `noValue`,没有值时,显示成“-”
|
||||||
|
- `bytes`,处理大小
|
||||||
|
- `uppercase`,大写
|
||||||
|
- `formatSize`,处理大小,显示如“2.32 GB”,“56.68 MB”
|
||||||
|
- `toLocalTime`,处理时间,显示如“2021-06-17 04:13:07”
|
||||||
|
- `toLocalTimeMoment`,处理时间,显示如“2021-06-17 04:13:07”
|
||||||
|
- `copyable`,可选项,该行内的数据是否可复制,如可复制,会显示复制 icon
|
||||||
|
- 以密钥`src/pages/compute/containers/Keypair/Detail/BaseDetail.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get keypairInfoCard() {
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: t('Fingerprint'),
|
||||||
|
dataIndex: 'fingerprint',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Public Key'),
|
||||||
|
dataIndex: 'public_key',
|
||||||
|
copyable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('User ID'),
|
||||||
|
dataIndex: 'user_id',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
title: t('Keypair Info'),
|
||||||
|
options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 通常需要复写的属性与函数
|
||||||
|
|
||||||
|
- `leftCards`:
|
||||||
|
- 必须复写该函数
|
||||||
|
- 左侧展示的 Card 列表
|
||||||
|
- 以镜像`src/pages/compute/containers/Image/Detail/BaseDetail.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get leftCards() {
|
||||||
|
const cards = [this.baseInfoCard, this.securityCard];
|
||||||
|
return this.isImageDetail ? cards : [this.InstanceCard, ...cards];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `init`
|
||||||
|
- 配置 Store 的函数,在这个函数中配置用于处理数据请求的
|
||||||
|
Store,如果配置了该函数,则会在展示该页面时发起数据请求,但是有时展示该页面时,并不需要额外请求,只需要使用`this.props.detail`即可
|
||||||
|
- 一般使用的是`new XXXStore()`形式
|
||||||
|
- 以镜像`src/pages/compute/containers/Image/Detail/BaseDetail.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
init() {
|
||||||
|
this.store = new ImageStore();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `rightCards`
|
||||||
|
- 右侧展示的 Card 列表
|
||||||
|
- `fetchData`
|
||||||
|
- 获取 Card 数据的函数
|
||||||
|
- 一般不需要复写该函数
|
||||||
|
- `detailData`
|
||||||
|
- 页面 Card 的数据来源
|
||||||
|
- 默认是`this.props.detail || toJS(this.store.detail)`
|
||||||
|
- 一般不需要复写该函数
|
||||||
|
- 以云硬盘的 Qos`src/pages/storage/containers/VolumeType/QosSpec/Detail/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get detailData() {
|
||||||
|
return this.store.detail.qos_specs;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- 路由信息中的`id`
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `getUrl`
|
||||||
|
- 生成页面 Url 的函数
|
||||||
|
- 如:需要给关联资源提供跳转功能,使用该函数,可以在控制台跳转到控制台的相应地址,在管理平台跳转到管理平台的相应地址
|
||||||
|
- `routing`
|
||||||
|
- 页面对应的路由信息
|
||||||
|
- `isLoading`
|
||||||
|
- 当前页面是否在数据更新,更新时会显示 loading 样式
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- 建议查看代码理解,`src/containers/BaseDetail/index.jsx`
|
554
docs/zh/develop/3-5-BaseStore-introduction.md
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-5-BaseStore-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
- 数据请求的处理
|
||||||
|
- 支持获取全部数据
|
||||||
|
- 支持分页获取数据
|
||||||
|
- 支持对数据的各种请求处理(PUT、POST、GET、PATCH、DELETE、HEAD 等)
|
||||||
|
|
||||||
|
# BaseStore 代码文件
|
||||||
|
|
||||||
|
- `src/stores/base.js`
|
||||||
|
|
||||||
|
# BaseStore 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 资源数据的 Store 继承于 BaseStore 类
|
||||||
|
- 代码位置:`src/stores/xxx/xxx.js`,如云主机对应的 store 在`src/stores/nova/instance.js`
|
||||||
|
- 只需要复写部分函数即可完成数据的请求操作
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 通常需要复写的属性与函数,主要包含:
|
||||||
|
- 与生成 url 相关的属性与函数
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 列表数据的再加工
|
||||||
|
- 详情数据的再加工
|
||||||
|
- 请求参数的处理
|
||||||
|
- url 的处理
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 清空数据
|
||||||
|
- 封装数据时对项目信息的处理
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 处理分页数据的`marker`
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 名词说明
|
||||||
|
|
||||||
|
- 前端分页
|
||||||
|
- 一次性从后端获取所有列表数据
|
||||||
|
- 前端基于获取到的数据总量、页面内配置的当前页数、每页数量来展示数据(`BaseList`组件处理)
|
||||||
|
- 后端分页
|
||||||
|
- 以当前页号、每页数量向后端请求数据
|
||||||
|
- 前端排序
|
||||||
|
- 使用前端分页时,按设定的排序信息对所有数据排序
|
||||||
|
- 使用后端分页时,按设定的排序信息对当前页内的数据排序
|
||||||
|
- 后端排序
|
||||||
|
- 以当前页号、每页数量、当前排序信息向后端请求数据
|
||||||
|
- 不存在前端分页+后端排序这种组合方式
|
||||||
|
|
||||||
|
## 通常需要复写的属性与函数
|
||||||
|
|
||||||
|
- `module`:
|
||||||
|
- 必须复写该函数
|
||||||
|
- 资源对应的模块
|
||||||
|
- 该函数用于生成请求的 url
|
||||||
|
- 以云主机`src/stores/nova/instance.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get module() {
|
||||||
|
return 'servers';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `apiVersion`
|
||||||
|
- 必须复写该函数
|
||||||
|
- 资源对应的 api 前缀
|
||||||
|
- 因所有的请求需要由服务端转发,所以,api 的前缀需要基于`profile`内的信息生成
|
||||||
|
- 以云主机`src/stores/nova/instance.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get apiVersion() {
|
||||||
|
return novaBase();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `responseKey`
|
||||||
|
- 必须复写该函数
|
||||||
|
- 用于生成数据返回的 key,创建的 key 等
|
||||||
|
- 以云主机`src/stores/nova/instance.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get responseKey() {
|
||||||
|
return 'server';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
![请求](/docs/zh/develop/images/store/response-key.png)
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `listDidFetch`
|
||||||
|
- 列表数据二次加工使用的函数
|
||||||
|
- 可请求其他 API 后,整合数据
|
||||||
|
- 可过滤数据
|
||||||
|
- 请求某个指定云硬盘的快照列表时,可以基于`filters`中的参数再次过滤数据
|
||||||
|
- 以云硬盘快照`src/stores/cinder/snapshot.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async listDidFetch(items, allProjects, filters) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
const { id } = filters;
|
||||||
|
const datas = id ? items.filter((it) => it.volume_id === id) : items;
|
||||||
|
return datas;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 如果需要显示加密信息,需要发起额外请求后,整合数据
|
||||||
|
- 以云硬盘类型`src/stores/cinder/volume-type.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async listDidFetch(items, allProjects, filters) {
|
||||||
|
const { showEncryption } = filters;
|
||||||
|
if (items.length === 0) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
if (!showEncryption) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
const promiseList = items.map((i) =>
|
||||||
|
request.get(`${this.getDetailUrl({ id: i.id })}/encryption`)
|
||||||
|
);
|
||||||
|
const encryptionList = await Promise.all(promiseList);
|
||||||
|
const result = items.map((i) => {
|
||||||
|
const { id } = i;
|
||||||
|
const encryption = encryptionList.find((e) => e.volume_type_id === id);
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
encryption,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `detailDidFetch`
|
||||||
|
- 详情数据二次加工使用的函数
|
||||||
|
- 可请求其他 API 后,整合数据
|
||||||
|
- 以云硬盘快照`src/stores/cinder/snapshot.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async detailDidFetch(item) {
|
||||||
|
const { volume_id } = item;
|
||||||
|
const volumeUrl = `${cinderBase()}/${
|
||||||
|
globals.user.project.id
|
||||||
|
}/volumes/${volume_id}`;
|
||||||
|
const { volume } = await request.get(volumeUrl);
|
||||||
|
item.volume = volume;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `listResponseKey`
|
||||||
|
- 列表数据的返回 Key
|
||||||
|
- 默认是`${this.responseKey}s`
|
||||||
|
- 以云硬盘快照`src/stores/cinder/snapshot.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get responseKey() {
|
||||||
|
return 'snapshot';
|
||||||
|
}
|
||||||
|
|
||||||
|
get listResponseKey() {
|
||||||
|
return 'volume_snapshots';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getListUrl`
|
||||||
|
- 请求数据使用的 url
|
||||||
|
- 前端分页请求列表数据(即一次性获取所有数据)时,优先使用`this.getListDetailUrl()`
|
||||||
|
- 后端分页请求列表数据时,按优先级`this.getListPageUrl()` > `this.getListDetailUrl()` > `this.getListUrl()`
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getListUrl = () => `${this.apiVersion}/${this.module}`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以 Heat 的堆栈的日志`src/stores/heat/event.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getListUrl = ({ id, name }) =>
|
||||||
|
`${this.apiVersion}/${this.module}/${name}/${id}/events`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getListDetailUrl`
|
||||||
|
- 请求数据使用的 url
|
||||||
|
- 前端分页请求列表数据(即一次性获取所有数据)时,优先使用`this.getListDetailUrl()`
|
||||||
|
- 后端分页请求列表数据时,按优先级`this.getListPageUrl()` > `this.getListDetailUrl()` > `this.getListUrl()`
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getListDetailUrl = () => '';
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以云硬盘`src/stores/cinder/volume.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getListDetailUrl = () => `${skylineBase()}/extension/volumes`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getListPageUrl`
|
||||||
|
- 后端分页数据使用的 url
|
||||||
|
- 后端分页请求列表数据时,按优先级`this.getListPageUrl()` > `this.getListDetailUrl()` > `this.getListUrl()`
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getListPageUrl = () => '';
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以云硬盘`src/stores/cinder/volume.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getListPageUrl = () => `${skylineBase()}/extension/volumes`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getDetailUrl`
|
||||||
|
- 详情数据对应的 url
|
||||||
|
- 使用 rest 风格的 API,所以,该 url 也是 put, delete, patch 对应的 url
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getDetailUrl = ({ id }) => `${this.getListUrl()}/${id}`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以堆栈`src/stores/heat/stack.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getDetailUrl = ({ id, name }) => `${this.getListUrl()}/${name}/${id}`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `needGetProject`
|
||||||
|
- 对服务端返回的数据是否需要二次加工其中的项目信息
|
||||||
|
- 一般 Openstack API 返回的数据只有`project_id`信息,按页面展示的需求,在管理平台需要展示项目名称
|
||||||
|
- 默认值是`true`
|
||||||
|
- 以元数据`src/stores/glance/metadata.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get needGetProject() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `mapper`
|
||||||
|
- 对服务端返回的列表、详情数据做二次加工
|
||||||
|
- 一般是为了更便捷的在资源列表、资源详情中展示数据使用
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get mapper() {
|
||||||
|
return (data) => data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以云硬盘`src/stores/cinder/volume.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get mapper() {
|
||||||
|
return (volume) => ({
|
||||||
|
...volume,
|
||||||
|
disk_tag: isOsDisk(volume) ? 'os_disk' : 'data_disk',
|
||||||
|
description: volume.description || (volume.origin_data || {}).description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `mapperBeforeFetchProject`
|
||||||
|
- 在处理项目信息前,对服务端返回的列表、详情数据做二次加工
|
||||||
|
- 一般是为了处理返回数据中的项目信息使用
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get mapperBeforeFetchProject() {
|
||||||
|
return (data) => data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以镜像`src/stores/glance/image.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get mapperBeforeFetchProject() {
|
||||||
|
return (data, filters, isDetail) => {
|
||||||
|
if (isDetail) {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
project_id: data.owner,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
project_id: data.owner,
|
||||||
|
project_name: data.owner_project_name || data.project_name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `paramsFunc`
|
||||||
|
- 前端分页请求(即`fetchList`)时,对请求参数的更新
|
||||||
|
- 默认是对从资源列表代码(`pages/xxxx/xxx/index.jsx`)使用`fetchList`时,参数的过滤
|
||||||
|
- 默认值
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get paramsFunc() {
|
||||||
|
if (this.filterByApi) {
|
||||||
|
return (params) => params;
|
||||||
|
}
|
||||||
|
return (params) => {
|
||||||
|
const reservedKeys = [
|
||||||
|
'all_data',
|
||||||
|
'all_projects',
|
||||||
|
'device_id',
|
||||||
|
'network_id',
|
||||||
|
'floating_network_id',
|
||||||
|
'start_at_gt',
|
||||||
|
'start_at_lt',
|
||||||
|
'binary',
|
||||||
|
'fixed_ip_address',
|
||||||
|
'device_owner',
|
||||||
|
'project_id',
|
||||||
|
'type',
|
||||||
|
'sort',
|
||||||
|
'security_group_id',
|
||||||
|
'id',
|
||||||
|
'security_group_id',
|
||||||
|
'owner_id',
|
||||||
|
'status',
|
||||||
|
'fingerprint',
|
||||||
|
'resource_types',
|
||||||
|
'floating_ip_address',
|
||||||
|
'uuid',
|
||||||
|
'loadbalancer_id',
|
||||||
|
'ikepolicy_id',
|
||||||
|
'ipsecpolicy_id',
|
||||||
|
'endpoint_id',
|
||||||
|
'peer_ep_group_id',
|
||||||
|
'local_ep_group_id',
|
||||||
|
'vpnservice_id',
|
||||||
|
];
|
||||||
|
const newParams = {};
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
if (reservedKeys.indexOf(key) >= 0) {
|
||||||
|
newParams[key] = params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newParams;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以云硬盘`src/stores/cinder/volume.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get paramsFunc() {
|
||||||
|
return (params) => {
|
||||||
|
const { serverId, ...rest } = params;
|
||||||
|
return rest;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `paramsFuncPage`
|
||||||
|
- 后端分页请求(即`fetchListByPage`)时,对请求参数的更新
|
||||||
|
- 默认是对从资源列表代码(`pages/xxxx/xxx/index.jsx`)使用`fetchListByPage`时,参数的过滤
|
||||||
|
- 默认值
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get paramsFuncPage() {
|
||||||
|
return (params) => {
|
||||||
|
const { current, ...rest } = params;
|
||||||
|
return rest;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以云硬盘类型`src/stores/cinder/volume-type.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get paramsFuncPage() {
|
||||||
|
return (params) => {
|
||||||
|
const { current, showEncryption, ...rest } = params;
|
||||||
|
return rest;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fetchListByLimit`
|
||||||
|
- 前端分页请求所有数据时,是否要基于`limit`发起多个请求,最终实现所有数据的获取
|
||||||
|
- Openstack API 默认返回的是 1000 个数据,对于某些资源数据很多的情况,需要使用该配置以便获取到所有数据
|
||||||
|
- 默认值是`false`
|
||||||
|
- 以镜像`src/stores/glance/image.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get fetchListByLimit() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `markerKey`
|
||||||
|
- 后端分页请求数据时,marker 的来源
|
||||||
|
- 因为对 Openstack 的请求是由后端转发的,所以并没有直接使用列表数据返回的 Openstack 拼接好的下一页数据应该使用的 Url,而是根据返回的数据,解析出`marker`
|
||||||
|
- 默认值是`id`
|
||||||
|
- 通常不需要复写
|
||||||
|
- 以密钥`src/stores/nova/keypair.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get markerKey() {
|
||||||
|
return 'keypair.name';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `requestListByMarker`
|
||||||
|
- 后端分页时,使用`marker`请求分页下的数据
|
||||||
|
- 通常不需要复写
|
||||||
|
- 默认值是
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async requestListByMarker(url, params, limit, marker) {
|
||||||
|
const newParams = {
|
||||||
|
...params,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
if (marker) {
|
||||||
|
newParams.marker = marker;
|
||||||
|
}
|
||||||
|
return request.get(url, newParams);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以云主机组`src/stores/nova/server-group.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async requestListByMarker(url, params, limit, marker) {
|
||||||
|
const newParams = {
|
||||||
|
...params,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
if (marker) {
|
||||||
|
newParams.offset = marker;
|
||||||
|
}
|
||||||
|
return request.get(url, newParams);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `requestListAllByLimit`
|
||||||
|
- 当`this.fetchListByLimit=true`时,前端分页使用该方法获取所有数据
|
||||||
|
- 通常不需要复写
|
||||||
|
- `updateUrl`
|
||||||
|
- 更新列表数据请求的 url
|
||||||
|
- 不常用
|
||||||
|
- `updateParamsSortPage`
|
||||||
|
- 使用后端排序时,对排序参数的处理
|
||||||
|
- 使用后端排序时,会在资源列表代码`pages/xxx/XXX/index.jsx`中自动生成相应的请求参数,store 对这些参数往往需要再次整理,否则会不符合 API 的参数要求
|
||||||
|
- 以云硬盘`src/stores/cinder/volume.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
updateParamsSortPage = (params, sortKey, sortOrder) => {
|
||||||
|
if (sortKey && sortOrder) {
|
||||||
|
params.sort_keys = sortKey;
|
||||||
|
params.sort_dirs = sortOrder === 'descend' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `listFilterByProject`
|
||||||
|
- 列表数据是否需要基于项目信息过滤
|
||||||
|
- `admin`权限下的部分 Openstack 资源(如`neutron`),会默认返回所有项目的数据,所以在控制台展示资源时,会根据该配置过滤数据
|
||||||
|
- 默认值是`false`
|
||||||
|
- 以 VPN`src/stores/neutron/vpn-service.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get listFilterByProject() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fetchList`
|
||||||
|
- `pages`下的列表页通常使用`this.store.fetchList`来获取前端分页数据
|
||||||
|
- 不建议复写该函数,如果需要再加工数据,建议使用`listDidFetch`
|
||||||
|
- 该函数会更新`this.list`属性中的相关数据,`pages`下的资源列表组件也是基于`this.list`进行数据展示
|
||||||
|
- `fetchListByPage`
|
||||||
|
- `pages`下的列表页通常使用`this.store.fetchList`来获取后端分页数据
|
||||||
|
- 不建议复写该函数,如果需要再加工数据,建议使用`listDidFetch`
|
||||||
|
- 该函数会更新`this.list`属性中的相关数据,`pages`下的资源列表组件也是基于`this.list`进行数据展示
|
||||||
|
- `getCountForPage`
|
||||||
|
- 获取列表数据的总量
|
||||||
|
- 通常在后端分页时可复写
|
||||||
|
- `getDetailParams`
|
||||||
|
- 更新详情数据请求时的参数
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getDetailParams = () => undefined;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fetchDetail`
|
||||||
|
- `pages`下的详情页通常使用`this.store.fetchDetail`来获取详情数据
|
||||||
|
- 通常不需要复写
|
||||||
|
- 数据再加工通常是重写`mapper`或`detailDidFetch`
|
||||||
|
- `create`
|
||||||
|
- 创建资源
|
||||||
|
- 使用`POST`api
|
||||||
|
- 通常不需要复写
|
||||||
|
- 使用`this.submitting`保证在发送请求时页面处于`loading`状态
|
||||||
|
- `edit`
|
||||||
|
- 更新资源
|
||||||
|
- 使用`PUT`api
|
||||||
|
- 通常不需要复写
|
||||||
|
- 使用`this.submitting`保证在发送请求时页面处于`loading`状态
|
||||||
|
- `patch`
|
||||||
|
- 更新资源
|
||||||
|
- 使用`PATCH`api
|
||||||
|
- 通常不需要复写
|
||||||
|
- 使用`this.submitting`保证在发送请求时页面处于`loading`状态
|
||||||
|
- `delete`
|
||||||
|
- 删除资源
|
||||||
|
- 使用`DELETE`api
|
||||||
|
- 通常不需要复写
|
||||||
|
- 使用`this.submitting`保证在发送请求时页面处于`loading`状态
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `submitting`
|
||||||
|
- 用于数据创建、数据更新时
|
||||||
|
- 依据请求的响应变更`this.isSubmitting`,对应的 Form,列表页等会展示 Loading 状态
|
||||||
|
- `currentProject`
|
||||||
|
- 当前用户登录的项目 ID
|
||||||
|
- `itemInCurrentProject`
|
||||||
|
- 数据是否属于当前用户登录的项目
|
||||||
|
- `listDidFetchProject`
|
||||||
|
- 对列表数据添加项目信息
|
||||||
|
- `requestListAll`
|
||||||
|
- 前端分页获取所有数据
|
||||||
|
- `requestListByPage`
|
||||||
|
- 后端分页所有当前页的数据
|
||||||
|
- `pureFetchList`
|
||||||
|
- 列表数据的请求函数
|
||||||
|
- 返回原始数据,不会对 API 的返回数据做加工
|
||||||
|
- `parseMarker`
|
||||||
|
- 使用后端分页时,从返回数据中解析出`marker`,用于请求上一页、下一页数据时使用
|
||||||
|
- `updateMarker`
|
||||||
|
- 更新`list`的`markers`
|
||||||
|
- `list.markers`是个数组,每个元素对应于`下标+1`页的`marker`
|
||||||
|
- `getMarker`
|
||||||
|
- 获取指定页对应的`marker`
|
||||||
|
- `getListDataFromResult`
|
||||||
|
- 从 API 的返回值中取出列表数据
|
||||||
|
- 利用`this.listResponseKey`获取
|
||||||
|
- `setSelectRowKeys`
|
||||||
|
- 对`pages`下的资源列表组件列表中数据项的选中记录
|
||||||
|
- `clearData`
|
||||||
|
- 清空`list`数据
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- 建议查看代码理解,`src/stores/base.js`
|
391
docs/zh/develop/3-6-FormAction-introduction.md
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-6-FormAction-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
![Form单页](/docs/zh/develop/images/form/page.png)
|
||||||
|
|
||||||
|
- 操作按钮点击后,单页显示 Form 表单
|
||||||
|
- 有独立的路由可供访问
|
||||||
|
- 一般用于创建资源,或是表单内容较多的 Form
|
||||||
|
- 点击`确认`按钮后,会根据请求的发送情况,展示`loading`状态,请求成功后,会自动跳转到相应的资源列表页
|
||||||
|
- 点击`取消`按钮后,会自动跳转到相应的资源列表页
|
||||||
|
- 如果请求发送成功,会在右上角展示操作成功的提示信息,该提示信息几秒后可自动消失
|
||||||
|
|
||||||
|
![Form单页](/docs/zh/develop/images/form/create-success.png)
|
||||||
|
|
||||||
|
- 如果请求发送失败,会在表单页的右上角展示错误信息,该提示信息只有点击关闭按钮后才可消失
|
||||||
|
|
||||||
|
# FormAction 代码文件
|
||||||
|
|
||||||
|
- `src/containers/Action/FormAction/index.jsx`
|
||||||
|
|
||||||
|
# FormAction 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 单页表单都继承于 FormAction 组件
|
||||||
|
- 代码位置:`pages/xxxx/containers/XXXX/actions/xxx.jsx`
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 必须复写的属性与函数,主要包含:
|
||||||
|
- 操作的 ID
|
||||||
|
- 操作的标题
|
||||||
|
- 页面对应的路径
|
||||||
|
- 资源列表页面对应的路径
|
||||||
|
- 操作对应的权限
|
||||||
|
- 对是否禁用操作的判定
|
||||||
|
- 表单项的配置
|
||||||
|
- 发送请求的函数
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 表单的默认值
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 对请求状态的展示
|
||||||
|
- 对请求结果的展示
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 必须复写的属性与函数
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- 静态属性
|
||||||
|
- 资源操作的 ID
|
||||||
|
- 需要具有唯一性,只针对资源的`actions`中的所有操作具有唯一性即可
|
||||||
|
- 必须复写该属性
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static id = 'volume-create';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `title`
|
||||||
|
- 静态属性
|
||||||
|
- 资源操作的标题
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static title = t('Create Volume');
|
||||||
|
```
|
||||||
|
|
||||||
|
- `path`
|
||||||
|
- 资源操作的对应的路由
|
||||||
|
- 静态属性或静态函数
|
||||||
|
- 静态函数时,参数为
|
||||||
|
- 参数`item`,资源列表中的条目数据
|
||||||
|
- 参数`containerProps`,父级 container(即按钮所在资源列表页面)的`props`属性
|
||||||
|
- 以创建镜像`src/pages/compute/containers/Image/actions/Create.jsx`为例
|
||||||
|
- 管理平台访问的路径是`/compute/image-admin/create`
|
||||||
|
- 控制台访问的路径是`/compute/image/create`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static path = (_, containerProp) => {
|
||||||
|
const { isAdminPage } = containerProp;
|
||||||
|
return isAdminPage
|
||||||
|
? '/compute/image-admin/create'
|
||||||
|
: '/compute/image/create';
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- 静态属性,以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static path = '/storage/volume/create';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `policy`
|
||||||
|
- 静态属性
|
||||||
|
- 页面对应的权限,如果权限验证不通过,则不会在资源列表页面显示该操作按钮
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static policy = 'volume:create';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `allowed`
|
||||||
|
- 静态函数
|
||||||
|
- 判定操作是否需要被禁用
|
||||||
|
- 返回`Promise`
|
||||||
|
- 不需用禁用的按钮,直接写作
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static allowed() {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 参数`item`,资源列表中的条目数据,一般用在资源列表中的条目的操作判定
|
||||||
|
- 参数`containerProps`,父级 container(即按钮所在资源列表页面)的`props`属性,一般用在详情页下相关资源的操作判定
|
||||||
|
- 以创建用户`src/pages/identity/containers/User/actions/Create.jsx`为例
|
||||||
|
- 如果是域详情中的用户列表,则不展示创建用户按钮
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static allowed(item, containerProps) {
|
||||||
|
const {
|
||||||
|
match: { path },
|
||||||
|
} = containerProps;
|
||||||
|
if (path.indexOf('domain-admin/detail') >= 0) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- 该操作对应的名称
|
||||||
|
- 在请求后提示语中使用该名称
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get name() {
|
||||||
|
return t('create volume');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `listUrl`
|
||||||
|
- 该操作对应的资源列表页
|
||||||
|
- 操作请求成功后,会自动进入到资源列表页
|
||||||
|
- 以云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get listUrl() {
|
||||||
|
return this.getUrl('/storage/volume');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `formItems`
|
||||||
|
- 该操作表单对应的表单项配置列表
|
||||||
|
- 每个表单项的配置信息可参考[3-10-FormItem 介绍](3-10-FormItem-introduction.md)
|
||||||
|
- 以创建域`src/pages/identity/containers/Domain/actions/Create.jsx`为例
|
||||||
|
- 表单包含名称、描述、状态
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get formItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: t('Name'),
|
||||||
|
type: 'input',
|
||||||
|
placeholder: t('Please input name'),
|
||||||
|
required: true,
|
||||||
|
help: t('The name cannot be modified after creation'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: t('Description'),
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enabled',
|
||||||
|
label: t('Status'),
|
||||||
|
type: 'radio',
|
||||||
|
optionType: 'default',
|
||||||
|
options: statusTypes,
|
||||||
|
required: true,
|
||||||
|
isWrappedValue: true,
|
||||||
|
help: t(
|
||||||
|
'Forbidden the domain will have a negative impact, all project and user in domain will be forbidden'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `onSubmit`
|
||||||
|
- 该操作的请求函数
|
||||||
|
- 操作请求成功后,会自动进入到资源列表页
|
||||||
|
- 操作失败后,会在表单页显示错误提示
|
||||||
|
- 返回`Promise`
|
||||||
|
- 返回表单对应的`store`中的函数
|
||||||
|
- 以创建域`src/pages/identity/containers/Domain/actions/Create.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
onSubmit = (values) => {
|
||||||
|
values.enabled = values.enabled.value;
|
||||||
|
return this.store.create(values);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `init`
|
||||||
|
- 初始化操作
|
||||||
|
- 在其中定义`this.store`,`loading`状态的展示是基于`this.store.isSubmitting`
|
||||||
|
- 在其中调用获取表单所需其他数据的函数
|
||||||
|
- 对`this.state`中属性的更新
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
- 获取配额信息、可用域数据、镜像数据、云硬盘类型
|
||||||
|
- 更新`this.state`中的初始值
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
init() {
|
||||||
|
this.snapshotStore = globalSnapshotStore;
|
||||||
|
this.imageStore = globalImageStore;
|
||||||
|
this.volumeStore = globalVolumeStore;
|
||||||
|
this.volumeTypeStore = globalVolumeTypeStore;
|
||||||
|
this.backupstore = globalBackupStore;
|
||||||
|
this.getQuota();
|
||||||
|
this.getAvailZones();
|
||||||
|
this.getImages();
|
||||||
|
this.getVolumeTypes();
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
count: 1,
|
||||||
|
sharedDisabled: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `defaultValue`
|
||||||
|
- 表单的初始值
|
||||||
|
- 默认值是`{}`
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
- 设置表单的默认源、大小、项目、可用域、云硬盘类型
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get defaultValue() {
|
||||||
|
const size = this.quotaIsLimit && this.maxSize < 10 ? this.maxSize : 10;
|
||||||
|
const { initVolumeType } = this.state;
|
||||||
|
const values = {
|
||||||
|
source: this.sourceTypes[0],
|
||||||
|
size,
|
||||||
|
project: this.currentProjectName,
|
||||||
|
availableZone: (this.availableZones[0] || []).value,
|
||||||
|
volume_type: initVolumeType,
|
||||||
|
};
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `nameForStateUpdate`
|
||||||
|
- 表单项内容变动时,更新到`this.state`中的表单键值对
|
||||||
|
- 这些存储到`this.store`中的键值对往往会影响表单项的展示,需要配合`get formItems`中的代码使用
|
||||||
|
- 如展开、隐藏更多配置项
|
||||||
|
- 如某些表单项必填性的变动
|
||||||
|
- 默认对`radio`, `more`类型的表单项的变动保存到`this.state`中
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
- 当`source=image`时,展示镜像选择列表,并基于镜像的选择,设置云硬盘容量的最小值
|
||||||
|
- 当`source=snapshot`时,展示云硬盘快照列表,并基于镜像的选择,设置云硬盘容量的最小值
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get nameForStateUpdate() {
|
||||||
|
return ['source', 'image', 'snapshot'];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `renderFooterLeft`
|
||||||
|
- 对表单底部左侧内部的渲染
|
||||||
|
- 默认返回`null`
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
- 展示批量创建的数量
|
||||||
|
- 基于输入的数量与剩余配额判定当前表单是否正确
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { count = 1 } = this.state;
|
||||||
|
const configs = {
|
||||||
|
min: 1,
|
||||||
|
max: 100,
|
||||||
|
precision: 0,
|
||||||
|
onChange: this.onCountChange,
|
||||||
|
formatter: (value) => `$ ${value}`.replace(/\D/g, ''),
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>{t('Count')}</span>
|
||||||
|
<InputNumber
|
||||||
|
{...configs}
|
||||||
|
value={count}
|
||||||
|
className={classnames(styles.input, 'volume-count')}
|
||||||
|
/>
|
||||||
|
{this.renderBadge()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
- `errorText`
|
||||||
|
- 错误信息的展示
|
||||||
|
- 一般不需要复写
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
- 展示了配额验证不通过的错误信息,当配额验证不通过时,并不发送请求,而是直接展示了错误提示信息
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get errorText() {
|
||||||
|
const { status } = this.state;
|
||||||
|
if (status === 'error') {
|
||||||
|
return t(
|
||||||
|
'Unable to create volume: insufficient quota to create resources.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return super.errorText;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `instanceName`
|
||||||
|
- 请求发送后,提示信息中的资源名称
|
||||||
|
- 默认值为`this.values.name`
|
||||||
|
- 以创建云硬盘`src/pages/storage/containers/Volume/actions/Create/index.jsx`为例
|
||||||
|
- 如果是批量创建云硬盘,则按`${name}-${index + 1}`的形式展示名称
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get instanceName() {
|
||||||
|
const { name } = this.values || {};
|
||||||
|
const { count = 1 } = this.state;
|
||||||
|
if (count === 1) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return new Array(count)
|
||||||
|
.fill(count)
|
||||||
|
.map((_, index) => `${name}-${index + 1}`)
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `labelCol`
|
||||||
|
- 配置表单左侧标签的布局
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get labelCol() {
|
||||||
|
return {
|
||||||
|
xs: { span: 5 },
|
||||||
|
sm: { span: 3 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以创建镜像`src/pages/compute/containers/Image/actions/Create.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get labelCol() {
|
||||||
|
return {
|
||||||
|
xs: { span: 6 },
|
||||||
|
sm: { span: 5 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `wrapperCol`
|
||||||
|
- 配置表单右侧内容的布局
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get wrapperCol() {
|
||||||
|
return {
|
||||||
|
xs: { span: 10 },
|
||||||
|
sm: { span: 8 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `values`
|
||||||
|
- 表单验证成功后,更新的表单值
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `getUrl`
|
||||||
|
- 生成页面 Url 的函数
|
||||||
|
- 如:需要给关联资源提供跳转功能,使用该函数,可以在控制台跳转到控制台的相应地址,在管理平台跳转到管理平台的相应地址
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- `FormAction`继承于`BaseForm`
|
||||||
|
- 建议查看代码理解,`src/components/Form/index.jsx`
|
387
docs/zh/develop/3-7-ModalAction-introduction.md
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-7-ModalAction-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
![弹窗型表单](/docs/zh/develop/images/form/modal.png)
|
||||||
|
|
||||||
|
- 操作按钮点击后,弹窗显示表单
|
||||||
|
- 点击`确认`按钮后,会根据请求的发送情况,展示`loading`状态
|
||||||
|
- 点击`取消`按钮后,弹窗消失
|
||||||
|
- 如果请求发送成功,会在右上角展示操作成功的提示信息,该提示信息几秒后可自动消失
|
||||||
|
- 如果请求发送失败,会在表单页的右上角展示错误信息,该提示信息只有点击关闭按钮后才可消失
|
||||||
|
- 支持批量操作,在表格中选中多个条目后,可点击表格上方的操作按钮,进行批量操作
|
||||||
|
|
||||||
|
# ModalAction 代码文件
|
||||||
|
|
||||||
|
- `src/containers/Action/ModalAction/index.jsx`
|
||||||
|
|
||||||
|
# ModalAction 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 弹窗型表单都继承于 ModalAction 组件
|
||||||
|
- 代码位置:`pages/xxxx/containers/XXXX/actions/xxx.jsx`
|
||||||
|
- 对于表单内容比较少的情况,通常是使用弹窗型的表单形式
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 必须复写的属性与函数,主要包含:
|
||||||
|
- 操作的 ID
|
||||||
|
- 操作的标题
|
||||||
|
- 操作对应的权限
|
||||||
|
- 对是否禁用操作的判定
|
||||||
|
- 表单项的配置
|
||||||
|
- 发送请求的函数
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 表单的默认值
|
||||||
|
- 表单的尺寸
|
||||||
|
- 表单中右侧标题与左侧表单主题的布局
|
||||||
|
- 是否是异步操作
|
||||||
|
- 资源的名称
|
||||||
|
- 请求结果提示语中是否要展示资源名称
|
||||||
|
- 操作按钮上的文字
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 对请求状态的展示
|
||||||
|
- 对请求结果的展示
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 必须复写的属性与函数
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- 静态属性
|
||||||
|
- 资源操作的 ID
|
||||||
|
- 需要具有唯一性,只针对资源的`actions`中的所有操作具有唯一性即可
|
||||||
|
- 必须复写该属性
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static id = 'attach-volume';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `title`
|
||||||
|
- 静态属性
|
||||||
|
- 资源操作的标题
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static title = t('Attach Volume');
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- 该操作对应的名称
|
||||||
|
- 在请求后提示语中使用该名称
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get name() {
|
||||||
|
return t('Attach volume');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `policy`
|
||||||
|
- 静态属性
|
||||||
|
- 操作对应的权限,如果权限验证不通过,则不会在资源列表页面显示该操作按钮
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static policy = 'os_compute_api:os-volumes-attachments:create';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `allowed`
|
||||||
|
- 静态函数
|
||||||
|
- 判定操作是否需要被禁用
|
||||||
|
- 返回`Promise`
|
||||||
|
- 不需用禁用的按钮,直接写作
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static allowed() {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 参数`item`,资源列表中的条目数据,一般用在资源列表中的条目的操作判定
|
||||||
|
- 参数`containerProps`,父级 container(即按钮所在资源列表页面)的`props`属性,一般用在详情页下相关资源的操作判定
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
- 管理平台不展示该操作按钮
|
||||||
|
- 当云主机满足:运行中、不处于删除中、未锁定、不是裸机 时,才会展示按操作按钮
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static allowed = (item, containerProps) => {
|
||||||
|
const { isAdminPage } = containerProps;
|
||||||
|
return Promise.resolve(
|
||||||
|
!isAdminPage &&
|
||||||
|
isActive(item) &&
|
||||||
|
isNotDeleting(item) &&
|
||||||
|
isNotLocked(item) &&
|
||||||
|
!isIronicInstance(item)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `formItems`
|
||||||
|
- 该操作表单对应的表单项配置列表
|
||||||
|
- 每个表单项的配置信息可参考[3-10-FormItem 介绍](3-10-FormItem-introduction.md)
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
- 表单项包含:云主机的名称展示、云硬盘的选择
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get formItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'instance',
|
||||||
|
label: t('Instance'),
|
||||||
|
type: 'label',
|
||||||
|
iconType: 'instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'volume',
|
||||||
|
label: t('Volume'),
|
||||||
|
type: 'volume-select-table',
|
||||||
|
tip: multiTip,
|
||||||
|
isMulti: false,
|
||||||
|
required: true,
|
||||||
|
serverId: this.item.id,
|
||||||
|
disabledFunc: (record) => {
|
||||||
|
const diskFormat = _get(
|
||||||
|
record,
|
||||||
|
'origin_data.volume_image_metadata.disk_format'
|
||||||
|
);
|
||||||
|
return diskFormat === 'iso';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `onSubmit`
|
||||||
|
- 该操作的请求函数
|
||||||
|
- 操作请求成功后,弹窗会消失,并显示成功提示,几秒后提示会消失
|
||||||
|
- 操作失败后,弹窗会消失,并显示错误提示,需要手动关闭提示,提示才会消失
|
||||||
|
- 返回`Promise`
|
||||||
|
- 返回表单对应的`store`中的函数
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
onSubmit = (values) => {
|
||||||
|
const { volume } = values;
|
||||||
|
const { id } = this.item;
|
||||||
|
const volumeId = volume.selectedRowKeys[0];
|
||||||
|
const body = {
|
||||||
|
volumeAttachment: {
|
||||||
|
volumeId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return this.store.attachVolume({ id, body });
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `init`
|
||||||
|
- 初始化操作
|
||||||
|
- 在其中定义`this.store`,`loading`状态的展示是基于`this.store.isSubmitting`
|
||||||
|
- 在其中调用获取表单所需其他数据的函数
|
||||||
|
- 对`this.state`中属性的更新
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
- 定义了操作对应的`store`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
init() {
|
||||||
|
this.store = globalServerStore;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `defaultValue`
|
||||||
|
- 表单的初始值
|
||||||
|
- 默认值是`{}`
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
- 设置了表单中,云主机名称的初始值
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get defaultValue() {
|
||||||
|
const { name } = this.item;
|
||||||
|
const value = {
|
||||||
|
instance: name,
|
||||||
|
};
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `nameForStateUpdate`
|
||||||
|
- 表单项内容变动时,更新到`this.state`中的表单键值对
|
||||||
|
- 这些存储到`this.store`中的键值对往往会影响表单项的展示,需要配合`get formItems`中的代码使用
|
||||||
|
- 如展开、隐藏更多配置项
|
||||||
|
- 如某些表单项必填性的变动
|
||||||
|
- 默认对`radio`, `more`类型的表单项的变动保存到`this.state`中
|
||||||
|
- 以云主机挂载网卡`src/pages/compute/containers/Instance/actions/AttachInterface.jsx`为例
|
||||||
|
- 表单中的网络的选中变更后,会更新子网列表的内容
|
||||||
|
- 但表单中子网的选中变更后,会更新输入 IP 的判定等
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get nameForStateUpdate() {
|
||||||
|
return ['network', 'ipType', 'subnet'];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `instanceName`
|
||||||
|
- 请求发送后,提示信息中的资源名称
|
||||||
|
- 默认值为`this.values.name`
|
||||||
|
- 以编辑浮动 IP`src/pages/network/containers/FloatingIp/actions/Edit.jsx`为例
|
||||||
|
- 提示的名称是浮动 IP 的地址
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get instanceName() {
|
||||||
|
return this.item.floating_ip_address;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isAsyncAction`
|
||||||
|
- 当前操作是否是异步操作
|
||||||
|
- 默认是`false`
|
||||||
|
- 如果是异步操作,提示语为:`xxx指令已下发,实例名称:xxx 您可等待几秒关注列表数据的变更或是手动刷新数据,以获取最终展示结果。`
|
||||||
|
- 如果是同步操作,提示语为:`xxx成功,实例名称:xxx。`
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get isAsyncAction() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `messageHasItemName`
|
||||||
|
- 请求结果的提示语中,是否要包含实例名称
|
||||||
|
- 默认值为`true`
|
||||||
|
- 有些资源,不存在名称,则可设置该值为`false`
|
||||||
|
- 以裸机节点创建端口``为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get messageHasItemName() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `buttonText`
|
||||||
|
- 静态属性
|
||||||
|
- 当操作按钮上的文字与弹窗的标题不一致时,需要复用该属性
|
||||||
|
- 以编辑镜像`src/pages/compute/containers/Image/actions/Edit.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static buttonText = t('Edit');
|
||||||
|
```
|
||||||
|
|
||||||
|
- `buttonType`
|
||||||
|
- 静态属性
|
||||||
|
- 按钮的类型,支持`primary`、`danger`
|
||||||
|
- 当按钮要强调操作危险性时,按钮或按钮上的文字一般为红色,使用`danger`
|
||||||
|
- 以禁止 Cinder 服务`src/pages/configuration/containers/SystemInfo/CinderService/actions/Disable.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static buttonType = 'danger';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `modalSize`
|
||||||
|
- 静态函数
|
||||||
|
- 标识弹出框的宽度:值为`small`、`middle`、`large`
|
||||||
|
- 值与宽度的对应为:
|
||||||
|
- `small`: 520
|
||||||
|
- `middle`: 720
|
||||||
|
- `large`: 1200
|
||||||
|
- 与`getModalSize`配合使用
|
||||||
|
- 默认值为`small`,即弹窗的宽度是 520px
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static get modalSize() {
|
||||||
|
return 'small';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以挂载云硬盘`src/pages/compute/containers/Instance/actions/AttachVolume.jsx`为例
|
||||||
|
- 表单的大小是`large`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static get modalSize() {
|
||||||
|
return 'large';
|
||||||
|
}
|
||||||
|
|
||||||
|
getModalSize() {
|
||||||
|
return 'large';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getModalSize`
|
||||||
|
- 配置表单左侧标题的布局
|
||||||
|
- 值为`small`、`middle`、`large`
|
||||||
|
- `labelCol`
|
||||||
|
- 配置表单左侧标签的布局
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get labelCol() {
|
||||||
|
const size = this.getModalSize();
|
||||||
|
if (size === 'large') {
|
||||||
|
return {
|
||||||
|
xs: { span: 6 },
|
||||||
|
sm: { span: 4 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
xs: { span: 8 },
|
||||||
|
sm: { span: 6 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以编辑域`src/pages/identity/containers/Domain/actions/Edit.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get labelCol() {
|
||||||
|
return {
|
||||||
|
xs: { span: 6 },
|
||||||
|
sm: { span: 5 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `wrapperCol`
|
||||||
|
- 配置表单右侧内容的布局
|
||||||
|
- 默认值为
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get wrapperCol() {
|
||||||
|
return {
|
||||||
|
xs: { span: 16 },
|
||||||
|
sm: { span: 16 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 以管理云主机类型元数据`src/pages/compute/containers/Flavor/actions/ManageMetadata.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get wrapperCol() {
|
||||||
|
return {
|
||||||
|
xs: { span: 18 },
|
||||||
|
sm: { span: 20 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `successText`
|
||||||
|
- 请求后生成的成功提示语
|
||||||
|
- `errorText`
|
||||||
|
- 请求失败后生成的报错提示语
|
||||||
|
- `containerProps`
|
||||||
|
- 获取来源于按钮所在父级组件的`props`
|
||||||
|
- `item`
|
||||||
|
- 获取操作对应的数据
|
||||||
|
- `items`
|
||||||
|
- 获取批量操作对应的数据
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- `ModalAction`继承于`BaseForm`
|
||||||
|
- 建议查看代码理解,`src/components/Form/index.jsx`
|
257
docs/zh/develop/3-8-ConfirmAction-introduction.md
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-8-ConfirmAction-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
![确认型](/docs/zh/develop/images/form/confirm.png)
|
||||||
|
|
||||||
|
- 操作按钮点击后,显示确认类型的表单
|
||||||
|
- 点击`确认`按钮后,会根据请求的发送情况,展示`loading`状态
|
||||||
|
- 点击`取消`按钮后,弹窗消失
|
||||||
|
- 如果请求发送成功,会在右上角展示操作成功的提示信息,该提示信息几秒后可自动消失
|
||||||
|
- 如果请求发送失败,会在表单页的右上角展示错误信息,该提示信息只有点击关闭按钮后才可消失
|
||||||
|
- 支持批量操作,在表格中选中多个条目后,可点击表格上方的操作按钮,进行批量操作
|
||||||
|
- 使用批量操作时,会对批量选中的资源中不符合操作条件的资源做出提示
|
||||||
|
|
||||||
|
# ConfirmAction 代码文件
|
||||||
|
|
||||||
|
- `src/containers/Action/ConfirmAction/index.jsx`
|
||||||
|
|
||||||
|
# ModalAction 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 弹窗型表单都继承于 ModalAction 组件
|
||||||
|
- 代码位置:`pages/xxxx/containers/XXXX/actions/xxx.jsx`
|
||||||
|
- 某些操作,只需要再次确认,无需用户输入更多内容即可,此时可使用该类型的组件,如:关闭云主机
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 必须复写的属性与函数,主要包含:
|
||||||
|
- 操作的 ID
|
||||||
|
- 操作的标题
|
||||||
|
- 操作对应的权限
|
||||||
|
- 对是否禁用操作的判定
|
||||||
|
- 发送请求的函数
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 资源的名称
|
||||||
|
- 请求结果提示语中是否要展示资源名称
|
||||||
|
- 是否是异步操作
|
||||||
|
- 操作按钮上的文字
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 对请求状态的展示
|
||||||
|
- 对请求结果的展示
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 必须复写的属性与函数
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- 资源操作的 ID
|
||||||
|
- 需要具有唯一性,只针对资源的`actions`中的所有操作具有唯一性即可
|
||||||
|
- 必须复写该属性
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get id() {
|
||||||
|
return 'stop';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `title`
|
||||||
|
- 资源操作的标题
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get title() {
|
||||||
|
return t('Stop Instance');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `actionName`
|
||||||
|
- 该操作对应的名称
|
||||||
|
- 在请求后提示语中使用该名称
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get actionName() {
|
||||||
|
return t('stop instance');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `policy`
|
||||||
|
- 页面对应的权限,如果权限验证不通过,则不会在资源列表页面显示该操作按钮
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
policy = 'os_compute_api:servers:stop';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `allowedCheckFunc`
|
||||||
|
- 判定操作是否需要被禁用
|
||||||
|
- 返回`Boolean`
|
||||||
|
- 不需用禁用的按钮,直接写作
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
allowedCheckFunc = () => true;
|
||||||
|
```
|
||||||
|
|
||||||
|
- 参数`item`,操作对应的数据
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
- 当云主机满足以下条件才会显示该操作按钮:处于运行中状态,控制台非锁定或是在管理平台
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
allowedCheckFunc = (item) => {
|
||||||
|
if (!item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isNotLockedOrAdmin(item, this.isAdminPage) && this.isRunning(item);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `onSubmit`
|
||||||
|
- 该操作的请求函数
|
||||||
|
- 操作请求成功后,弹窗会消失,并显示成功提示,几秒后提示会消失
|
||||||
|
- 操作失败后,弹窗会消失,并显示错误提示,需要手动关闭提示,提示才会消失
|
||||||
|
- 返回`Promise`
|
||||||
|
- 返回表单对应的`store`中的函数
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
onSubmit = (item) => {
|
||||||
|
const { id } = item || this.item;
|
||||||
|
return globalServerStore.stop({ id });
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `buttonText`
|
||||||
|
- 当操作按钮上的文字与弹窗的标题不一致时,需要复用该属性
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
- 弹窗上的标题是“停止云主机”,按钮上的文字是“停止”
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get buttonText() {
|
||||||
|
return t('Stop');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `buttonType`
|
||||||
|
- 按钮的类型,支持`primary`、`danger`、`default`
|
||||||
|
- 默认值为`default`
|
||||||
|
- 当按钮要强调操作危险性时,按钮或按钮上的文字一般为红色,使用`danger`
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get buttonType() {
|
||||||
|
return 'danger';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `passiveAction`
|
||||||
|
- 批量操作时,如果某个资源不符合条件,会在发送请求前展示提示语,如果提示语需要以被动语态,则需要设置该属性
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get passiveAction() {
|
||||||
|
return t('be stopped');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isAsyncAction`
|
||||||
|
- 当前操作是否是异步操作
|
||||||
|
- 默认是`false`
|
||||||
|
- 如果是异步操作,提示语为:`xxx指令已下发,实例名称:xxx 您可等待几秒关注列表数据的变更或是手动刷新数据,以获取最终展示结果。`
|
||||||
|
- 如果是同步操作,提示语为:`xxx成功,实例名称:xxx。`
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get isAsyncAction() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `messageHasItemName`
|
||||||
|
- 请求结果的提示语中,是否要包含实例名称
|
||||||
|
- 默认值为`true`
|
||||||
|
- 有些资源,不存在名称,则可设置该值为`false`
|
||||||
|
- `performErrorMsg`
|
||||||
|
- 批量操作时,如果某个资源不符合条件,会在发送请求前展示提示语
|
||||||
|
- 默认值为`无法xxx, 实例名称:xxxx。`
|
||||||
|
- 以停止云主机`src/pages/compute/containers/Instance/actions/Stop.jsx`为例
|
||||||
|
- 如果选中的云主机不处于运行中状态,提示`云主机\"{ name }\"状态不是运行中,无法关闭。`
|
||||||
|
- 如果选中的云主机有处于锁定状态的,提示`云主机\"{ name }\"被锁定,无法关闭。`
|
||||||
|
- 其他情况,皆提示`无法关闭云主机\"{ name }\"`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
performErrorMsg = (failedItems) => {
|
||||||
|
const instance = isArray(failedItems) ? failedItems[0] : failedItems;
|
||||||
|
let errorMsg = t('You are not allowed to stop instance "{ name }".', {
|
||||||
|
name: instance.name,
|
||||||
|
});
|
||||||
|
if (!this.isRunning(instance)) {
|
||||||
|
errorMsg = t(
|
||||||
|
'Instance "{ name }" status is not in active or suspended, can not stop it.',
|
||||||
|
{ name: instance.name }
|
||||||
|
);
|
||||||
|
} else if (!isNotLockedOrAdmin(instance, this.isAdminPage)) {
|
||||||
|
errorMsg = t('Instance "{ name }" is locked, can not stop it.', {
|
||||||
|
name: instance.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return errorMsg;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getNameOne`
|
||||||
|
- 提示语中实例名称的来源
|
||||||
|
- 默认是
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getNameOne = (data) => data.name;`
|
||||||
|
```
|
||||||
|
|
||||||
|
- 参数`data`为操作对应的资源数据
|
||||||
|
- 以释放浮动 IP`src/pages/network/containers/FloatingIp/actions/Release.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
getNameOne = (data) => data.floating_ip_address;
|
||||||
|
```
|
||||||
|
|
||||||
|
- `getName`
|
||||||
|
- 不建议复写该函数
|
||||||
|
- 建议复写`getNameOne`
|
||||||
|
- `confirmContext`
|
||||||
|
- 确认弹窗中的提示语
|
||||||
|
- 默认为`确认{ action }(实例名称:{name})?`
|
||||||
|
- 以删除云主机类型`src/pages/compute/containers/Flavor/actions/Delete.jsx`为例
|
||||||
|
- 提示`若有云主机正在使用此 flavor,删除会导致云主机的 flavor 数据缺失,确定删除 {name} ?`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
confirmContext = (data) => {
|
||||||
|
const name = this.getName(data);
|
||||||
|
return t(
|
||||||
|
"If an instance is using this flavor, deleting it will cause the instance's flavor data to be missing. Are you sure to delete {name}?",
|
||||||
|
{ name }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `submitErrorMsg`
|
||||||
|
- 操作失败后的错误提示语
|
||||||
|
- 一般不需要复写
|
||||||
|
- 默认为`无法{action},实例名称:{name}。`
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `submitSuccessMsg`
|
||||||
|
- 请求后生成的成功提示语
|
||||||
|
- `submitErrorMsgBatch`
|
||||||
|
- 批量操作请求后生成的报错提示语
|
||||||
|
- `perform`
|
||||||
|
- 批量操作时,判定选中的数据是否可操作,如果不可操作,给出相应提示语
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- 建议查看代码理解,`src/containers/Action/ConfirmAction/index.jsx`
|
293
docs/zh/develop/3-9-StepAction-introduction.md
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
简体中文 | [English](/docs/en/develop/3-9-StepAction-introduction.md)
|
||||||
|
|
||||||
|
# 用途
|
||||||
|
|
||||||
|
![分步Form](/docs/zh/develop/images/form/step.png)
|
||||||
|
|
||||||
|
- 操作按钮点击后,单页显示分步操作的表单
|
||||||
|
- 有独立的路由可供访问
|
||||||
|
- 一般用于创建资源,或是表单内容较多的 Form
|
||||||
|
- 支持点击`下一步`、`上一步`操作按钮
|
||||||
|
- 点击`取消`按钮后,会自动跳转到相应的资源列表页
|
||||||
|
- 如果请求发送成功,会在右上角展示操作成功的提示信息,该提示信息几秒后可自动消失
|
||||||
|
|
||||||
|
![Form单页](/docs/zh/develop/images/form/create-success.png)
|
||||||
|
|
||||||
|
- 如果请求发送失败,会在表单页的右上角展示错误信息,该提示信息只有点击关闭按钮后才可消失
|
||||||
|
|
||||||
|
# StepAction 代码文件
|
||||||
|
|
||||||
|
- `src/containers/Action/StepAction/index.jsx`
|
||||||
|
|
||||||
|
# StepAction 属性与函数定义介绍
|
||||||
|
|
||||||
|
- 分步表单都继承于 StepAction 组件
|
||||||
|
- 代码位置:`pages/xxxx/containers/XXXX/actions/xxx/index.jsx`
|
||||||
|
- 只需要复写部分函数即可完成页面的开发
|
||||||
|
- 需要编写每一步的 Form
|
||||||
|
- 属性与函数分为以下四种,
|
||||||
|
- 必须复写的属性与函数,主要包含:
|
||||||
|
- 操作的 ID
|
||||||
|
- 操作的标题
|
||||||
|
- 页面对应的路径
|
||||||
|
- 资源列表页面对应的路径
|
||||||
|
- 操作对应的权限
|
||||||
|
- 对是否禁用操作的判定
|
||||||
|
- 表单项的配置
|
||||||
|
- 发送请求的函数
|
||||||
|
- 每步操作的配置
|
||||||
|
- 按需复写的函数与属性,主要包含:
|
||||||
|
- 是否具有确认信息的页面
|
||||||
|
- 请求成功后的提示语
|
||||||
|
- 请求失败的报错提示语
|
||||||
|
- 对页面底部左侧数据的渲染
|
||||||
|
- 无需复写的函数与属性,主要包含:
|
||||||
|
- 当前页是否是管理平台页面
|
||||||
|
- 基类中的基础函数,主要包含:
|
||||||
|
- 渲染页面
|
||||||
|
- 对请求状态的展示
|
||||||
|
- 对请求结果的展示
|
||||||
|
- 更详细与全面的介绍见下
|
||||||
|
|
||||||
|
## 必须复写的属性与函数
|
||||||
|
|
||||||
|
- `id`
|
||||||
|
- 静态属性
|
||||||
|
- 资源操作的 ID
|
||||||
|
- 需要具有唯一性,只针对资源的`actions`中的所有操作具有唯一性即可
|
||||||
|
- 必须复写该属性
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static id = 'instance-create';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `title`
|
||||||
|
- 静态属性
|
||||||
|
- 资源操作的标题
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static title = t('Create Instance');
|
||||||
|
```
|
||||||
|
|
||||||
|
- `path`
|
||||||
|
- 资源操作的对应的路由
|
||||||
|
- 静态属性或静态函数
|
||||||
|
- 静态函数时,参数为
|
||||||
|
- 参数`item`,资源列表中的条目数据
|
||||||
|
- 参数`containerProps`,父级 container(即按钮所在资源列表页面)的`props`属性
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
- 在云主机列表页点击创建云主机按钮,页面跳转到`/compute/instance/create`
|
||||||
|
- 在云主机组详情页中点击创建云主机按钮,页面跳转到`/compute/instance/create?servergroup=${detail.id}`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static path = (_, containerProps) => {
|
||||||
|
const { detail, match } = containerProps || {};
|
||||||
|
if (!detail || isEmpty(detail)) {
|
||||||
|
return '/compute/instance/create';
|
||||||
|
}
|
||||||
|
if (match.path.indexOf('/compute/server') >= 0) {
|
||||||
|
return `/compute/instance/create?servergroup=${detail.id}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- 静态属性,以创建云主机类型`src/pages/compute/containers/Flavor/actions/StepCreate/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static path = '/compute/flavor-admin/create';
|
||||||
|
```
|
||||||
|
|
||||||
|
- `policy`
|
||||||
|
- 静态属性
|
||||||
|
- 页面对应的权限,如果权限验证不通过,则不会在资源列表页面显示该操作按钮
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static policy = [
|
||||||
|
'os_compute_api:servers:create',
|
||||||
|
'os_compute_api:os-availability-zone:list',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- `allowed`
|
||||||
|
- 静态函数
|
||||||
|
- 判定操作是否需要被禁用
|
||||||
|
- 返回`Promise`
|
||||||
|
- 不需用禁用的按钮,直接写作
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
static allowed() {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `name`
|
||||||
|
- 该操作对应的名称
|
||||||
|
- 在请求后提示语中使用该名称
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get name() {
|
||||||
|
return t('Create instance');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `listUrl`
|
||||||
|
- 该操作对应的资源列表页
|
||||||
|
- 操作请求成功后,会自动进入到资源列表页
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
- 在镜像列表页的条目操作中,点击创建云主机并操作成功后,返回到镜像列表页
|
||||||
|
- 在云硬盘列表页的条目操作中,点击创建云主机并操作成功后,返回到云硬盘列表页
|
||||||
|
- 在云硬盘列表页的条目操作中,点击创建云主机并操作成功后,返回到云硬盘列表页
|
||||||
|
- 在云主机组详情页,点击创建云主机并操作成功后,返回到云主机详情页中
|
||||||
|
- 在云主机列表页中,点击创建云主机并操作成功后,返回到云主机列表页
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get listUrl() {
|
||||||
|
const { image, volume, servergroup } = this.locationParams;
|
||||||
|
if (image) {
|
||||||
|
return '/compute/image';
|
||||||
|
}
|
||||||
|
if (volume) {
|
||||||
|
return '/storage/volume';
|
||||||
|
}
|
||||||
|
if (servergroup) {
|
||||||
|
return `/compute/server-group/detail/${servergroup}`;
|
||||||
|
}
|
||||||
|
return '/compute/instance';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `steps`
|
||||||
|
- 每一步的配置
|
||||||
|
- 每个配置项
|
||||||
|
- `title`,每一步的标题
|
||||||
|
- `component`,每一步表单对应的组件,继承于`BaseForm`(`src/components/Form`)
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
- 包含 4 步:基础配置、网络配置、系统配置、确认配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get steps() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t('Base Config'),
|
||||||
|
component: BaseStep,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Network Config'),
|
||||||
|
component: NetworkStep,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('System Config'),
|
||||||
|
component: SystemStep,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Confirm Config'),
|
||||||
|
component: ConfirmStep,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `onSubmit`
|
||||||
|
- 该操作的请求函数
|
||||||
|
- 操作请求成功后,会自动进入到资源列表页
|
||||||
|
- 操作失败后,会在表单页显示错误提示
|
||||||
|
- 返回`Promise`
|
||||||
|
- 返回表单对应的`store`中的函数
|
||||||
|
|
||||||
|
## 按需复写的属性与函数
|
||||||
|
|
||||||
|
- `init`
|
||||||
|
- 初始化操作
|
||||||
|
- 在其中定义`this.store`,`loading`状态的展示是基于`this.store.isSubmitting`
|
||||||
|
- 在其中调用获取表单所需其他数据的函数
|
||||||
|
- 对`this.state`中属性的更新
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
- 获取配额信息
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
init() {
|
||||||
|
this.store = globalServerStore;
|
||||||
|
this.projectStore = globalProjectStore;
|
||||||
|
this.getQuota();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `instanceName`
|
||||||
|
- 请求发送后,提示信息中的资源名称
|
||||||
|
- 默认值为`this.values.name`
|
||||||
|
- 以创建云主机`src/pages/compute/containers/Instance/actions/StepCreate/index.jsx`为例
|
||||||
|
- 如果是批量创建云主机,则按`${name}-${index + 1}`的形式展示名称
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
get instanceName() {
|
||||||
|
const { name, count = 1 } = this.values || {};
|
||||||
|
if (count === 1) {
|
||||||
|
return this.unescape(name);
|
||||||
|
}
|
||||||
|
return this.unescape(
|
||||||
|
new Array(count)
|
||||||
|
.fill(count)
|
||||||
|
.map((_, index) => `${name}-${index + 1}`)
|
||||||
|
.join(', ')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `renderFooterLeft`
|
||||||
|
- 对表单底部左侧内部的渲染
|
||||||
|
- 默认返回`null`
|
||||||
|
- src/pages/compute/containers/Instance/actions/StepCreate/index.jsx
|
||||||
|
- 展示批量创建的数量
|
||||||
|
- 基于输入的数量与剩余配额判定当前表单是否正确
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
renderFooterLeft() {
|
||||||
|
const { data } = this.state;
|
||||||
|
const { count = 1, source: { value: sourceValue } = {} } = data;
|
||||||
|
const configs = {
|
||||||
|
min: 1,
|
||||||
|
max: sourceValue === 'bootableVolume' ? 1 : 100,
|
||||||
|
precision: 0,
|
||||||
|
onChange: this.onCountChange,
|
||||||
|
formatter: (value) => `$ ${value}`.replace(/\D/g, ''),
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span>{t('Count')}</span>
|
||||||
|
<InputNumber
|
||||||
|
{...configs}
|
||||||
|
value={count}
|
||||||
|
className={classnames(styles.input, 'instance-count')}
|
||||||
|
/>
|
||||||
|
{this.renderBadge()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `successText`
|
||||||
|
- 操作成功的提示信息
|
||||||
|
- `errorText`
|
||||||
|
- 错误信息的展示
|
||||||
|
- 一般不需要复写
|
||||||
|
- `renderFooterLeft`
|
||||||
|
- 表单底部左侧的渲染函数
|
||||||
|
|
||||||
|
## 不需要复写的属性与函数
|
||||||
|
|
||||||
|
- `values`
|
||||||
|
- 表单验证成功后,更新的表单值
|
||||||
|
- `isAdminPage`
|
||||||
|
- 当前页面是否是“管理平台”的页面
|
||||||
|
- `getUrl`
|
||||||
|
- 生成页面 Url 的函数
|
||||||
|
- 如:需要给关联资源提供跳转功能,使用该函数,可以在控制台跳转到控制台的相应地址,在管理平台跳转到管理平台的相应地址
|
||||||
|
|
||||||
|
## 基类中的基础函数
|
||||||
|
|
||||||
|
- `StepAction`继承于`StepForm`
|
||||||
|
- 建议查看代码理解,`src/components/StepForm/index.jsx`
|
BIN
docs/zh/develop/images/detail/image-detail-info.png
Executable file
After Width: | Height: | Size: 95 KiB |
BIN
docs/zh/develop/images/detail/volume.png
Executable file
After Width: | Height: | Size: 79 KiB |
BIN
docs/zh/develop/images/form/ace-editor.png
Executable file
After Width: | Height: | Size: 69 KiB |
BIN
docs/zh/develop/images/form/action.png
Executable file
After Width: | Height: | Size: 88 KiB |
BIN
docs/zh/develop/images/form/add-select.png
Executable file
After Width: | Height: | Size: 79 KiB |
BIN
docs/zh/develop/images/form/check-group.png
Executable file
After Width: | Height: | Size: 97 KiB |
BIN
docs/zh/develop/images/form/check.png
Executable file
After Width: | Height: | Size: 81 KiB |
BIN
docs/zh/develop/images/form/confirm.png
Executable file
After Width: | Height: | Size: 87 KiB |
0
docs/zh/develop/images/form/courgette.log
Executable file
BIN
docs/zh/develop/images/form/create-success.png
Executable file
After Width: | Height: | Size: 99 KiB |
BIN
docs/zh/develop/images/form/descriptions.png
Executable file
After Width: | Height: | Size: 71 KiB |
BIN
docs/zh/develop/images/form/form-divider.png
Executable file
After Width: | Height: | Size: 68 KiB |
BIN
docs/zh/develop/images/form/form-extra.png
Executable file
After Width: | Height: | Size: 112 KiB |
BIN
docs/zh/develop/images/form/form-label.png
Executable file
After Width: | Height: | Size: 99 KiB |
BIN
docs/zh/develop/images/form/form-tip.png
Executable file
After Width: | Height: | Size: 80 KiB |
BIN
docs/zh/develop/images/form/input-int.png
Executable file
After Width: | Height: | Size: 78 KiB |
BIN
docs/zh/develop/images/form/input-json.png
Executable file
After Width: | Height: | Size: 57 KiB |
BIN
docs/zh/develop/images/form/input-name.png
Executable file
After Width: | Height: | Size: 64 KiB |
BIN
docs/zh/develop/images/form/input-number.png
Executable file
After Width: | Height: | Size: 103 KiB |
BIN
docs/zh/develop/images/form/input-password.png
Executable file
After Width: | Height: | Size: 63 KiB |
BIN
docs/zh/develop/images/form/input.png
Executable file
After Width: | Height: | Size: 109 KiB |
BIN
docs/zh/develop/images/form/instance-action.png
Executable file
After Width: | Height: | Size: 95 KiB |
BIN
docs/zh/develop/images/form/instance-volume.png
Executable file
After Width: | Height: | Size: 78 KiB |
BIN
docs/zh/develop/images/form/ip-distributer.png
Executable file
After Width: | Height: | Size: 7.4 KiB |
BIN
docs/zh/develop/images/form/ip-input.png
Executable file
After Width: | Height: | Size: 88 KiB |
BIN
docs/zh/develop/images/form/label-col.png
Executable file
After Width: | Height: | Size: 99 KiB |
BIN
docs/zh/develop/images/form/mac-address.png
Executable file
After Width: | Height: | Size: 103 KiB |
BIN
docs/zh/develop/images/form/member-allocator.png
Executable file
After Width: | Height: | Size: 68 KiB |
BIN
docs/zh/develop/images/form/metadata-transfer.png
Executable file
After Width: | Height: | Size: 38 KiB |
BIN
docs/zh/develop/images/form/modal.png
Executable file
After Width: | Height: | Size: 72 KiB |
BIN
docs/zh/develop/images/form/more.png
Executable file
After Width: | Height: | Size: 63 KiB |
BIN
docs/zh/develop/images/form/network-select-table.png
Executable file
After Width: | Height: | Size: 95 KiB |
BIN
docs/zh/develop/images/form/network-select.png
Executable file
After Width: | Height: | Size: 88 KiB |
BIN
docs/zh/develop/images/form/page.png
Executable file
After Width: | Height: | Size: 58 KiB |
BIN
docs/zh/develop/images/form/port-range.png
Executable file
After Width: | Height: | Size: 104 KiB |
BIN
docs/zh/develop/images/form/radio.png
Executable file
After Width: | Height: | Size: 63 KiB |
BIN
docs/zh/develop/images/form/select-table-tabs.png
Executable file
After Width: | Height: | Size: 74 KiB |
BIN
docs/zh/develop/images/form/select-table.png
Executable file
After Width: | Height: | Size: 76 KiB |
BIN
docs/zh/develop/images/form/select.png
Executable file
After Width: | Height: | Size: 74 KiB |
BIN
docs/zh/develop/images/form/slider-input.png
Executable file
After Width: | Height: | Size: 62 KiB |
BIN
docs/zh/develop/images/form/step.png
Executable file
After Width: | Height: | Size: 69 KiB |
BIN
docs/zh/develop/images/form/switch.png
Executable file
After Width: | Height: | Size: 88 KiB |
BIN
docs/zh/develop/images/form/tab-select-table.png
Executable file
After Width: | Height: | Size: 63 KiB |
BIN
docs/zh/develop/images/form/textarea-from-file.png
Executable file
After Width: | Height: | Size: 72 KiB |
BIN
docs/zh/develop/images/form/textarea.png
Executable file
After Width: | Height: | Size: 97 KiB |
BIN
docs/zh/develop/images/form/title.png
Executable file
After Width: | Height: | Size: 57 KiB |
BIN
docs/zh/develop/images/form/transfer.png
Executable file
After Width: | Height: | Size: 78 KiB |
BIN
docs/zh/develop/images/form/upload.png
Executable file
After Width: | Height: | Size: 65 KiB |
BIN
docs/zh/develop/images/form/volume-action.png
Executable file
After Width: | Height: | Size: 101 KiB |
BIN
docs/zh/develop/images/form/volume-select-table.png
Executable file
After Width: | Height: | Size: 73 KiB |
BIN
docs/zh/develop/images/form/wrapper-col.png
Executable file
After Width: | Height: | Size: 80 KiB |
BIN
docs/zh/develop/images/i18n/english.png
Executable file
After Width: | Height: | Size: 118 KiB |
BIN
docs/zh/develop/images/i18n/i18n.png
Executable file
After Width: | Height: | Size: 113 KiB |
BIN
docs/zh/develop/images/list/batch.png
Executable file
After Width: | Height: | Size: 72 KiB |
BIN
docs/zh/develop/images/list/download.png
Executable file
After Width: | Height: | Size: 555 B |
BIN
docs/zh/develop/images/list/fresh.png
Executable file
After Width: | Height: | Size: 788 B |
BIN
docs/zh/develop/images/list/hide.png
Executable file
After Width: | Height: | Size: 77 KiB |
BIN
docs/zh/develop/images/list/pagination.png
Executable file
After Width: | Height: | Size: 2.0 KiB |
BIN
docs/zh/develop/images/list/search-example.png
Executable file
After Width: | Height: | Size: 62 KiB |
BIN
docs/zh/develop/images/list/search.png
Executable file
After Width: | Height: | Size: 7.4 KiB |
BIN
docs/zh/develop/images/list/stop-auto-refresh.png
Executable file
After Width: | Height: | Size: 874 B |
BIN
docs/zh/develop/images/list/tab-list.png
Executable file
After Width: | Height: | Size: 82 KiB |
BIN
docs/zh/develop/images/list/tab-service.png
Executable file
After Width: | Height: | Size: 73 KiB |
BIN
docs/zh/develop/images/list/volumes.png
Executable file
After Width: | Height: | Size: 72 KiB |
BIN
docs/zh/develop/images/menu/admin-menu.png
Executable file
After Width: | Height: | Size: 54 KiB |
BIN
docs/zh/develop/images/menu/console-menu.png
Executable file
After Width: | Height: | Size: 55 KiB |
BIN
docs/zh/develop/images/store/response-key.png
Executable file
After Width: | Height: | Size: 36 KiB |
142
docs/zh/test/1-ready-to-work.md
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
简体中文 | [English](/docs/en/test/1-ready-to-work.md)
|
||||||
|
|
||||||
|
# 两种测试
|
||||||
|
|
||||||
|
我们提供了两种类型的测试
|
||||||
|
|
||||||
|
- E2E 测试
|
||||||
|
- 侧重于功能点测试
|
||||||
|
- 能提供代码覆盖率数据
|
||||||
|
- 使用`Cypress`框架
|
||||||
|
- 测试结果保存到便于预览的静态页面中
|
||||||
|
- 单元测试
|
||||||
|
- 侧重于基础函数测试
|
||||||
|
- 使用`Jest`框架
|
||||||
|
|
||||||
|
# E2E 测试
|
||||||
|
|
||||||
|
## 搭建 E2E 测试环境
|
||||||
|
|
||||||
|
在 Centos,Windows 的 wsl2 中均成功搭建过 E2E 测试环境
|
||||||
|
|
||||||
|
- node 环境
|
||||||
|
- package.json 中要求:`"node": ">=10.22.0"`
|
||||||
|
- 验证 nodejs 版本
|
||||||
|
|
||||||
|
```shell
|
||||||
|
node -v
|
||||||
|
```
|
||||||
|
|
||||||
|
- yarn
|
||||||
|
- 安装 yarn
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install -g yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
- 安装依赖包
|
||||||
|
- 在项目根目录下执行,即`package.json`同级,需要耐心等待安装完成
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
- 安装系统依赖
|
||||||
|
- `Ubuntu/Debian`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
|
||||||
|
```
|
||||||
|
|
||||||
|
- `CentOS`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yum install -y xorg-x11-server-Xvfb gtk2-devel gtk3-devel libnotify-devel GConf2 nss libXScrnSaver alsa-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
- 调整访问路径、账号等信息
|
||||||
|
- E2E 的配置文件存放于`test/e2e/config/config.yaml`,在其中配置了
|
||||||
|
- `baseUrl`,测试访问路径
|
||||||
|
- `env`,环境变量
|
||||||
|
- `switchToAdminProject`,登录后是否需要切换到`admin`项目下
|
||||||
|
- `username`,访问控制台的用户名,需要具有控制台操作权限的用户
|
||||||
|
- `password`,访问控制台的密码
|
||||||
|
- `usernameAdmin`,访问管理平台的用户名,需要具有管理平台操作权限的用户
|
||||||
|
- `passwordAdmin`,访问管理平台的密码
|
||||||
|
- `testFiles`,测试文件列表
|
||||||
|
- 可以通过直接修改`config.yaml`中的相应数值完成配置变更
|
||||||
|
- 也可以通过`local_config.yaml`完成配置变更
|
||||||
|
- 复制`test/e2e/config/config.yaml`到`test/e2e/config/local_config.yaml`中
|
||||||
|
- 修改`local_config.yaml`中的相应变量
|
||||||
|
- 对于变量的取值,优先级为:`local_config.yaml` > `config.yaml`
|
||||||
|
|
||||||
|
## 命令行运行 E2E
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
![控制台](/docs/zh/test/images/e2e/console.png)
|
||||||
|
|
||||||
|
## GUI 运行 E2E
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run test:e2e:open
|
||||||
|
```
|
||||||
|
|
||||||
|
Cypress 提供了 GUI
|
||||||
|
|
||||||
|
![gui](/docs/zh/test/images/e2e/gui-list.png)
|
||||||
|
|
||||||
|
![work](/docs/zh/test/images/e2e/gui-work.png)
|
||||||
|
|
||||||
|
## E2E 测试结果
|
||||||
|
|
||||||
|
测试运行结束后,访问`test/e2e/report/merge-report.html`即可查看
|
||||||
|
|
||||||
|
![结果](/docs/zh/test/images/e2e/result.png)
|
||||||
|
|
||||||
|
## E2E 代码覆盖率测试结果
|
||||||
|
|
||||||
|
测试运行结束后,访问`coverage/lcov-report/index.html`即可查看
|
||||||
|
|
||||||
|
> 注意:代码覆盖率,需要 E2E 访问的`baseUrl`对应的前端包,是具有可检测代码覆盖率版本的`dist`包
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run build:test
|
||||||
|
```
|
||||||
|
|
||||||
|
以上述方式打包的文件,就是具有可测试代码覆盖率的前端包
|
||||||
|
|
||||||
|
以下,给出前端访问带有代码覆盖率功能的前端包的 nginx 配置
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 0.0.0.0:8088 default_server;
|
||||||
|
|
||||||
|
root /path/to/skyline-console/dist;
|
||||||
|
index index.html;
|
||||||
|
server_name _;
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://<backend_address>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 单元测试
|
||||||
|
|
||||||
|
## 命令行运行单元测试
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn run test:unit
|
||||||
|
```
|
||||||
|
|
||||||
|
## 单元测试结果
|
||||||
|
|
||||||
|
直接在命令行控制台中即可查看运行结果
|
||||||
|
|
||||||
|
![单元测试结果](/docs/zh/test/images/unit/result.png)
|
91
docs/zh/test/2-catalog-introduction.md
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
简体中文 | [English](/docs/en/test/2-catalog-introduction.md)
|
||||||
|
|
||||||
|
```
|
||||||
|
test
|
||||||
|
├── e2e (E2E代码存放位置)
|
||||||
|
│ ├── config
|
||||||
|
│ │ ├── config.yaml (E2E运行时的部分配置,主要配置了测试用例文件列表,登录账号等信息)
|
||||||
|
│ │ └── local_config.yaml (E2E运行时的部分配置,主要配置了测试用例文件列表,登录账号等信息,是gitignore的,优先级高于config.yaml)
|
||||||
|
│ ├── fixtures (存放运行时需要的上传文件,读取文件等)
|
||||||
|
│ │ ├── keypair (测试密钥读取的文件)
|
||||||
|
│ │ ├── metadata.json (测试元数据读取的文件)
|
||||||
|
│ │ ├── stack-content.yaml (测试堆栈读取的文件)
|
||||||
|
│ │ └── stack-params.yaml (测试堆栈读取的文件)
|
||||||
|
│ ├── integration (存放测试用例)
|
||||||
|
│ │ └── pages (按网页菜单结构调整目录)
|
||||||
|
│ │ ├── compute (计算)
|
||||||
|
│ │ │ ├── aggregate.spec.js (主机集合)
|
||||||
|
│ │ │ ├── baremetal.spec.js (裸机配置)
|
||||||
|
│ │ │ ├── flavor.spec.js (云主机类型)
|
||||||
|
│ │ │ ├── hypervisor.spec.js (虚拟机管理器)
|
||||||
|
│ │ │ ├── image.spec.js (镜像)
|
||||||
|
│ │ │ ├── instance.spec.js (云主机)
|
||||||
|
│ │ │ ├── ironic.spec.js (裸机)
|
||||||
|
│ │ │ ├── keypair.spec.js (密钥)
|
||||||
|
│ │ │ └── server-group.spec.js (云主机组)
|
||||||
|
│ │ ├── configuration (平台配置)
|
||||||
|
│ │ │ ├── metadata.spec.js (元数据)
|
||||||
|
│ │ │ └── system.spec.js (系统信息)
|
||||||
|
│ │ ├── error.spec.js (错误页面)
|
||||||
|
│ │ ├── heat (资源编排)
|
||||||
|
│ │ │ └── stack.spec.js (堆栈)
|
||||||
|
│ │ ├── identity (身份管理)
|
||||||
|
│ │ │ ├── domain.spec.js (域)
|
||||||
|
│ │ │ ├── project.spec.js (项目)
|
||||||
|
│ │ │ ├── role.spec.js (角色)
|
||||||
|
│ │ │ ├── user-group.spec.js (用户组)
|
||||||
|
│ │ │ └── user.spec.js (用户)
|
||||||
|
│ │ ├── login.spec.js (登录)
|
||||||
|
│ │ ├── management (运维管理)
|
||||||
|
│ │ │ └── recycle-bin.spec.js (回收站)
|
||||||
|
│ │ ├── network (网络)
|
||||||
|
│ │ │ ├── floatingip.spec.js (浮动IP)
|
||||||
|
│ │ │ ├── lb.spec.js (负载均衡)
|
||||||
|
│ │ │ ├── network.spec.js (网络)
|
||||||
|
│ │ │ ├── qos-policy.spec.js (Qos策略)
|
||||||
|
│ │ │ ├── router.spec.js (路由器)
|
||||||
|
│ │ │ ├── security-group.spec.js (安全组)
|
||||||
|
│ │ │ ├── topology.spec.js (网络拓扑)
|
||||||
|
│ │ │ ├── virtual-adapter.spec.js (虚拟网卡)
|
||||||
|
│ │ │ └── vpn.spec.js (VPN)
|
||||||
|
│ │ └── storage (存储)
|
||||||
|
│ │ ├── backup.spec.js (备份)
|
||||||
|
│ │ ├── qos.spec.js (QoS)
|
||||||
|
│ │ ├── snapshot.spec.js (云硬盘快照)
|
||||||
|
│ │ ├── storage.spec.js (存储后端)
|
||||||
|
│ │ ├── volume-type.spec.js (云硬盘类型)
|
||||||
|
│ │ └── volume.spec.js (云硬盘)
|
||||||
|
│ ├── plugins (Cypress的扩展)
|
||||||
|
│ │ └── index.js (配置了对配置文件的读取,配置了使用代码覆盖率功能)
|
||||||
|
│ ├── report (存放E2E的测试报告)
|
||||||
|
│ │ ├── merge-report.html (最终生成的测试报告,记录了每个用例的执行情况)
|
||||||
|
│ │ └── merge-report.json (results目录下的测试结果的汇总)
|
||||||
|
│ ├── results (存放测试用的结果文件)
|
||||||
|
│ ├── screenshots (存放测试出错时的快照)
|
||||||
|
│ ├── support (编写测试用例时,二次封装的函数)
|
||||||
|
│ │ ├── commands.js (存放登录、登出等操作函数)
|
||||||
|
│ │ ├── common.js (存放基础函数)
|
||||||
|
│ │ ├── constants.js (存放每个资源的路由)
|
||||||
|
│ │ ├── detail-commands.js (存放资源详情页相关的函数,基于框架,详情页的操作具有一致性)
|
||||||
|
│ │ ├── form-commands.js (存放表单相关的函数,基于框架,对表单项的操作具有一致性)
|
||||||
|
│ │ ├── index.js
|
||||||
|
│ │ ├── resource-commands.js (存放资源操作相关的函数,如:创建云主机、创建路由、删除资源等)
|
||||||
|
│ │ └── table-commands.js (存放资源列表相关的函数,基于框架,对列表的操作具有一致性)
|
||||||
|
│ └── utils (存放对于配置文件的读取函数)
|
||||||
|
│ └── index.js
|
||||||
|
└── unit (单元测试)
|
||||||
|
├── local-storage-mock.js (本地存储的mock函数)
|
||||||
|
├── locales (测试国际化时使用的翻译文件)
|
||||||
|
│ ├── en-US.js
|
||||||
|
│ └── zh-CN.js
|
||||||
|
├── setup-tests.js (配置单元测试)
|
||||||
|
└── svg-mock.js (图片加载的mock)
|
||||||
|
```
|
||||||
|
|
||||||
|
- E2E 测试的代码,存放在`test/e2e`目录下
|
||||||
|
- E2E 的其他全局配置,存放在`cypress.json`
|
||||||
|
- 单元测试的基础代码,存放在`test/unit`目录下
|
||||||
|
- 单元测试的其他全局配置,存放在`jest.config.js`
|
||||||
|
- 单元测试的测试代码,通常是与待测试文件放在相同的目录下,并以`test.js`或`spec.js`为后缀
|
||||||
|
- 如:`src/utils/index.js`与`src/utils/index.test.js`
|
||||||
|
- 如:`src/utils/local-storage.js`与`src/utils/local-storage.spec.js`
|
104
docs/zh/test/3-0-how-to-edit-e2e-case.md
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
简体中文 | [English](/docs/en/test/3-0-how-to-edit-e2e-case.md)
|
||||||
|
|
||||||
|
关于 Cypress 的具体介绍及使用方法,请参考[官方文档](https://docs.cypress.io/guides/overview/why-cypress)
|
||||||
|
|
||||||
|
这里主要给出编写 Skyline-console 前端页面中,资源对应的 E2E 用例,并使用`test/e2e/support`中定义的函数的说明
|
||||||
|
|
||||||
|
以下介绍,以云主机用例`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
一般,测试资源的相应功能时,是按照以下顺序
|
||||||
|
|
||||||
|
1. 准备测试使用的相关变量
|
||||||
|
- 创建资源时的必须参数,如:名称、密码
|
||||||
|
- 编辑资源时的必须参数,如:新的名称
|
||||||
|
- 创建关联资源时,关联资源的名称,如:网络名称、路由器名称、云硬盘名称
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const uuid = Cypress._.random(0, 1e6);
|
||||||
|
const name = `e2e-instance-${uuid}`;
|
||||||
|
const newname = `${name}-1`;
|
||||||
|
const password = 'passw0rd_1';
|
||||||
|
const volumeName = `e2e-instance-attach-volume-${uuid}`;
|
||||||
|
const networkName = `e2e-network-for-instance-${uuid}`;
|
||||||
|
const routerName = `e2e-router-for-instance-${uuid}`;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 操作前登录
|
||||||
|
- 如果是操作控制台资源,使用`cy.login`
|
||||||
|
- 如果是操作管理平台资源,使用`cy.loginAdmin`
|
||||||
|
- 一般会在`login`与`loginAdmin`函数中使用变量`listUrl`,即登录后直接访问资源所在页面
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 创建关联资源,使用`resource-commands.js`中提供的创建资源的函数,以测试云主机为例
|
||||||
|
- 创建网络,用于测试创建云主机、挂载网卡
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
```
|
||||||
|
|
||||||
|
- 创建路由器`cy.createRouter`,用于测试关联浮动 IP 时确保浮动 IP 可达
|
||||||
|
- 以如下方式创建的路由器将开启外网网关,并绑定了`networkName`网络的子网
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.createRouter({ name: routerName, network: networkName });
|
||||||
|
```
|
||||||
|
|
||||||
|
- 创建浮动 IP`cy.createFip`,用于测试关联浮动 IP
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.createFip();
|
||||||
|
```
|
||||||
|
|
||||||
|
- 创建云硬盘`cy.createVolume`(用于测试挂载云硬盘)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.createVolume(volumeName);
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 编写创建资源的用例
|
||||||
|
5. 编写访问资源详情的用例
|
||||||
|
6. 分别编写资源的所有操作对应的用例
|
||||||
|
- 一般`编辑`操作的用例写在后面,其后编写`删除`操作的用例,这样能测试到编辑是否生效
|
||||||
|
7. 删除关联资源,使用`resource-commands.js`中提供的删除资源的函数,这是为了测试用例执行后,测试账号内的资源尽可能的干净
|
||||||
|
- 删除浮动 IP
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.deleteAll('fip');
|
||||||
|
```
|
||||||
|
|
||||||
|
- 删除路由器`routerName`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.deleteRouter(routerName, networkName);
|
||||||
|
```
|
||||||
|
|
||||||
|
- 删除网络`networkName`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.deleteAll('network', networkName);
|
||||||
|
```
|
||||||
|
|
||||||
|
- 删除云硬盘`volumeName`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.deleteAll('volume', volumeName);
|
||||||
|
```
|
||||||
|
|
||||||
|
- 删除所有可用状态的云硬盘
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
cy.deleteAllAvailableVolume();
|
||||||
|
```
|
||||||
|
|
||||||
|
上述步骤中的`4`、`5`、`6`主要使用了
|
||||||
|
|
||||||
|
- `test/e2e/support/form-commands.js`中的函数操作表单,详细介绍见[3-1-E2E-form-operation](3-1-E2E-form-operation.md)
|
||||||
|
- `test/e2e/support/table-commands.js`中的函数,操作表格中的按钮点击、搜索、进入详情,详细介绍见[3-2-E2E-table-operation](3-2-E2E-table-operation.md)
|
||||||
|
- `test/e2e/support/detail-commands.js`中的函数,操作返回列表页、检测详情内容、切换详情 Tab,详细介绍见[3-3-E2E-detail-operation](3-3-E2E-detail-operation.md)
|
||||||
|
|
||||||
|
创建、删除关联资源主要使用了`test/e2e/support/resource-commands.js`中的函数,,详细介绍见[3-4-E2E-resource-operation](3-4-E2E-resource-operation.md)
|
591
docs/zh/test/3-1-E2E-form-operation.md
Normal file
@ -0,0 +1,591 @@
|
|||||||
|
简体中文 | [English](/docs/en/test/3-1-E2E-form-operation.md)
|
||||||
|
|
||||||
|
因为前端框架使用的一致性,我们在编写表单操作的相关用例,选取元素并进行操作时,往往会发现有很强的规律性,所以我们对大多数表单操作都编写了相应的 Cypress 函数,极大的减少了编写测试用例的难度,以下会对主要使用的表单操作函数做出详细的说明。
|
||||||
|
|
||||||
|
> 注意:编写的函数均以能完整完成对一个表单项的操作为原则
|
||||||
|
|
||||||
|
## 点击按钮的操作
|
||||||
|
|
||||||
|
- `closeNotice`
|
||||||
|
- 关闭操作后右上角的操作成功的提示信息
|
||||||
|
|
||||||
|
![notice](/docs/zh/test/images/e2e/form/notice.png)
|
||||||
|
|
||||||
|
- `waitFormLoading`
|
||||||
|
- 等待表单请求完成
|
||||||
|
- 表单填写并验证通过后,点击确认按钮,会向服务端发起相应请求,这时表单项的确认按钮会处于`Loading`的状态
|
||||||
|
- 使用该函数,而不是`cy.wait(seconds)`,能更有效的保证同步请求已经处理完全,从而保证后续用例的先决条件
|
||||||
|
|
||||||
|
![wait-form-loading](/docs/zh/test/images/e2e/form/wait-form-loading.png)
|
||||||
|
|
||||||
|
- `clickFormActionSubmitButton`
|
||||||
|
- 点击确认型表单的确认按钮,并等待请求完成
|
||||||
|
|
||||||
|
![click-form-submit](/docs/zh/test/images/e2e/form/click-form-submit.png)
|
||||||
|
|
||||||
|
- `clickModalActionSubmitButton`
|
||||||
|
- 点击弹窗型表单的确认按钮,并等待请求完成
|
||||||
|
|
||||||
|
![click-modal-submit](/docs/zh/test/images/e2e/form/click-modal-submit.png)
|
||||||
|
|
||||||
|
- `clickModalActionCancelButton`
|
||||||
|
- 点击弹窗型表单的取消按钮
|
||||||
|
- `clickConfirmActionSubmitButton`
|
||||||
|
- 点击确认型表单的确认按钮,等待请求完成,并关闭请求成功的提示信息
|
||||||
|
- 参数`waitTime`,关闭提示信息后的等待时间
|
||||||
|
|
||||||
|
![click-confirm-submit](/docs/zh/test/images/e2e/form/click-confirm-submit.png)
|
||||||
|
|
||||||
|
- `checkDisableAction`
|
||||||
|
- 某些数据不符合要求时,使用批量操作,会弹出报错,该函数验证该数据的确不合操作要求,并关闭报错提示
|
||||||
|
- 以锁定状态的云主机`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
- 锁定后不再支持启动、关闭、重启操作
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully lock', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickConfirmActionInMoreSub('Lock', 'Instance Status')
|
||||||
|
.wait(10000);
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.selectFirst()
|
||||||
|
.clickHeaderButtonByTitle('Start')
|
||||||
|
.checkDisableAction(2000)
|
||||||
|
.clickHeaderButtonByTitle('Stop')
|
||||||
|
.checkDisableAction(2000)
|
||||||
|
.clickHeaderButtonByTitle('Reboot')
|
||||||
|
.checkDisableAction(2000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![disable-action](/docs/zh/test/images/e2e/form/disable-action.png)
|
||||||
|
|
||||||
|
- `clickStepActionNextButton`
|
||||||
|
- 点击分步表单的下一步/确认按钮
|
||||||
|
- 以创建云主机用例`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
- 共需要点击 3 次下一步,1 次确认按钮
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
cy.clickHeaderButton(1)
|
||||||
|
.url()
|
||||||
|
.should('include', `${listUrl}/create`)
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelect('flavor')
|
||||||
|
.formTableSelect('image')
|
||||||
|
.formSelect('systemDisk')
|
||||||
|
.formAddSelectAdd('dataDisk')
|
||||||
|
.formSelect('dataDisk')
|
||||||
|
.wait(2000)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelectBySearch('networkSelect', networkName, 5000)
|
||||||
|
.formTableSelectBySearch('securityGroup', 'default', 5000)
|
||||||
|
.wait(2000)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.formInput('name', name)
|
||||||
|
.formRadioChoose('loginType', 1)
|
||||||
|
.formInput('password', password)
|
||||||
|
.formInput('confirmPassword', password)
|
||||||
|
.wait(2000)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.wait(2000)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.waitFormLoading()
|
||||||
|
.url()
|
||||||
|
.should('include', listUrl)
|
||||||
|
.closeNotice()
|
||||||
|
.waitStatusActiveByRefresh();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![click-step-next](/docs/zh/test/images/e2e/form/click-step-next.png)
|
||||||
|
|
||||||
|
- `clickStepActionCancelButton`
|
||||||
|
- 点击分步表单的取消按钮
|
||||||
|
- 以镜像创建云主机用例`test/e2e/integration/pages/compute/image.spec.js`为例
|
||||||
|
- 只验证能成功进入到创建云主机页面,然后点击取消按钮完成该用例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create instance with cancel', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Create Instance')
|
||||||
|
.wait(2000)
|
||||||
|
.clickStepActionCancelButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 对表单项的操作
|
||||||
|
|
||||||
|
通过页面查看元素的结构、样式等,发现,所有的表单项,都具有`id`,而且对应于开发时编写的表单配置`formItem`的`name`属性,也可直接通过查看页面内元素的`id`获取`name`,如下图所示,`form-item-col-`之后的内容便是`name`
|
||||||
|
|
||||||
|
![form-name](/docs/zh/test/images/e2e/form/form-name.png)
|
||||||
|
|
||||||
|
- `formInput`
|
||||||
|
- 带有`input`输入框的表单项输入内容
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`value`,输入的内容
|
||||||
|
- 以编辑云主机用例`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully edit', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Edit')
|
||||||
|
.formInput('name', newname)
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.wait(2000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![input](/docs/zh/test/images/e2e/form/input.png)
|
||||||
|
|
||||||
|
- `formJsonInput`
|
||||||
|
- 带有`textarea`输入框的表单项输入`json`格式内容
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`content`,输入的对象
|
||||||
|
- 以创建堆栈,编写`json`型的参数`test/e2e/integration/pages/heat/stack.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
const volumeJson = {
|
||||||
|
name: volumeName,
|
||||||
|
};
|
||||||
|
cy.clickHeaderButton(1, 2000)
|
||||||
|
.formAttachFile('content', contentFile)
|
||||||
|
.formAttachFile('params', paramFile)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.wait(2000)
|
||||||
|
.formInput('name', name)
|
||||||
|
.formJsonInput('volume_name_spec', volumeJson)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.waitFormLoading()
|
||||||
|
.wait(5000)
|
||||||
|
.tableSearchSelectText('Name', name)
|
||||||
|
.waitStatusActiveByRefresh();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![textarea-json](/docs/zh/test/images/e2e/form/textarea-json.png)
|
||||||
|
|
||||||
|
- `formCheckboxClick`
|
||||||
|
- 点击表单项中的`checkbox`
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`index`,默认为`0`
|
||||||
|
- 以云主机修改配置`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully resize', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMoreSub('Resize', 'Configuration Update')
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelect('newFlavor')
|
||||||
|
.formCheckboxClick('option')
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.waitStatusActiveByRefresh();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![checkbox](/docs/zh/test/images/e2e/form/checkbox.png)
|
||||||
|
|
||||||
|
- `formTableSelectAll`
|
||||||
|
- 对表格选择类型的表单项做全选操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 以云硬盘类型修改访问`test/e2e/integration/pages/storage/volume-type.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage access to projects', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Manage Access')
|
||||||
|
.formCheckboxClick('isPublic')
|
||||||
|
.formTableSelectAll('access')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![select-all](/docs/zh/test/images/e2e/form/select-all.png)
|
||||||
|
|
||||||
|
- `formTableNotSelectAll`
|
||||||
|
- 对表格选择类型的表单项做取消全选操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 以主机集合管理主机时不选择主机`test/e2e/integration/pages/compute/aggregate.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage host: no host', () => {
|
||||||
|
cy.tableSearchText(newname)
|
||||||
|
.clickActionInMore('Manage Host')
|
||||||
|
.formTableNotSelectAll('hosts')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![unselect-all](/docs/zh/test/images/e2e/form/unselect-all.png)
|
||||||
|
|
||||||
|
- `formTableSelect`
|
||||||
|
- 对表格选择类型的表单项做选择操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`value`,如果设置`value`,则选择表格中含有该值的条目,如果不设置`value`,则选择表格中的第一个条目
|
||||||
|
- 以云主机挂载网卡选择网络`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully attach interface', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMoreSub('Attach Interface', 'Related Resources')
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelect('network')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![select-table](/docs/zh/test/images/e2e/form/select-table.png)
|
||||||
|
|
||||||
|
- `formTableSelectBySearch`
|
||||||
|
- 对表格选择类型的表单项,先做搜索操作,然后选择条目中的第一条
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`value`,搜索内容,一般是对搜索项中`名称`的搜索
|
||||||
|
- 参数`waitTime`,搜索后等待时间,不设置,等待 2 秒钟
|
||||||
|
- 以云主机挂载云硬盘选择云硬盘`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
- 操作成功后,进入云硬盘列表页查看云硬盘的状态为“已使用”
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully attach volume', () => {
|
||||||
|
// prepair volume
|
||||||
|
cy.visitPage(listUrl)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.clickActionInMoreSub('Attach Volume', 'Related Resources')
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelectBySearch('volume', volumeName)
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.wait(5000);
|
||||||
|
|
||||||
|
// check attach successful
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.goToDetail()
|
||||||
|
.clickDetailTab('Volume')
|
||||||
|
.tableSearchText(volumeName)
|
||||||
|
.checkColumnValue(2, 'In-use');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![select-table-search](/docs/zh/test/images/e2e/form/select-table-search.png)
|
||||||
|
|
||||||
|
- `formTableSelectBySearchOption`
|
||||||
|
- 对表格选择类型的表单项,先做搜索操作,然后选择条目中的第一条
|
||||||
|
- 搜索是对搜索项的选择,不同于`formTableSelectBySearch`是基于输入
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`name`,搜索项的名称
|
||||||
|
- 参数`value`,搜索项对应的值
|
||||||
|
- 参数`waitTime`,搜索后的等待时间,默认为 2 秒
|
||||||
|
- 以创建全量备份`test/e2e/integration/pages/storage/backup.spec.js`为例
|
||||||
|
- 选择状态为使用中的云硬盘
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create full bakcup', () => {
|
||||||
|
cy.clickHeaderButton(1, 5000)
|
||||||
|
.formInput('name', name)
|
||||||
|
.formTableSelectBySearch('volume', volumeName)
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.wait(5000)
|
||||||
|
.waitTableLoading();
|
||||||
|
|
||||||
|
cy.clickHeaderButton(1, 5000)
|
||||||
|
.formInput('name', nameIns)
|
||||||
|
.formTableSelectBySearchOption('volume', 'Status', 'In-use')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
|
||||||
|
cy.wait(30000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![select-table-option](/docs/zh/test/images/e2e/form/select-table-option.png)
|
||||||
|
|
||||||
|
- `formSelect`
|
||||||
|
- 对选择器类型的表单项的操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`label`,选中的内容,如果不设置,选中第一个选项,如果设置,选择`label`对应的选项
|
||||||
|
- 以创建云主机组选择策略`test/e2e/integration/pages/compute/server-group.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
cy.clickHeaderButton(1)
|
||||||
|
.formInput('name', name)
|
||||||
|
.formSelect('policy')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![select](/docs/zh/test/images/e2e/form/select.png)
|
||||||
|
|
||||||
|
- 以网络 QoS 策略创建带宽限制规则时设置方向为“入方向”`test/e2e/integration/pages/network/qos-policy.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create bandwidth ingress limit rule', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Create Bandwidth Limit Rule')
|
||||||
|
.formSelect('direction', 'ingress')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![select-value](/docs/zh/test/images/e2e/form/select-value.png)
|
||||||
|
|
||||||
|
- `formRadioChoose`
|
||||||
|
- 对单选类型的表单项的操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`itemIndex`,选中第几项,默认为 0,即选择第一项
|
||||||
|
- 以创建密钥选择“导入密钥”`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create by file', () => {
|
||||||
|
cy.clickHeaderButton(1)
|
||||||
|
.formRadioChoose('type', 1)
|
||||||
|
.formInput('name', nameByFile)
|
||||||
|
.formAttachFile('public_key', filename)
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.tableSearchText(nameByFile)
|
||||||
|
.checkTableFirstRow(nameByFile);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![radio](/docs/zh/test/images/e2e/form/radio.png)
|
||||||
|
|
||||||
|
- `formAttachFile`
|
||||||
|
- 对上传文件的表单项的操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`filename`,上传文件的名称,文件需要预先保存在`test/e2e/fixtures`目录下
|
||||||
|
- 以创建密钥选择文件为例`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create by file', () => {
|
||||||
|
cy.clickHeaderButton(1)
|
||||||
|
.formRadioChoose('type', 1)
|
||||||
|
.formInput('name', nameByFile)
|
||||||
|
.formAttachFile('public_key', filename)
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.tableSearchText(nameByFile)
|
||||||
|
.checkTableFirstRow(nameByFile);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![attach-file](/docs/zh/test/images/e2e/form/attach-file.png)
|
||||||
|
|
||||||
|
- 以创建镜像选择文件为例`test/e2e/integration/pages/compute/image.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
cy.clickHeaderButton(1)
|
||||||
|
.url()
|
||||||
|
.should('include', `${listUrl}/create`)
|
||||||
|
.formInput('name', name)
|
||||||
|
.formAttachFile('file', filename)
|
||||||
|
.formSelect('disk_format', 'QCOW2 - QEMU Emulator')
|
||||||
|
.formSelect('os_distro', 'Others')
|
||||||
|
.formInput('os_version', 'cirros-0.4.0-x86_64')
|
||||||
|
.formInput('os_admin_user', 'root')
|
||||||
|
.formSelect('usage_type', 'Common Server')
|
||||||
|
.formText('description', name)
|
||||||
|
.clickFormActionSubmitButton()
|
||||||
|
.wait(2000)
|
||||||
|
.url()
|
||||||
|
.should('include', listUrl)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.waitStatusActiveByRefresh();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![attach-file-image](/docs/zh/test/images/e2e/form/attach-file-image.png)
|
||||||
|
|
||||||
|
- `formAddSelectAdd`
|
||||||
|
- 对可增加条目的表单项的操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 以主机集合管理元数据时,添加新的自定义元数据`test/e2e/integration/pages/compute/aggregate.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage metadata', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Manage Metadata')
|
||||||
|
.wait(5000)
|
||||||
|
.formAddSelectAdd('customs')
|
||||||
|
.formInputKeyValue('customs', 'key', 'value')
|
||||||
|
.formTransferLeftCheck('systems', 0)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![add-select](/docs/zh/test/images/e2e/form/add-select.png)
|
||||||
|
|
||||||
|
- `formSwitch`
|
||||||
|
- 对开关型的表单项的点击操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 以创建具有共享属性的网络 QoS 策略`test/e2e/integration/pages/network/qos-policy.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
cy.clickHeaderButton(1)
|
||||||
|
.wait(2000)
|
||||||
|
.formInput('name', name)
|
||||||
|
.formText('description', name)
|
||||||
|
.formSwitch('shared')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![switch](/docs/zh/test/images/e2e/form/switch.png)
|
||||||
|
|
||||||
|
- `formButtonClick`
|
||||||
|
- 对表单项中的按钮的点击操作
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 以项目更改配额时展开/关闭“高级选项”`test/e2e/integration/pages/identity/project.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully edit quota', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Edit Quota')
|
||||||
|
.formInput('instances', 11)
|
||||||
|
.formButtonClick('more')
|
||||||
|
.wait(2000)
|
||||||
|
.formButtonClick('more')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![more](/docs/zh/test/images/e2e/form/more.png)
|
||||||
|
|
||||||
|
![more-open](/docs/zh/test/images/e2e/form/more-open.png)
|
||||||
|
|
||||||
|
- `formTransfer`
|
||||||
|
- 对穿梭框类型的表单操作
|
||||||
|
1. 对左侧的穿梭框基于搜索展示指定待选条目
|
||||||
|
2. 选中待选条目的第一条
|
||||||
|
3. 点击穿梭框中间的方向按钮,使得选中内容进入到右侧穿梭框中
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`value`,搜索内容
|
||||||
|
- 以项目管理用户`test/e2e/integration/pages/identity/project.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage user', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Manage User')
|
||||||
|
.formTransfer('select_user', username)
|
||||||
|
.formTransferRight('select_user', username)
|
||||||
|
.formSelect('select_user', 'admin')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![transfer-left](/docs/zh/test/images/e2e/form/transfer-left.png)
|
||||||
|
|
||||||
|
- `formTransferRight`
|
||||||
|
- 对右侧的穿梭框基于搜索展示指定待选条目
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`value`,搜索内容
|
||||||
|
- 以用户组管理用户为例`test/e2e/integration/pages/identity/user-group.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage user', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Manage User')
|
||||||
|
.formTransfer('select_user', username)
|
||||||
|
.formTransferRight('select_user', username)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.goToDetail()
|
||||||
|
.clickDetailTab('Sub User', 'userGroup');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![transfer-right](/docs/zh/test/images/e2e/form/transfer-right.png)
|
||||||
|
|
||||||
|
- `formTabClick`
|
||||||
|
- 点击带有 Tab 的表单项中的 Tab
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`index`,指定的 Tab 的下标
|
||||||
|
- 以编辑浮动 IP,切换到共享策略为例`test/e2e/integration/pages/network/floatingip.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully edit', () => {
|
||||||
|
cy.clickFirstActionButton()
|
||||||
|
.formText('description', 'description')
|
||||||
|
.formTabClick('qos_policy_id', 1)
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelectBySearch('qos_policy_id', policyName)
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.wait(2000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![tab](/docs/zh/test/images/e2e/form/tab.png)
|
||||||
|
|
||||||
|
- `formInputKeyValue`
|
||||||
|
- 对`KeyValue`组件的表单项进行输入操作,一般是配合`formAddSelectAdd`使用,对添加的新的`KeyValue`组件的条目,输入内容
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`key`,左侧 input 输入的内容
|
||||||
|
- 参数`value`,右侧 input 输入的内容
|
||||||
|
- 以主机集合管理元数据时,添加新的自定义元数据`test/e2e/integration/pages/compute/aggregate.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage metadata', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Manage Metadata')
|
||||||
|
.wait(5000)
|
||||||
|
.formAddSelectAdd('customs')
|
||||||
|
.formInputKeyValue('customs', 'key', 'value')
|
||||||
|
.formTransferLeftCheck('systems', 0)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![key-value](/docs/zh/test/images/e2e/form/key-value.png)
|
||||||
|
|
||||||
|
- `formTransferLeftCheck`
|
||||||
|
- 对左侧的穿梭框的操作
|
||||||
|
1. 选中左侧穿梭框中的指定节点
|
||||||
|
2. 点击穿梭框中间的方向按钮,使得选中内容进入到右侧穿梭框中
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`index`,节点的下标
|
||||||
|
- 以主机集合管理元数据时,添加新的自定义元数据`test/e2e/integration/pages/compute/aggregate.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage metadata', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Manage Metadata')
|
||||||
|
.wait(5000)
|
||||||
|
.formAddSelectAdd('customs')
|
||||||
|
.formInputKeyValue('customs', 'key', 'value')
|
||||||
|
.formTransferLeftCheck('systems', 0)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![transfer-left-click](/docs/zh/test/images/e2e/form/transfer-left-click.png)
|
||||||
|
|
||||||
|
- `formTransferRightCheck`
|
||||||
|
- 对右侧的穿梭框的操作
|
||||||
|
1. 选中右侧穿梭框中的节点
|
||||||
|
2. 点击穿梭框中间的方向按钮,使得选中内容进入到左侧穿梭框中
|
||||||
|
- 参数`formItemName`,即开发代码中`formItem`的`name`值
|
||||||
|
- 参数`index`,节点的下标
|
||||||
|
- 以云主机类型,修改元数据`test/e2e/integration/pages/compute/flavor.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully manage metadata', () => {
|
||||||
|
cy.clickTab('Custom')
|
||||||
|
.tableSearchText(customName)
|
||||||
|
.clickActionButtonByTitle('Manage Metadata')
|
||||||
|
.wait(5000)
|
||||||
|
.formTransferLeftCheck('systems', 0)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
|
||||||
|
// todo: remove key-value metadata
|
||||||
|
cy.clickTab('Custom')
|
||||||
|
.tableSearchText(customName)
|
||||||
|
.clickActionButtonByTitle('Manage Metadata')
|
||||||
|
.wait(5000)
|
||||||
|
.formTransferRightCheck('systems', 0)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![transfer-right-check](/docs/zh/test/images/e2e/form/transfer-right-check.png)
|
||||||
|
|
||||||
|
对资源操作的各种操作,主要用到了上方介绍的函数,函数的具体编写,请查看`test/e2e/support/form-commands.js`
|
548
docs/zh/test/3-2-E2E-table-operation.md
Normal file
@ -0,0 +1,548 @@
|
|||||||
|
简体中文 | [English](/docs/en/test/3-2-E2E-table-operation.md)
|
||||||
|
|
||||||
|
因为前端框架使用的一致性,我们在编写表单操作的相关用例,选取元素并进行操作时,往往会发现有很强的规律性,所以我们对大多数表格操作都编写了相应的 Cypress 函数,极大的减少了编写测试用例的难度,以下会对主要使用的表格操作函数做出详细的说明。
|
||||||
|
|
||||||
|
## 对表格整体的操作
|
||||||
|
|
||||||
|
主要包含:等待列表加载完成
|
||||||
|
|
||||||
|
- `waitTableLoading`
|
||||||
|
- 等待列表加载完成
|
||||||
|
- 列表在加载过程中,会有`loading`状态展示,等待该状态结束
|
||||||
|
|
||||||
|
![wait-table-loading](/docs/zh/test/images/e2e/table/wait-table-loading.png)
|
||||||
|
|
||||||
|
- `checkTableFirstRow`
|
||||||
|
- 验证表格第一行是否包含指定内容,一般用于创建后验证创建资源是否存在
|
||||||
|
- 参数`name`,第一行需要包含的内容,一般用于验证名称是否存在
|
||||||
|
- 以查看密钥详情`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
- 创建后,检查密钥是否存在,验证成功后进入详情页
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully detail', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.checkTableFirstRow(name)
|
||||||
|
.goToDetail()
|
||||||
|
.checkDetailName(name);
|
||||||
|
cy.goBackToList(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![check-first-row](/docs/zh/test/images/e2e/table/check-first-row.png)
|
||||||
|
|
||||||
|
- `tableSearchText`
|
||||||
|
- 在表格上方的搜索栏中输入内容,并等待搜索完成
|
||||||
|
- 参数`str`,搜索的内容,一般是搜索名称
|
||||||
|
- 通过搜索,使得待操作的资源位于表格中的第一行,以便进行后续的操作
|
||||||
|
- 以查看密钥详情`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
1. 创建后,使用名称搜索密钥,并等待搜索完成
|
||||||
|
2. 检查表格中第一行是否包含指定名称的资源
|
||||||
|
3. 进入详情页,检查名称是否与预期一致
|
||||||
|
4. 返回列表页
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully detail', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.checkTableFirstRow(name)
|
||||||
|
.goToDetail()
|
||||||
|
.checkDetailName(name);
|
||||||
|
cy.goBackToList(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![search](/docs/zh/test/images/e2e/table/search.png)
|
||||||
|
|
||||||
|
- `tableSimpleSearchText`
|
||||||
|
- 在表格上方的搜索栏中输入内容,并等待搜索完成
|
||||||
|
- 有些表格使用的是简单搜索,搜索项只支持输入文字,此时搜索框使用的组件与`tableSearchText`中的搜索框组件不一样
|
||||||
|
- 参数`str`,搜索的内容,一般是搜索名称
|
||||||
|
- 通过搜索,使得待操作的资源位于表格中的第一行,以便进行后续的操作
|
||||||
|
- 以搜索系统信息中的服务`test/e2e/integration/pages/configuration/system.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully services', () => {
|
||||||
|
cy.tableSimpleSearchText('nova');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![simple-search](/docs/zh/test/images/e2e/table/simple-search.png)
|
||||||
|
|
||||||
|
- `tableSearchSelect`
|
||||||
|
- 使用表格上方的搜索栏中的选择项进行搜索,并等待搜索完成
|
||||||
|
1. 点击输入框,在待选搜索项中选择搜索项
|
||||||
|
2. 点击选中搜索类别下的选择项
|
||||||
|
3. 等待搜索完成
|
||||||
|
- 参数`name`,搜索项的名称
|
||||||
|
- 参数`value`,搜索项对应的选择项的标签
|
||||||
|
- 通过搜索,使得待操作的资源位于表格中的第一行,以便进行后续的操作
|
||||||
|
- 以浮动IP绑定云主机`test/e2e/integration/pages/network/floatingip.spec.js`为例
|
||||||
|
1. 在浮动IP表格中,搜索`状态`为`停止`的浮动IP
|
||||||
|
2. 对表格中的第一个资源点击`关联`操作
|
||||||
|
3. 完成绑定云主机的操作
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully associate instance', () => {
|
||||||
|
cy.tableSearchSelect('Status', 'Down')
|
||||||
|
.clickActionInMore('Associate')
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelectBySearch('instance', instanceName)
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelect('port')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![search-select-1](/docs/zh/test/images/e2e/table/search-select-1.png)
|
||||||
|
|
||||||
|
![search-select-2](/docs/zh/test/images/e2e/table/search-select-2.png)
|
||||||
|
|
||||||
|
![search-select-3](/docs/zh/test/images/e2e/table/search-select-3.png)
|
||||||
|
|
||||||
|
- `tableSearchSelectText`
|
||||||
|
- 使用表格上方的搜索栏进行搜索,并等待搜索完成
|
||||||
|
1. 点击输入框,在待选搜索项中选择搜索项
|
||||||
|
2. 输入搜索内容,回车
|
||||||
|
3. 等待搜索完成
|
||||||
|
- 不选择搜索项时直接输入,是对第一个支持输入的搜索项进行搜索
|
||||||
|
- 参数`name`,搜索项的名称
|
||||||
|
- 参数`value`,输入的内容
|
||||||
|
- 通过搜索,使得待操作的资源位于表格中的第一行,以便进行后续的操作
|
||||||
|
- 以创建堆栈`test/e2e/integration/pages/heat/stack.spec.js`为例
|
||||||
|
1. 创建后,进入资源列表页
|
||||||
|
2. 在列表页使用名称搜索
|
||||||
|
3. 等待资源的状态可用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
const volumeJson = {
|
||||||
|
name: volumeName,
|
||||||
|
};
|
||||||
|
cy.clickHeaderButton(1, 2000)
|
||||||
|
.formAttachFile('content', contentFile)
|
||||||
|
.formAttachFile('params', paramFile)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.wait(2000)
|
||||||
|
.formInput('name', name)
|
||||||
|
.formJsonInput('volume_name_spec', volumeJson)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.waitFormLoading()
|
||||||
|
.wait(5000)
|
||||||
|
.tableSearchSelectText('Name', name)
|
||||||
|
.waitStatusActiveByRefresh();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![search-text-1](/docs/zh/test/images/e2e/table/search-text-1.png)
|
||||||
|
|
||||||
|
![search-text-2](/docs/zh/test/images/e2e/table/search-text-2.png)
|
||||||
|
|
||||||
|
![search-text-3](/docs/zh/test/images/e2e/table/search-text-3.png)
|
||||||
|
|
||||||
|
- `checkEmptyTable`
|
||||||
|
- 验证表格为空表格
|
||||||
|
- 一般用于删除资源后验证
|
||||||
|
- 以删除路由器`test/e2e/integration/pages/network/router.spec.js`为例
|
||||||
|
1. 关闭外网网关
|
||||||
|
2. 删除
|
||||||
|
3. 搜索
|
||||||
|
4. 验证表格为空表格,即删除成功
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully close external gateway and delete', () => {
|
||||||
|
cy.tableSearchText(newname)
|
||||||
|
.clickConfirmActionInMore('Close External Gateway')
|
||||||
|
.clickConfirmActionInMore('Delete')
|
||||||
|
.tableSearchText(newname)
|
||||||
|
.checkEmptyTable();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `goToDetail`
|
||||||
|
- 访问第一行资源的详情页,并等待详情页加载完成
|
||||||
|
- 参数`index`,链接所在列的下标,默认为`1`
|
||||||
|
- 参数`waitTime`,详情页加载完后等待的时间
|
||||||
|
- 以镜像`test/e2e/integration/pages/compute/image.spec.js`为例
|
||||||
|
1. 搜索
|
||||||
|
2. 进入详情页
|
||||||
|
3. 验证详情名称
|
||||||
|
4. 返回列表页
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully detail', () => {
|
||||||
|
cy.tableSearchText(name).goToDetail();
|
||||||
|
cy.checkDetailName(name);
|
||||||
|
cy.goBackToList(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![detail-1](/docs/zh/test/images/e2e/table/detail-1.png)
|
||||||
|
|
||||||
|
![detail-2](/docs/zh/test/images/e2e/table/detail-2.png)
|
||||||
|
|
||||||
|
- `checkColumnValue`
|
||||||
|
- 验证第一行指定列的内容是否符合预期
|
||||||
|
- 参数`columnIndex`,指定列的下标
|
||||||
|
- 参数`value`,预期的值
|
||||||
|
- 以云主机`test/e2e/integration/pages/compute/image.spec.js`为例
|
||||||
|
1. 搜索
|
||||||
|
2. 关闭云主机
|
||||||
|
3. 验证云主机的状态为`关闭`
|
||||||
|
4. 验证批量操作中的`关闭`操作不可用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully stop', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickConfirmActionInMoreSub('Stop', 'Instance Status')
|
||||||
|
.wait(10000)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.checkColumnValue(6, 'Shutoff')
|
||||||
|
.selectFirst()
|
||||||
|
.clickHeaderButtonByTitle('Stop')
|
||||||
|
.checkDisableAction(2000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![check-value](/docs/zh/test/images/e2e/table/check-value.png)
|
||||||
|
|
||||||
|
- `selectFirst`
|
||||||
|
- 选中表格中第一行,以便做后续的批量操作
|
||||||
|
- 以云主机`test/e2e/integration/pages/compute/image.spec.js`为例
|
||||||
|
1. 搜索
|
||||||
|
2. 关闭云主机
|
||||||
|
3. 验证云主机的状态为`关闭`
|
||||||
|
4. 选中第一行
|
||||||
|
5. 点击批量操作中的`关闭`按钮
|
||||||
|
6. 弹出错误提示
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully stop', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickConfirmActionInMoreSub('Stop', 'Instance Status')
|
||||||
|
.wait(10000)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.checkColumnValue(6, 'Shutoff')
|
||||||
|
.selectFirst()
|
||||||
|
.clickHeaderButtonByTitle('Stop')
|
||||||
|
.checkDisableAction(2000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![select-first](/docs/zh/test/images/e2e/table/select-first.png)
|
||||||
|
|
||||||
|
- `selectAll`
|
||||||
|
- 选中表格中所有条目,以便做后续的批量操作
|
||||||
|
- 通常是为了清空数据使用
|
||||||
|
|
||||||
|
- `waitStatusActiveByRefresh`
|
||||||
|
- 每`5`秒点击表格上方的刷新按钮,直到资源状态变为可用状态
|
||||||
|
- 资源在创建或变更后,往往需要一定的时间才能变为可用状态,之后才能进行后续操作
|
||||||
|
- 以创建堆栈`test/e2e/integration/pages/heat/stack.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
const volumeJson = {
|
||||||
|
name: volumeName,
|
||||||
|
};
|
||||||
|
cy.clickHeaderButton(1, 2000)
|
||||||
|
.formAttachFile('content', contentFile)
|
||||||
|
.formAttachFile('params', paramFile)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.wait(2000)
|
||||||
|
.formInput('name', name)
|
||||||
|
.formJsonInput('volume_name_spec', volumeJson)
|
||||||
|
.clickStepActionNextButton()
|
||||||
|
.waitFormLoading()
|
||||||
|
.wait(5000)
|
||||||
|
.tableSearchSelectText('Name', name)
|
||||||
|
.waitStatusActiveByRefresh();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![wait-1](/docs/zh/test/images/e2e/table/wait-1.png)
|
||||||
|
|
||||||
|
![wait-2](/docs/zh/test/images/e2e/table/wait-2.png)
|
||||||
|
|
||||||
|
## 对按钮的操作
|
||||||
|
|
||||||
|
主要包含
|
||||||
|
- 位于表单上放的主按钮操作(一般创建操作)、批量操作
|
||||||
|
- 位于表单每一行的行操作
|
||||||
|
### 表单上方按钮的操作
|
||||||
|
|
||||||
|
表格上方的按钮一般包含:刷新、创建、批量操作按钮、配置表格列表项、下载
|
||||||
|
- `clickHeaderButton`
|
||||||
|
- 点击表格上方的按钮,
|
||||||
|
- 参数`buttonIndex`,表格上方按钮的下标
|
||||||
|
- 参数`waitTime`,点击后的等待时间,默认为 2 秒
|
||||||
|
- 一般,创建按钮的下标是 1
|
||||||
|
- 以创建密钥用例`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create', () => {
|
||||||
|
cy.clickHeaderButton(1)
|
||||||
|
.formInput('name', name)
|
||||||
|
.clickModalActionSubmitButton()
|
||||||
|
.wait(5000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![header-btn-index](/docs/zh/test/images/e2e/table/header-btn-index.png)
|
||||||
|
|
||||||
|
- `clickHeaderButtonByTitle`
|
||||||
|
- 通过名称点击表格上方的按钮,一般用于批量操作按钮的点击
|
||||||
|
- 参数`title`,表格上方按钮上的文字
|
||||||
|
- 参数`waitTime`,点击后的等待时间,默认为 2 秒
|
||||||
|
- 以关闭状态下的云主机无法进行关闭操作`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
- 点击表单顶部的关闭按钮
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully stop', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickConfirmActionInMoreSub('Stop', 'Instance Status')
|
||||||
|
.wait(10000)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.checkColumnValue(6, 'Shutoff')
|
||||||
|
.selectFirst()
|
||||||
|
.clickHeaderButtonByTitle('Stop')
|
||||||
|
.checkDisableAction(2000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![header-btn-title](/docs/zh/test/images/e2e/table/header-btn-title.png)
|
||||||
|
|
||||||
|
- `clickHeaderConfirmButtonByTitle`
|
||||||
|
- 该函数会完成
|
||||||
|
1. 通过名称点击表格上方的按钮,页面弹出确认操作提示
|
||||||
|
2. 点击`确认`按钮
|
||||||
|
- 参数`title`,表格上方按钮上的文字
|
||||||
|
- 参数`waitTime`,点击后的等待时间,默认为 2 秒
|
||||||
|
- 以释放浮动IP`test/e2e/integration/pages/network/floatingip.spec.js`为例
|
||||||
|
- 全选停止状态的浮动IP,并批量释放
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete', () => {
|
||||||
|
cy.tableSearchSelect('Status', 'Down')
|
||||||
|
.selectAll()
|
||||||
|
.clickHeaderConfirmButtonByTitle('Release');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![header-confirm-title](/docs/zh/test/images/e2e/table/header-confirm-title.png)
|
||||||
|
|
||||||
|
### 表单第一行的行操作
|
||||||
|
|
||||||
|
- `clickFirstActionButton`
|
||||||
|
- 点击表单第一行的操作列中的第一个按钮,一般用于对弹窗型操作按钮
|
||||||
|
单页型操作按钮的点击
|
||||||
|
- 以编辑用户`test/e2e/integration/pages/identity/user.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully edit', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickFirstActionButton()
|
||||||
|
.formInput('name', newname)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![click-first](/docs/zh/test/images/e2e/table/click-first.png)
|
||||||
|
|
||||||
|
- `clickActionButtonByTitle`
|
||||||
|
- 根据标题点击第一行中的操作
|
||||||
|
- 以编辑、启动服务`test/e2e/integration/pages/configuration/system.spec.js`为例
|
||||||
|
- 当服务启动时,点击`禁用`按钮
|
||||||
|
- 当服务停止时,点击`启用`按钮
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully disable compute services', () => {
|
||||||
|
cy.clickTab(computeServicesTab)
|
||||||
|
.tableSearchText('nova-compute')
|
||||||
|
.clickActionButtonByTitle('Disable')
|
||||||
|
.formText('disabled_reason', reason)
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully enable compute services', () => {
|
||||||
|
cy.clickTab(computeServicesTab)
|
||||||
|
.tableSearchSelect('Service Status', 'Disabled')
|
||||||
|
.clickActionButtonByTitle('Enable')
|
||||||
|
.clickConfirmActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![action-by-title](/docs/zh/test/images/e2e/table/action-by-title.png)
|
||||||
|
|
||||||
|
![action-by-title-2](/docs/zh/test/images/e2e/table/action-by-title-2.png)
|
||||||
|
|
||||||
|
- `clickActionInMore`
|
||||||
|
- 根据标题点击第一行中`更多`中的操作
|
||||||
|
- 以点击镜像的创建云主机按钮`test/e2e/integration/pages/compute/image.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully create instance with cancel', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMore('Create Instance')
|
||||||
|
.wait(2000)
|
||||||
|
.clickStepActionCancelButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![action-in-more](/docs/zh/test/images/e2e/table/action-in-more.png)
|
||||||
|
|
||||||
|
- `clickActionInMoreSub`
|
||||||
|
- 根据标题点击第一行操作的子菜单下的操作
|
||||||
|
- 参数`title`,按钮的标题
|
||||||
|
- 参数`subMenu`,子菜单的标题
|
||||||
|
- 以云主机点击`关联资源`下的`挂载网卡` `test/e2e/integration/pages/compute/image.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully attach interface', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickActionInMoreSub('Attach Interface', 'Related Resources')
|
||||||
|
.wait(5000)
|
||||||
|
.formTableSelect('network')
|
||||||
|
.clickModalActionSubmitButton();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![action-in-sub](/docs/zh/test/images/e2e/table/action-in-sub.png)
|
||||||
|
|
||||||
|
- `checkActionDisabledInFirstRow`
|
||||||
|
- 验证指定名称的资源的指定操作不可用
|
||||||
|
1. 基于指定名称搜索资源
|
||||||
|
2. 验证搜索结果的第一行中的操作列`更多`中不存在指定操作
|
||||||
|
- 参数`title`,操作的名称
|
||||||
|
- 参数`name`,资源的名称
|
||||||
|
- 资源处于某些状态后,某些操作是需要被禁用的,行操作列表中的第一个操作,如果不可操作,则处于`禁用`状态,而`更多`中的操作,如果不可用,则不展示
|
||||||
|
- 以路由器`test/e2e/integration/pages/network/router.spec.js`为例
|
||||||
|
1. 创建路由器时开启公网网关
|
||||||
|
2. 验证路由器不可删除,即不存在`删除`按钮
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully disable delete', () => {
|
||||||
|
cy.checkActionDisabledInFirstRow('Delete', name);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![disable-more-action](/docs/zh/test/images/e2e/table/disable-more-action.png)
|
||||||
|
|
||||||
|
- `clickFirstActionDisabled`
|
||||||
|
- 验证表格中第一行的操作中的第一个操作不可用
|
||||||
|
- 资源处于某些状态后,某些操作是需要被禁用的,行操作列表中的第一个操作,如果不可操作,则处于`禁用`状态,而`更多`中的操作,如果不可用,则不展示
|
||||||
|
- 以云主机组`test/e2e/integration/pages/compute/server-group.spec.js`为例
|
||||||
|
1. 在云主机组下创建云主机
|
||||||
|
2. 验证含有云主机的云主机不可删除
|
||||||
|
3. 删除云主机后,云主机组删除成功
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete', () => {
|
||||||
|
cy.clickFirstActionDisabled();
|
||||||
|
cy.forceDeleteInstance(instanceName);
|
||||||
|
cy.wait(5000);
|
||||||
|
cy.visitPage(listUrl)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.clickConfirmActionInFirst()
|
||||||
|
.checkEmptyTable();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![disable-first](/docs/zh/test/images/e2e/table/disable-first.png)
|
||||||
|
|
||||||
|
- `clickConfirmActionInFirst`
|
||||||
|
- 完成表格中第一行的第一个操作按钮对应的操作
|
||||||
|
1. 点击表格中第一行的第一个操作按钮,该操作是一个确认型操作
|
||||||
|
2. 点击`确认`按钮,并等待请求完成,关闭请求成功的提示信息
|
||||||
|
- 参数`waitTime`,关闭操作成功提示后的等待时间
|
||||||
|
- 以云主机组`test/e2e/integration/pages/compute/server-group.spec.js`为例
|
||||||
|
1. 在云主机组下创建云主机
|
||||||
|
2. 验证含有云主机的云主机不可删除
|
||||||
|
3. 删除云主机后,云主机组删除成功
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete', () => {
|
||||||
|
cy.clickFirstActionDisabled();
|
||||||
|
cy.forceDeleteInstance(instanceName);
|
||||||
|
cy.wait(5000);
|
||||||
|
cy.visitPage(listUrl)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.clickConfirmActionInFirst()
|
||||||
|
.checkEmptyTable();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![first-confirm](/docs/zh/test/images/e2e/table/first-confirm.png)
|
||||||
|
|
||||||
|
![first-confirm-2](/docs/zh/test/images/e2e/table/first-confirm-2.png)
|
||||||
|
|
||||||
|
- `clickConfirmActionButton`
|
||||||
|
- 完成表格中第一行的列出的操作按钮中对应的操作
|
||||||
|
1. 点击表格中第一行的指定操作,该操作是一个确认型操作
|
||||||
|
2. 点击`确认`按钮,并等待请求完成,关闭请求成功的提示信息
|
||||||
|
- 参数`title`,指定操作的名称
|
||||||
|
- 参数`waitTime`,关闭操作提示成功后的等待时间
|
||||||
|
- 以删除VPN IPsec策略`test/e2e/integration/pages/compute/server-group.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete ipsec policy', () => {
|
||||||
|
cy.clickTab('IPsec Policy')
|
||||||
|
.tableSearchText(ipsecPolicy)
|
||||||
|
.clickConfirmActionButton('Delete');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![confirm-action](/docs/zh/test/images/e2e/table/confirm-action.png)
|
||||||
|
|
||||||
|
- `clickConfirmActionInMore`
|
||||||
|
- 完成表格中第一行的`更多`中对应的操作
|
||||||
|
1. 点击表格中第一行的`更多`中指定操作,该操作是一个确认型操作
|
||||||
|
2. 点击`确认`按钮,并等待请求完成,关闭请求成功的提示信息
|
||||||
|
- 参数`title`,指定操作的名称
|
||||||
|
- 参数`waitTime`,关闭操作提示成功后的等待时间
|
||||||
|
- 以删除路由器`test/e2e/integration/pages/network/router.spec.js`为例
|
||||||
|
1. 搜索
|
||||||
|
2. 完成`更多`中的`关闭公网网关`操作
|
||||||
|
2. 完成`更多`中的`删除`操作
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully close external gateway and delete', () => {
|
||||||
|
cy.tableSearchText(newname)
|
||||||
|
.clickConfirmActionInMore('Close External Gateway')
|
||||||
|
.clickConfirmActionInMore('Delete')
|
||||||
|
.tableSearchText(newname)
|
||||||
|
.checkEmptyTable();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![confirm-more-1](/docs/zh/test/images/e2e/table/confirm-more-1.png)
|
||||||
|
|
||||||
|
![confirm-more-2](/docs/zh/test/images/e2e/table/confirm-more-2.png)
|
||||||
|
|
||||||
|
- `clickConfirmActionInMoreSub`
|
||||||
|
- 完成表格中第一行的`更多`中指定子菜单下对应的操作
|
||||||
|
1. 点击表格中第一行的`更多`中指定子菜单下的指定操作,该操作是一个确认型操作
|
||||||
|
2. 点击`确认`按钮,并等待请求完成,关闭请求成功的提示信息
|
||||||
|
- 参数`title`,指定操作的名称
|
||||||
|
- 参数`subMenu`,指定子菜单的名称
|
||||||
|
- 参数`waitTime`,关闭操作提示成功后的等待时间
|
||||||
|
- 以锁定云主机`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully lock', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.clickConfirmActionInMoreSub('Lock', 'Instance Status')
|
||||||
|
.wait(10000);
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.selectFirst()
|
||||||
|
.clickHeaderButtonByTitle('Start')
|
||||||
|
.checkDisableAction(2000)
|
||||||
|
.clickHeaderButtonByTitle('Stop')
|
||||||
|
.checkDisableAction(2000)
|
||||||
|
.clickHeaderButtonByTitle('Reboot')
|
||||||
|
.checkDisableAction(2000);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![confirm-in-sub](/docs/zh/test/images/e2e/table/confirm-in-sub.png)
|
||||||
|
|
||||||
|
|
||||||
|
对表格操作的各种操作,主要用到了上方介绍的函数,函数的具体编写,请查看`test/e2e/support/table-commands.js`
|
96
docs/zh/test/3-3-E2E-detail-operation.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
简体中文 | [English](/docs/en/test/3-3-E2E-detail-operation.md)
|
||||||
|
|
||||||
|
因为前端框架使用的一致性,我们在编写详情操作的相关用例,选取元素并进行操作时,往往会发现有很强的规律性,所以我们对大多数详情操作都编写了相应的 Cypress 函数,极大的减少了编写测试用例的难度,以下会对主要使用的表格操作函数做出详细的说明。
|
||||||
|
|
||||||
|
- `checkDetailName`
|
||||||
|
- 验证详情页头部包含指定资源名称
|
||||||
|
- 参数`name`,资源名称
|
||||||
|
- 以查看密钥详情`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully detail', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.checkTableFirstRow(name)
|
||||||
|
.goToDetail()
|
||||||
|
.checkDetailName(name);
|
||||||
|
cy.goBackToList(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![name](/docs/zh/test/images/e2e/detail/name.png)
|
||||||
|
|
||||||
|
- `goBackToList`
|
||||||
|
- 点击详情页的`返回`按钮,进入列表页,并等待列表加载完成
|
||||||
|
- 参数`url`,列表url
|
||||||
|
- 如果设置,会验证返回的列表路由是否符合预期
|
||||||
|
- 以查看密钥详情`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
1. 搜索
|
||||||
|
2. 验证表格第一行是否包含指定名称
|
||||||
|
3. 进入详情页
|
||||||
|
4. 验证详情页的名称
|
||||||
|
5. 返回列表页
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully detail', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.checkTableFirstRow(name)
|
||||||
|
.goToDetail()
|
||||||
|
.checkDetailName(name);
|
||||||
|
cy.goBackToList(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![list](/docs/zh/test/images/e2e/detail/list.png)
|
||||||
|
|
||||||
|
- `goBackToList`
|
||||||
|
- 点击详情页的`返回`按钮,进入列表页,并等待列表加载完成
|
||||||
|
- 参数`url`,列表url
|
||||||
|
- 如果设置,会验证返回的列表路由是否符合预期
|
||||||
|
- 以查看密钥详情`test/e2e/integration/pages/compute/keypair.spec.js`为例
|
||||||
|
1. 搜索
|
||||||
|
2. 验证表格第一行是否包含指定名称
|
||||||
|
3. 进入详情页
|
||||||
|
4. 验证详情页的名称
|
||||||
|
5. 返回列表页
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully detail', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.checkTableFirstRow(name)
|
||||||
|
.goToDetail()
|
||||||
|
.checkDetailName(name);
|
||||||
|
cy.goBackToList(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![list](/docs/zh/test/images/e2e/detail/list.png)
|
||||||
|
|
||||||
|
- `clickDetailTab`
|
||||||
|
- 点击详情页下方的指定Tab标签,并等待相关资源列表加载完成
|
||||||
|
- 参数`label`,指定的Tab标签
|
||||||
|
- 参数`urlTab`,路由中的tab属性
|
||||||
|
- 如果设置,会验证切换标签后路由中的tab属性是否符合预期
|
||||||
|
- 参数`waitTime`,切换标签后的等待时间
|
||||||
|
- 以查看网络详情`test/e2e/integration/pages/network/network.spec.js`为例
|
||||||
|
1. 搜索
|
||||||
|
2. 验证表格第一行是否包含指定名称
|
||||||
|
3. 进入详情页
|
||||||
|
4. 验证详情页的名称
|
||||||
|
5. 点击子网Tab,并等待列表加载完成
|
||||||
|
6. 点击端口Tab,并等待列表加载完成
|
||||||
|
5. 返回列表页
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully detail', () => {
|
||||||
|
cy.tableSearchText(name)
|
||||||
|
.checkTableFirstRow(name)
|
||||||
|
.goToDetail()
|
||||||
|
.checkDetailName(name);
|
||||||
|
cy.clickDetailTab('Subnets', 'subnets').clickDetailTab('Ports', 'ports');
|
||||||
|
cy.goBackToList(listUrl);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
![tab](/docs/zh/test/images/e2e/detail/tab.png)
|
||||||
|
|
||||||
|
对详情页主要用到了上方介绍的函数,函数的具体编写,请查看`test/e2e/support/detail-commands.js`
|
276
docs/zh/test/3-4-E2E-resource-operation.md
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
简体中文 | [English](/docs/en/test/3-4-E2E-resource-operation.md)
|
||||||
|
|
||||||
|
在E2E的过程中,创建资源的时候,往往需要先创建关联资源,而删除资源后,也需要删除掉相关资源,所以以完整创建/删除为原则,封装了对相关资源的操作。
|
||||||
|
|
||||||
|
- `createInstance`
|
||||||
|
- 创建云主机,并等待云主机变为`运行中`状态
|
||||||
|
- 参数`name`,云主机的名称
|
||||||
|
- 参数`networkName`,云主机创建时选择的网络名称
|
||||||
|
- 以浮动IP关联云主机`test/e2e/integration/pages/network/floatingip.spec.js`为例
|
||||||
|
- 为了能成功关联云主机,需要满足云主机网卡所在的子网所连接的路由器开启了公网网关
|
||||||
|
1. 创建带有子网的网络`networkName`
|
||||||
|
2. 创建开启了公网网关并连接网络`networkName`子网的路由器`routerName`
|
||||||
|
3. 创建挂载了网络`networkName`上的网卡的云主机`instanceName`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
cy.createRouter({ name: routerName, network: networkName });
|
||||||
|
cy.createInstance({ name: instanceName, networkName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createNetwork`
|
||||||
|
- 创建网络,该网络带有一个子网
|
||||||
|
- 参数`name`,网络的名称
|
||||||
|
- 参数`networkName`,云主机创建时选择的网络名称
|
||||||
|
- 以路由器连接子网为例`test/e2e/integration/pages/network/router.spec.js`为例
|
||||||
|
- 创建了名称为`networkName`的网络,为连接子网做准备
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createNetworkPolicy`
|
||||||
|
- 创建网络QoS策略
|
||||||
|
- 参数`name`,策略的名称
|
||||||
|
- 以虚拟网卡修改QoS为例`test/e2e/integration/pages/network/virtual-adapter.spec.js`为例
|
||||||
|
- 创建了名称为`policyName`的策略,为修改QoS做准备
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource by admin', () => {
|
||||||
|
cy.loginAdmin().wait(5000).createNetworkPolicy({ name: policyName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createRouter`
|
||||||
|
- 创建开启了公网网关的路由器
|
||||||
|
- 参数`name`,路由器的名称
|
||||||
|
- 参数`network`
|
||||||
|
- 若设置,则路由器会连接`network`网络的子网
|
||||||
|
- 以浮动IP关联云主机`test/e2e/integration/pages/network/floatingip.spec.js`为例
|
||||||
|
- 为了能成功关联云主机,需要满足云主机网卡所在的子网所连接的路由器开启了公网网关
|
||||||
|
1. 创建带有子网的网络`networkName`
|
||||||
|
2. 创建开启了公网网关并连接网络`networkName`子网的路由器`routerName`
|
||||||
|
3. 创建挂载了网络`networkName`上的网卡的云主机`instanceName`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
cy.createRouter({ name: routerName, network: networkName });
|
||||||
|
cy.createInstance({ name: instanceName, networkName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `deleteRouter`
|
||||||
|
- 删除路由器,会断开路由器的子网,关闭路由器的公网网关,最终成功删除路由器
|
||||||
|
- 参数`network`
|
||||||
|
- 若设置,则需要先断开路由器的子网
|
||||||
|
- 参数`name`,路由器的名称
|
||||||
|
|
||||||
|
- 以浮动IP删除关联资源`test/e2e/integration/pages/network/floatingip.spec.js`为例
|
||||||
|
- 为了能成功关联云主机,需要满足云主机网卡所在的子网所连接的路由器开启了公网网关
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete related resources', () => {
|
||||||
|
cy.forceDeleteInstance(instanceName);
|
||||||
|
cy.deleteRouter(routerName, networkName);
|
||||||
|
cy.deleteAll('network', networkName);
|
||||||
|
cy.loginAdmin().wait(5000);
|
||||||
|
cy.deleteAll('networkQosPolicy', policyName);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `forceDeleteInstance`
|
||||||
|
- 强制删除云主机,而不是使用软删除
|
||||||
|
- 参数`name`,云主机的名称
|
||||||
|
- 以删除云主机组`test/e2e/integration/pages/compute/server-group.spec.js`为例
|
||||||
|
1. 先删除云主机组下的云主机
|
||||||
|
2. 再成功删除云主机组
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete', () => {
|
||||||
|
cy.clickFirstActionDisabled();
|
||||||
|
cy.forceDeleteInstance(instanceName);
|
||||||
|
cy.wait(5000);
|
||||||
|
cy.visitPage(listUrl)
|
||||||
|
.tableSearchText(name)
|
||||||
|
.clickConfirmActionInFirst()
|
||||||
|
.checkEmptyTable();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createVolume`
|
||||||
|
- 创建云硬盘
|
||||||
|
- 参数`name`,云硬盘的名称
|
||||||
|
- 以云硬盘备份`test/e2e/integration/pages/storage/backup.spec.js`为例
|
||||||
|
- 创建云硬盘的备份,需要先准备好云硬盘
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createVolume(volumeName);
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
cy.createInstance({ name: instanceName, networkName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createSecurityGrouop`
|
||||||
|
- 创建安全组
|
||||||
|
- 参数`name`,安全组的名称
|
||||||
|
- 以虚拟网卡`test/e2e/integration/pages/network/virtual-adapter.spec.js`为例
|
||||||
|
- 测试管理安全组,需要先准备好安全组
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createFip();
|
||||||
|
cy.createSecurityGrouop({ name: securityGroupName });
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
cy.createRouter({ name: routerName, network: networkName });
|
||||||
|
cy.createInstance({ name: instanceName, networkName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createFip`
|
||||||
|
- 创建浮动IP
|
||||||
|
- 以云主机`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
- 测试绑定浮动IP,需要准备好可达的浮动IP
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
cy.createRouter({ name: routerName, network: networkName });
|
||||||
|
cy.createFip();
|
||||||
|
cy.createVolume(volumeName);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createUserGroup`
|
||||||
|
- 创建用户组
|
||||||
|
- 参数`name`,用户组的名称
|
||||||
|
- 以项目`test/e2e/integration/pages/identity/project.spec.js`为例
|
||||||
|
- 测试管理用户组操作,需要准备好用户组
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createUser({ name: username });
|
||||||
|
cy.createUserGroup({ name: userGroupName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createUser`
|
||||||
|
- 创建用户
|
||||||
|
- 参数`name`,用户的名称
|
||||||
|
- 以项目`test/e2e/integration/pages/identity/project.spec.js`为例
|
||||||
|
- 测试管理用户操作,需要准备好用户
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createUser({ name: username });
|
||||||
|
cy.createUserGroup({ name: userGroupName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createProject`
|
||||||
|
- 创建项目
|
||||||
|
- 参数`name`,项目的名称
|
||||||
|
- 以用户`test/e2e/integration/pages/identity/user.spec.js`为例
|
||||||
|
- 测试创建用户,需要准备项目
|
||||||
|
- 测试管理项目权限,需要准备项目
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createProject({ name: projectName });
|
||||||
|
cy.createProject({ name: projectName2 });
|
||||||
|
cy.createUserGroup({ name: userGroupName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `createIronicImage`
|
||||||
|
- 创建裸机使用的镜像
|
||||||
|
- 参数`name`,镜像的名称
|
||||||
|
- 以裸机`test/e2e/integration/pages/compute/ironic.spec.js`为例
|
||||||
|
- 创建裸机,需要能创建裸机的镜像
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully prepair resource', () => {
|
||||||
|
cy.createNetwork({ name: networkName });
|
||||||
|
cy.createRouter({ name: routerName, network: networkName });
|
||||||
|
cy.createFip();
|
||||||
|
cy.createIronicImage({ name: imageName });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `deleteInstance`
|
||||||
|
- 删除云主机
|
||||||
|
- 参数`name`,云主机的名称
|
||||||
|
- 参数`deleteRecycleBin`,默认为`true`,表示需要进入回收站二次删除
|
||||||
|
- 以云主机删除`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete', () => {
|
||||||
|
cy.deleteInstance(newname);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `deleteAllAvailableVolume`
|
||||||
|
- 删除所有可用的云硬盘
|
||||||
|
- 以云主机`test/e2e/integration/pages/compute/instance.spec.js`为例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete related resources', () => {
|
||||||
|
cy.deleteAll('fip');
|
||||||
|
cy.deleteRouter(routerName, networkName);
|
||||||
|
cy.deleteAll('network', networkName);
|
||||||
|
cy.deleteAll('volume', volumeName);
|
||||||
|
cy.deleteAllAvailableVolume();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `deleteAll`
|
||||||
|
- 删除符合条件的资源
|
||||||
|
- 参数`resourceName`,资源名称,支持
|
||||||
|
|
||||||
|
```javacript
|
||||||
|
export default {
|
||||||
|
// compute
|
||||||
|
instance: instanceListUrl,
|
||||||
|
image: imageListUrl,
|
||||||
|
|
||||||
|
// storage
|
||||||
|
volume: volumeListUrl,
|
||||||
|
volumeSnapshot: volumeSnapshotListUrl,
|
||||||
|
backup: backupListUrl,
|
||||||
|
volumeType: volumeTypeListUrl,
|
||||||
|
|
||||||
|
// network
|
||||||
|
network: networkListUrl,
|
||||||
|
router: routerListUrl,
|
||||||
|
networkQosPolicy: policyListUrl,
|
||||||
|
fip: fipListUrl,
|
||||||
|
virtualAdapter: virtualAdapterListUrl,
|
||||||
|
|
||||||
|
// security
|
||||||
|
securityGroup: securityGroupListUrl,
|
||||||
|
|
||||||
|
// identity
|
||||||
|
project: projectListUrl,
|
||||||
|
user: userListUrl,
|
||||||
|
userGroup: userGroupListUrl,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- 参数`name`
|
||||||
|
- 如设置,则删除指定名称的资源
|
||||||
|
- 如不设置,则删除资源列表下的所有资源
|
||||||
|
- 参数`tab`
|
||||||
|
- 如设置,表示资源位于`tab`标签下,需要先切换到指定标签下
|
||||||
|
- 以云硬盘类型`test/e2e/integration/pages/storage/volume-type.spec.js`为例
|
||||||
|
- 删除管理QoS时准备的QoS
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
it('successfully delete related resources', () => {
|
||||||
|
cy.deleteAll('volumeType', qosName, 'QoS');
|
||||||
|
});
|
||||||
|
```
|
BIN
docs/zh/test/images/e2e/console.png
Executable file
After Width: | Height: | Size: 37 KiB |
BIN
docs/zh/test/images/e2e/detail/list.png
Executable file
After Width: | Height: | Size: 130 KiB |
BIN
docs/zh/test/images/e2e/detail/name.png
Executable file
After Width: | Height: | Size: 126 KiB |
BIN
docs/zh/test/images/e2e/detail/tab.png
Executable file
After Width: | Height: | Size: 146 KiB |
BIN
docs/zh/test/images/e2e/form/add-select.png
Executable file
After Width: | Height: | Size: 127 KiB |
BIN
docs/zh/test/images/e2e/form/attach-file-image.png
Executable file
After Width: | Height: | Size: 118 KiB |
BIN
docs/zh/test/images/e2e/form/attach-file.png
Executable file
After Width: | Height: | Size: 118 KiB |