mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat:Steps组件 (#1784)
* fix: Form horizontal水平模式下 label 显示问题 * feat:Steps组件 * feat:Steps组件 * feat:Steps组件 * feat:Steps组件 * feat:Steps组件 文档 * feat:Steps组件 文档 Co-authored-by: yupeng12 <yupeng12@baidu.com>
This commit is contained in:
parent
5c9a7dac27
commit
c6bebeb50d
249
docs/zh-CN/components/steps.md
Normal file
249
docs/zh-CN/components/steps.md
Normal file
@ -0,0 +1,249 @@
|
||||
---
|
||||
title: Steps 步骤条
|
||||
description:
|
||||
type: 0
|
||||
group: ⚙ 组件
|
||||
menuName: Steps
|
||||
icon:
|
||||
order: 68
|
||||
---
|
||||
|
||||
步骤条组件
|
||||
|
||||
## 基本用法
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"body": [
|
||||
{
|
||||
"type": "steps",
|
||||
"value": 1,
|
||||
"steps": [
|
||||
{
|
||||
"title": "First",
|
||||
"subTitle": "this is subTitle",
|
||||
"description": "this is description"
|
||||
},
|
||||
{
|
||||
"title": "Second"
|
||||
},
|
||||
{
|
||||
"title": "Last"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 设置状态
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"body": {
|
||||
"type": "steps",
|
||||
"value": 1,
|
||||
"status": "error",
|
||||
"steps": [
|
||||
{
|
||||
"title": "First"
|
||||
},
|
||||
{
|
||||
"title": "Second",
|
||||
"subTitle": "this is subTitle",
|
||||
"description": "this is description"
|
||||
},
|
||||
{
|
||||
"title": "Last"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据映射
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"step": 1,
|
||||
"status": "error"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "steps",
|
||||
"status": "${status}",
|
||||
"value": "${step}",
|
||||
"steps": [
|
||||
{
|
||||
"title": "First",
|
||||
"subTitle": "this is subTitle",
|
||||
"description": "this is description"
|
||||
},
|
||||
{
|
||||
"title": "Second"
|
||||
},
|
||||
{
|
||||
"title": "Last"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 接口映射
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"initApi": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/steps/get",
|
||||
"body": [
|
||||
{
|
||||
"type": "steps",
|
||||
"value": "${step}",
|
||||
"status": "${status}",
|
||||
"steps": [
|
||||
{
|
||||
"title": "First",
|
||||
"subTitle": "this is subTitle",
|
||||
"description": "this is description"
|
||||
},
|
||||
{
|
||||
"title": "Secord"
|
||||
},
|
||||
{
|
||||
"title": "Last"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Form 中静态展示
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"initApi": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/steps/steps",
|
||||
"controls": [
|
||||
{
|
||||
"type": "steps",
|
||||
"source": "${steps}",
|
||||
"value": "${current}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 远程拉取
|
||||
|
||||
除了可以通过数据映射获取当前数据域中的变量以外,source 还支持配置接口,格式为 API,用于动态返回选项组。
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "steps",
|
||||
"name": "steps",
|
||||
"source": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/mock2/steps/steps"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
远程拉取接口时,返回的数据结构除了需要满足 amis 接口要求的基本数据结构 以外,必须用"steps"作为选项组的 key 值,如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 0,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"steps": [
|
||||
{
|
||||
"title": "First",
|
||||
"subTitle": "this is sub title",
|
||||
"value": "first"
|
||||
},
|
||||
{
|
||||
"title": "Secord",
|
||||
"description": "this is description",
|
||||
"value": "secord"
|
||||
},
|
||||
{
|
||||
"title": "Last",
|
||||
"value": "last"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 自定义不同步骤以及状态
|
||||
|
||||
```schema
|
||||
{
|
||||
"type": "page",
|
||||
"body": {
|
||||
"type": "steps",
|
||||
"value": "b",
|
||||
"status": {
|
||||
"a": "finish",
|
||||
"b": "error",
|
||||
"c": "wait"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"title": "First",
|
||||
"value": "a"
|
||||
},
|
||||
{
|
||||
"title": "Second",
|
||||
"subTitle": "this is subTitle",
|
||||
"description": "this is description",
|
||||
"value": "b"
|
||||
},
|
||||
{
|
||||
"title": "Third",
|
||||
"value": "c"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------- | --------------------------------------------------------------------------------- | ------ | ------------------------------------------------------------- |
|
||||
| type | `string` | | `"steps"` 指定为 步骤条 渲染器 |
|
||||
| steps | Array<[step](#step)> | [] | 数组,配置步骤信息 |
|
||||
| source | [API](../../../docs/types/api) 或 [数据映射](../../../docs/concepts/data-mapping) | | 选项组源,可通过数据映射获取当前数据域变量、或者配置 API 对象 |
|
||||
| value | `string` \| `number` | `-` | 指定当前步骤,如果是`string`需要在 step 中配置 value |
|
||||
| status | [StepStatus](#StepStatus) \| {[propName: string]: stepStatus;} | `-` | 状态 |
|
||||
| className | `string` | `-` | 自定义类名 |
|
||||
|
||||
### step
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ------ | --------------------------------------- |
|
||||
| title | `string` | `-` | 标题 |
|
||||
| subTitle | `string` | `-` | 子标题 |
|
||||
| icon | `string` | | icon 名,支持 fontawesome v4 或使用 url |
|
||||
| value | `string` | | value |
|
||||
| className | `string` | `-` | 自定义类名 |
|
||||
|
||||
### StepStatus
|
||||
|
||||
`wait` | `process` | `finish` | `error`
|
@ -984,6 +984,15 @@ export const components = [
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'Steps 步骤条',
|
||||
path: '/zh-CN/components/steps',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/zh-CN/components/steps.md').then(
|
||||
makeMarkdownRenderer
|
||||
)
|
||||
},
|
||||
{
|
||||
label: 'Property 属性表',
|
||||
path: '/zh-CN/components/property',
|
||||
|
@ -52,6 +52,7 @@
|
||||
|
||||
--borderColor: #{$borderColor};
|
||||
--borderColorLight: #{lighten($borderColor, 5%)};
|
||||
--borderColorDarken: #{darken($borderColor, 10%)};
|
||||
--borderRadius: 0.142rem;
|
||||
--borderRadiusMd: 0.285rem;
|
||||
--borderRadiusLg: 0.428rem;
|
||||
@ -1334,4 +1335,15 @@
|
||||
|
||||
--AnchorNav-links-container-borderRight: #{px2rem(1px)} solid #d3dae0;
|
||||
--AnchorNav-onActive-borderWidth: 0 #{px2rem(2px)} 0 0;
|
||||
|
||||
--Steps-bg: var(--borderColorDarken);
|
||||
--Steps-status-success: var(--info);
|
||||
--Steps-status-error: var(--danger);
|
||||
--Steps-icon-fontsize: var(--fontSizeLg);
|
||||
--Steps-title-fontsize: var(--fontSizeLg);
|
||||
--Steps-title-color: var(--text--loud-color);
|
||||
--Steps-sub-title-fontsize: var(--fontSizeBase);
|
||||
--Steps-sub-title-color: var(--text-color);
|
||||
--Steps-line-bg: var(--Steps-bg);
|
||||
--Steps-line-success-bg: var(--Steps-status-success);
|
||||
}
|
||||
|
101
scss/components/_steps.scss
Normal file
101
scss/components/_steps.scss
Normal file
@ -0,0 +1,101 @@
|
||||
.#{$ns}Steps {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
.#{$ns}StepsItem {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 0 px2rem(20px);
|
||||
height: auto;
|
||||
line-height: px2rem(32px);
|
||||
&-container {
|
||||
&Icon {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
padding-right: px2rem(10px);
|
||||
.#{$ns}StepsItem-icon {
|
||||
display: inline-block;
|
||||
width: px2rem(30px);
|
||||
height: px2rem(30px);
|
||||
line-height: px2rem(28px);
|
||||
border-radius: 50%;
|
||||
font-size: var(--Steps-icon-fontsize);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
&Wrapper {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
.#{$ns}StepsItem-body {
|
||||
.#{$ns}StepsItem-title {
|
||||
font-size: var(--Steps-title-fontsize);
|
||||
color: var(--Steps-title-color);
|
||||
padding-right: px2rem(20px);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
> span {
|
||||
display: inline-block;
|
||||
}
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: px2rem(16px);
|
||||
height: 1px;
|
||||
left: 100%;
|
||||
width: 9999px;
|
||||
padding-right: 10px;
|
||||
background-color: var(--Steps-line-bg);
|
||||
}
|
||||
}
|
||||
.#{$ns}StepsItem-title.is-success {
|
||||
&:after {
|
||||
background-color: var(--Steps-line-success-bg);
|
||||
}
|
||||
}
|
||||
.#{$ns}StepsItem-subTitle {
|
||||
padding-left: px2rem(10px);
|
||||
font-size: var(--Steps-sub-title-fontsize);
|
||||
color: var(--Steps-sub-title-color);
|
||||
}
|
||||
.#{$ns}StepsItem-description {
|
||||
max-width: px2rem(140px);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
flex: none;
|
||||
.#{$ns}StepsItem-title {
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.#{$ns}StepsItem.is-wait {
|
||||
.#{$ns}StepsItem-icon {
|
||||
background-color: var(--white);
|
||||
color: var(--Steps-bg);
|
||||
border: 1px solid var(--Steps-bg);
|
||||
}
|
||||
}
|
||||
.#{$ns}StepsItem.is-error {
|
||||
.#{$ns}StepsItem-icon {
|
||||
background-color: var(--Steps-status-error);
|
||||
color: var(--white);
|
||||
border: 1px solid var(--Steps-status-error);
|
||||
}
|
||||
.#{$ns}StepsItem-title, .#{$ns}StepsItem-subTitle, .#{$ns}StepsItem-description {
|
||||
color: var(--Steps-status-error);
|
||||
}
|
||||
}
|
||||
.#{$ns}StepsItem.is-finish, .#{$ns}StepsItem.is-process {
|
||||
.#{$ns}StepsItem-icon {
|
||||
background-color: var(--Steps-status-success);
|
||||
color: var(--white);
|
||||
border: 1px solid var(--Steps-status-success);
|
||||
}
|
||||
}
|
||||
}
|
@ -64,6 +64,7 @@
|
||||
@import '../components/log';
|
||||
@import '../components/json';
|
||||
@import '../components/icon';
|
||||
@import '../components/steps';
|
||||
|
||||
@import '../components/form/fieldset';
|
||||
@import '../components/form/group';
|
||||
|
@ -54,6 +54,7 @@ import {PaginationWrapperSchema} from './renderers/PaginationWrapper';
|
||||
import {PaginationSchema} from './renderers/Pagination';
|
||||
import {AnchorNavSchema} from './renderers/AnchorNav';
|
||||
import {AvatarSchema} from './renderers/Avatar';
|
||||
import {StepsSchema} from './renderers/Steps';
|
||||
|
||||
// 每加个类型,这补充一下。
|
||||
export type SchemaType =
|
||||
@ -137,7 +138,8 @@ export type SchemaType =
|
||||
| 'video'
|
||||
| 'wizard'
|
||||
| 'wrapper'
|
||||
| 'anchor-nav';
|
||||
| 'anchor-nav'
|
||||
| 'steps';
|
||||
|
||||
export type SchemaObject =
|
||||
| PageSchema
|
||||
@ -195,7 +197,8 @@ export type SchemaObject =
|
||||
| WizardSchema
|
||||
| WrapperSchema
|
||||
| FormSchema
|
||||
| AnchorNavSchema;
|
||||
| AnchorNavSchema
|
||||
| StepsSchema;
|
||||
|
||||
export type SchemaCollection =
|
||||
| SchemaObject
|
||||
|
@ -165,6 +165,7 @@ import './renderers/Icon';
|
||||
import './renderers/Carousel';
|
||||
import './renderers/AnchorNav';
|
||||
import './renderers/Form/AnchorNav';
|
||||
import './renderers/Steps';
|
||||
import Scoped, {ScopedContext} from './Scoped';
|
||||
|
||||
import {FormItem, registerFormItem} from './renderers/Form/Item';
|
||||
|
173
src/renderers/Steps.tsx
Normal file
173
src/renderers/Steps.tsx
Normal file
@ -0,0 +1,173 @@
|
||||
import React from 'react';
|
||||
import {Renderer, RendererProps} from '../factory';
|
||||
import {BaseSchema} from '../Schema';
|
||||
import {Icon} from '../components/icons';
|
||||
import {RemoteOptionsProps, withRemoteConfig} from '../components/WithRemoteConfig';
|
||||
import {resolveVariable} from '../utils/tpl-builtin';
|
||||
|
||||
enum StepStatus {
|
||||
wait = 'wait',
|
||||
process = 'process',
|
||||
finish = 'finish',
|
||||
error = 'error'
|
||||
};
|
||||
|
||||
export type StepSchema = {
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* 子标题
|
||||
*/
|
||||
subTitle?: string;
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
|
||||
value?: string | number;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
description?: string;
|
||||
} & Omit<BaseSchema, 'type'>;
|
||||
|
||||
export interface StepsSchema extends BaseSchema {
|
||||
/**
|
||||
* 指定为 Steps 步骤条渲染器
|
||||
*/
|
||||
type: 'steps';
|
||||
|
||||
/**
|
||||
* 步骤
|
||||
*/
|
||||
steps?: Array<StepSchema>;
|
||||
|
||||
/**
|
||||
* API 或 数据映射
|
||||
*/
|
||||
source?: string;
|
||||
|
||||
/**
|
||||
* 指定当前步骤
|
||||
*/
|
||||
value?: number | string;
|
||||
|
||||
/**
|
||||
* 变量映射
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
status?: StepStatus | {
|
||||
[propName: string]: StepStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示模式
|
||||
*/
|
||||
mode?: 'horizontal' | 'vertical';
|
||||
}
|
||||
|
||||
export interface StepsProps extends RendererProps, Omit<StepsSchema, 'className'> {}
|
||||
|
||||
export function Steps(props: StepsProps) {
|
||||
const {className, classnames: cx, steps, value = 0, status, data, source, config} = props;
|
||||
const stepsRow = resolveVariable(source, data) as Array<StepSchema> || config || steps || [];
|
||||
const resolveValue = typeof value === 'string' && isNaN(+value)
|
||||
? resolveVariable(value, data) as string || +value : +value;
|
||||
const valueIndex = stepsRow.findIndex(item => item.value && item.value === resolveValue);
|
||||
const currentValue = valueIndex !== -1 ? valueIndex : resolveValue;
|
||||
const FINISH_ICON = 'check';
|
||||
const ERROR_ICON = 'close';
|
||||
|
||||
function getStepStatus(step: StepSchema, i: number): {stepStatus: StepStatus, icon?: string} {
|
||||
let stepStatus = StepStatus.wait;
|
||||
let icon = step.icon;
|
||||
|
||||
if (i < currentValue) {
|
||||
stepStatus = StepStatus.finish;
|
||||
!icon && (icon = FINISH_ICON);
|
||||
}
|
||||
else if (i === currentValue) {
|
||||
stepStatus = StepStatus.process;
|
||||
}
|
||||
|
||||
if (typeof status === 'string') {
|
||||
if (i === currentValue) {
|
||||
const resolveStatus = resolveVariable(status, data);
|
||||
stepStatus = resolveStatus || status || StepStatus.process;
|
||||
stepStatus === StepStatus.error && !icon && (icon = ERROR_ICON);
|
||||
}
|
||||
}
|
||||
else if (typeof status === 'object') {
|
||||
const key = step.value;
|
||||
key && status[key] && (stepStatus = status[key]);
|
||||
}
|
||||
|
||||
return {
|
||||
stepStatus,
|
||||
icon
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className={cx('Steps', className)}>
|
||||
{stepsRow.map((step, i) => {
|
||||
const {stepStatus, icon} = getStepStatus(step, i);
|
||||
|
||||
return (
|
||||
<li key={i} className={cx('StepsItem', `is-${stepStatus}`, step.className)}>
|
||||
<div className={cx('StepsItem-container')}>
|
||||
<div className={cx('StepsItem-containerIcon')}>
|
||||
<span className={cx('StepsItem-icon')}>
|
||||
{
|
||||
icon ? <Icon icon={icon} className="icon" /> : (i + 1)
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div className={cx('StepsItem-containerWrapper')}>
|
||||
<div className={cx('StepsItem-body')}>
|
||||
<div className={cx('StepsItem-title', i < currentValue && 'is-success')}>
|
||||
<span>{step.title}</span>
|
||||
<span className={cx('StepsItem-subTitle')}>{step.subTitle || step.value}</span>
|
||||
</div>
|
||||
<div className={cx('StepsItem-description')}>{step.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const StepsWithRemoteConfig = withRemoteConfig({
|
||||
adaptor: data => data.steps || data
|
||||
})(
|
||||
class extends React.Component<
|
||||
RemoteOptionsProps & React.ComponentProps<typeof Steps>
|
||||
> {
|
||||
render() {
|
||||
const {config, ...rest} = this.props;
|
||||
return (
|
||||
<Steps config={config} {...rest} />
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)steps$/,
|
||||
name: 'steps'
|
||||
})
|
||||
export class StepsRenderer extends React.Component<StepsProps> {
|
||||
render() {
|
||||
return <StepsWithRemoteConfig {...this.props} />;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user