mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: 美化crud2移动端的卡片样式,支持查询表单折叠
This commit is contained in:
parent
46a2dd0ad4
commit
c7039f7376
@ -878,4 +878,8 @@ $Table-strip-bg: transparent;
|
||||
:root {
|
||||
--fontSizeBase: var(--fontSizeLg);
|
||||
}
|
||||
:root,
|
||||
.AMISCSSWrapper {
|
||||
--Page-body-padding: var(--gap-md);
|
||||
}
|
||||
}
|
||||
|
@ -30,21 +30,21 @@
|
||||
font-weight: var(--Pick-base-value-fontWeight);
|
||||
background: var(--Pick-base-value-bgColor);
|
||||
border-width: var(--Pick-base-value-top-border-width)
|
||||
var(--Pick-base-value-right-border-width)
|
||||
var(--Pick-base-value-bottom-border-width)
|
||||
var(--Pick-base-value-left-border-width);
|
||||
var(--Pick-base-value-right-border-width)
|
||||
var(--Pick-base-value-bottom-border-width)
|
||||
var(--Pick-base-value-left-border-width);
|
||||
border-style: var(--Pick-base-value-top-border-style)
|
||||
var(--Pick-base-value-right-border-style)
|
||||
var(--Pick-base-value-bottom-border-style)
|
||||
var(--Pick-base-value-left-border-style);
|
||||
var(--Pick-base-value-right-border-style)
|
||||
var(--Pick-base-value-bottom-border-style)
|
||||
var(--Pick-base-value-left-border-style);
|
||||
border-color: var(--Pick-base-value-top-border-color)
|
||||
var(--Pick-base-value-right-border-color)
|
||||
var(--Pick-base-value-bottom-border-color)
|
||||
var(--Pick-base-value-left-border-color);
|
||||
var(--Pick-base-value-right-border-color)
|
||||
var(--Pick-base-value-bottom-border-color)
|
||||
var(--Pick-base-value-left-border-color);
|
||||
border-radius: var(--Pick-base-top-left-border-radius)
|
||||
var(--Pick-base-top-right-border-radius)
|
||||
var(--Pick-base-bottom-right-border-radius)
|
||||
var(--Pick-base-bottom-left-border-radius);
|
||||
var(--Pick-base-top-right-border-radius)
|
||||
var(--Pick-base-bottom-right-border-radius)
|
||||
var(--Pick-base-bottom-left-border-radius);
|
||||
margin-right: var(--gap-xs);
|
||||
margin-top: var(--gap-xs);
|
||||
|
||||
@ -133,6 +133,111 @@
|
||||
&-filter {
|
||||
margin-bottom: var(--gap-base);
|
||||
}
|
||||
|
||||
&.is-mobile-cards {
|
||||
// 移动端卡片模式不需要列选择器
|
||||
.#{$ns}ColumnToggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.#{$ns}SearchBox {
|
||||
border-radius: var(--Form-input-borderRadius);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.#{$ns}Card {
|
||||
--Card-borderRadius: var(--sizes-size-5);
|
||||
--gap-base: var(--sizes-size-9);
|
||||
--fontSizeBase: var(--fonts-size-7);
|
||||
--body-lineHeight: var(--sizes-base-11);
|
||||
--Card-actions-borderColor: #f2f2f4;
|
||||
--Card-actions-fontSize: var(--fontSizeBase);
|
||||
font-size: var(--fontSizeBase);
|
||||
border: 0;
|
||||
box-shadow: var(--boxShadowSm);
|
||||
&-field {
|
||||
margin-bottom: var(--sizes-size-3);
|
||||
}
|
||||
&-fieldLabel {
|
||||
color: var(--colors-neutral-text-5);
|
||||
font-size: var(--fontSizeBase);
|
||||
flex-basis: var(--sizes-base-28);
|
||||
line-height: var(--body-lineHeight);
|
||||
margin-right: var(--sizes-size-6);
|
||||
}
|
||||
&-fieldValue {
|
||||
color: var(--colors-neutral-text-2);
|
||||
}
|
||||
|
||||
&-actions {
|
||||
&-wrapper {
|
||||
padding-left: var(--gap-md);
|
||||
padding-right: var(--gap-md);
|
||||
}
|
||||
& > a {
|
||||
height: var(--sizes-base-12);
|
||||
line-height: var(--sizes-base-12);
|
||||
margin-top: var(--sizes-size-4);
|
||||
margin-bottom: var(--sizes-size-4);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Form-item {
|
||||
.#{$ns}Form-value,
|
||||
.#{$ns}Form-control {
|
||||
font-size: var(--fontSizeBase);
|
||||
}
|
||||
}
|
||||
|
||||
&-multiMedia {
|
||||
&--right {
|
||||
align-items: flex-start;
|
||||
}
|
||||
&-img {
|
||||
width: var(--sizes-base-45);
|
||||
height: var(--sizes-base-45);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Image {
|
||||
border: 0;
|
||||
|
||||
&--thumb {
|
||||
padding-left: 0;
|
||||
img {
|
||||
border-radius: var(--sizes-size-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Panel {
|
||||
--Panel-bodyPadding: var(--gap-md);
|
||||
--Panel-headingPadding: var(--gap-sm) var(--gap-md);
|
||||
--Panel-body-paddingTop: var(--gap-md);
|
||||
--Panel-body-paddingBottom: var(--gap-md);
|
||||
--Panel-body-paddingLeft: var(--gap-md);
|
||||
--Panel-body-paddingRight: var(--gap-md);
|
||||
&--form {
|
||||
margin: 0;
|
||||
margin-bottom: var(--gap-md);
|
||||
border-radius: var(--sizes-size-5);
|
||||
}
|
||||
&-body {
|
||||
padding-top: 0;
|
||||
}
|
||||
.#{$ns}Form {
|
||||
&--column {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
&-item {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
|
@ -256,6 +256,7 @@
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
height: px2rem(44px);
|
||||
font-size: var(--fontSizeMd);
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
|
@ -145,6 +145,18 @@
|
||||
var(--Panel-heading-bottom-border-width)
|
||||
var(--Panel-heading-left-border-width);
|
||||
border-radius: var(--Panel-headingBorderRadius);
|
||||
|
||||
&.is-collapsible {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
transition: transform 0.1s ease-in;
|
||||
&.is-collapsed {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
@ -219,8 +231,10 @@
|
||||
border-radius: 0;
|
||||
|
||||
.#{$ns}Panel-title {
|
||||
padding-left: var(--Panel-body-paddingLeft);
|
||||
border-left: px2rem(3px) solid var(--primary);
|
||||
.icon {
|
||||
width: var(--sizes-base-7);
|
||||
height: var(--sizes-base-7);
|
||||
}
|
||||
font-size: var(--fontSizeLg);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
text-align: center;
|
||||
color: #999;
|
||||
margin-bottom: 12px;
|
||||
font-size: var(--fontSizeMd);
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
|
@ -655,7 +655,6 @@
|
||||
overflow-wrap: break-word;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: var(--fontSizeLg);
|
||||
|
||||
& + .#{$ns}Form-item-controlBox {
|
||||
max-width: calc(100% - 28%);
|
||||
@ -672,10 +671,8 @@
|
||||
}
|
||||
|
||||
.#{$ns}TextControl-input {
|
||||
font-size: var(--fontSizeLg);
|
||||
|
||||
input {
|
||||
height: calc(var(--Form-input-lineHeight) * var(--fontSizeLg));
|
||||
height: calc(var(--Form-input-lineHeight) * var(--Form-item-fontSize));
|
||||
}
|
||||
}
|
||||
|
||||
@ -744,7 +741,6 @@
|
||||
.#{$ns}Form-control {
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
font-size: var(--fontSizeLg);
|
||||
min-width: 0;
|
||||
|
||||
.#{$ns}ColorPicker {
|
||||
@ -865,6 +861,7 @@
|
||||
|
||||
.#{$ns}TextareaControl > textarea,
|
||||
.#{$ns}Form-control > .#{$ns}TextControl-input,
|
||||
.#{$ns}Number-input,
|
||||
.#{$ns}TextControl.is-focused > .#{$ns}TextControl-input {
|
||||
border: none;
|
||||
padding: 0 var(--Form-input-paddingX) 0 0;
|
||||
|
@ -17,6 +17,7 @@ export interface CardProps extends ThemeProps {
|
||||
footerClassName?: string;
|
||||
media?: React.ReactNode;
|
||||
mediaPosition?: 'top' | 'left' | 'right' | 'bottom';
|
||||
mediaActionPosition?: 'outside';
|
||||
toolbar?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
actions?: React.ReactNode;
|
||||
@ -80,6 +81,7 @@ export class Card extends React.Component<CardProps> {
|
||||
footerClassName,
|
||||
media,
|
||||
mediaPosition,
|
||||
mediaActionPosition,
|
||||
actions,
|
||||
children,
|
||||
onClick,
|
||||
@ -150,6 +152,20 @@ export class Card extends React.Component<CardProps> {
|
||||
|
||||
const body = children;
|
||||
|
||||
const actionView =
|
||||
secondary || actions ? (
|
||||
<div className={cx('Card-footer-wrapper', footerClassName)}>
|
||||
{secondary ? (
|
||||
<div className={cx('Card-secondary', secondaryClassName)}>
|
||||
{secondary}
|
||||
</div>
|
||||
) : null}
|
||||
{actions ? (
|
||||
<div className={cx('Card-actions-wrapper')}>{actions}</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={this.handleClick}
|
||||
@ -159,45 +175,26 @@ export class Card extends React.Component<CardProps> {
|
||||
style={style}
|
||||
>
|
||||
{media ? (
|
||||
<div className={cx(`Card-multiMedia--${mediaPosition}`)}>
|
||||
{media}
|
||||
<div className={cx('Card-multiMedia-flex')}>
|
||||
{heading}
|
||||
{body ? (
|
||||
<div className={cx('Card-body', bodyClassName)}>{body}</div>
|
||||
) : null}
|
||||
{secondary || actions ? (
|
||||
<div className={cx('Card-footer-wrapper', footerClassName)}>
|
||||
{secondary ? (
|
||||
<div className={cx('Card-secondary', secondaryClassName)}>
|
||||
{secondary}
|
||||
</div>
|
||||
) : null}
|
||||
{actions ? (
|
||||
<div className={cx('Card-actions-wrapper')}>{actions}</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<>
|
||||
<div className={cx(`Card-multiMedia--${mediaPosition}`)}>
|
||||
{media}
|
||||
<div className={cx('Card-multiMedia-flex')}>
|
||||
{heading}
|
||||
{body ? (
|
||||
<div className={cx('Card-body', bodyClassName)}>{body}</div>
|
||||
) : null}
|
||||
{!mediaActionPosition ? actionView : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{mediaActionPosition === 'outside' ? actionView : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{heading}
|
||||
{body ? (
|
||||
<div className={cx('Card-body', bodyClassName)}>{body}</div>
|
||||
) : null}
|
||||
{secondary || actions ? (
|
||||
<div className={cx('Card-footer-wrapper', footerClassName)}>
|
||||
{secondary ? (
|
||||
<div className={cx('Card-secondary', secondaryClassName)}>
|
||||
{secondary}
|
||||
</div>
|
||||
) : null}
|
||||
{actions ? (
|
||||
<div className={cx('Card-actions-wrapper')}>{actions}</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{actionView}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1276,7 +1276,26 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
replaceQuery: true,
|
||||
resetPage: true
|
||||
});
|
||||
}
|
||||
},
|
||||
// 移动端的查询表单支持折叠
|
||||
...(this.props.mobileUI
|
||||
? {
|
||||
columnCount: 1,
|
||||
mode: 'normal',
|
||||
collapsible: true,
|
||||
title: {
|
||||
type: 'container',
|
||||
body: [
|
||||
{
|
||||
type: 'icon',
|
||||
icon: 'column-filter',
|
||||
className: 'icon mr-2'
|
||||
},
|
||||
(item as any).title || ''
|
||||
]
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -1335,14 +1354,20 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
}
|
||||
|
||||
transformTable2cards() {
|
||||
const {store, columns, card} = this.props;
|
||||
const {store, columns: propsColumns, card, mobileMode} = this.props;
|
||||
const body: any[] = [];
|
||||
const fieldCount = mobileMode.fieldCount || 4;
|
||||
const actions: any[] = [];
|
||||
let cover: string = '';
|
||||
|
||||
((store.columns ?? columns) || []).forEach((item: any) => {
|
||||
const columns = (store.columns ?? propsColumns) || [];
|
||||
for (let index = 0; index < columns.length; index++) {
|
||||
const item = columns[index];
|
||||
if (!isPlainObject(item)) {
|
||||
return;
|
||||
} else if (item.type === 'operation') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.type === 'operation') {
|
||||
actions.push(...(item?.buttons || []));
|
||||
} else if (item.type === 'button' && item.name === 'operation') {
|
||||
actions.push(item);
|
||||
@ -1350,9 +1375,20 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
if (!item.label && item.title) {
|
||||
item.label = item.title;
|
||||
}
|
||||
body.push(item);
|
||||
|
||||
if (item.type === 'static-image' && !cover) {
|
||||
cover = `\${${item.name}}`;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (body.length < fieldCount) {
|
||||
if (item.type === 'static-image' && item.title) {
|
||||
delete item.title;
|
||||
}
|
||||
body.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!body.length) {
|
||||
return null;
|
||||
@ -1364,7 +1400,18 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
card: {
|
||||
...card,
|
||||
body,
|
||||
actions
|
||||
actions,
|
||||
...(cover
|
||||
? {
|
||||
media: {
|
||||
type: 'image',
|
||||
url: cover,
|
||||
position: 'right',
|
||||
className: ''
|
||||
},
|
||||
mediaActionPosition: 'outside'
|
||||
}
|
||||
: {})
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -1420,13 +1467,20 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
|
||||
let mobileModeProps: any = {};
|
||||
if (mobileMode && mobileUI && mode.includes('table')) {
|
||||
const cardsSchema = this.transformTable2cards();
|
||||
if (typeof mobileMode === 'string' && mobileMode === 'cards') {
|
||||
const cardsSchema = this.transformTable2cards();
|
||||
if (cardsSchema) {
|
||||
mobileModeProps = cardsSchema;
|
||||
}
|
||||
} else if (typeof mobileMode === 'object') {
|
||||
mobileModeProps = {...mobileMode};
|
||||
mobileModeProps = {
|
||||
...cardsSchema,
|
||||
...mobileMode,
|
||||
card: {
|
||||
...cardsSchema?.card,
|
||||
...mobileMode.card
|
||||
}
|
||||
};
|
||||
}
|
||||
// 移动端模式,默认开启上拉刷新
|
||||
if (mobileModeProps && !_pullRefresh?.disabled) {
|
||||
@ -1500,7 +1554,10 @@ export default class CRUD2 extends React.Component<CRUD2Props, any> {
|
||||
return (
|
||||
<div
|
||||
className={cx('Crud2', className, {
|
||||
'is-loading': store.loading
|
||||
'is-loading': store.loading,
|
||||
'is-mobile': mobileUI,
|
||||
'is-mobile-cards':
|
||||
mobileMode === 'cards' || mobileModeProps.type === 'cards'
|
||||
})}
|
||||
style={style}
|
||||
data-id={id}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
import {ActionSchema} from './Action';
|
||||
import {FormHorizontal} from 'amis-core';
|
||||
import omit from 'lodash/omit';
|
||||
import {Icon} from 'amis-ui';
|
||||
|
||||
/**
|
||||
* Panel渲染器。
|
||||
@ -80,6 +81,11 @@ export interface PanelSchema extends BaseSchema {
|
||||
*/
|
||||
affixFooter?: boolean | 'always';
|
||||
|
||||
/**\
|
||||
* 可折叠。先简单实现一下
|
||||
*/
|
||||
collapsible?: boolean;
|
||||
|
||||
/**
|
||||
* 配置子表单项默认的展示方式。
|
||||
*/
|
||||
@ -123,6 +129,10 @@ export default class Panel extends React.Component<PanelProps> {
|
||||
// bodyClassName: 'Panel-body'
|
||||
};
|
||||
|
||||
state = {
|
||||
collapsed: false
|
||||
};
|
||||
|
||||
renderBody(): JSX.Element | null {
|
||||
const {
|
||||
type,
|
||||
@ -205,6 +215,7 @@ export default class Panel extends React.Component<PanelProps> {
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
id,
|
||||
collapsible,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
@ -214,33 +225,37 @@ export default class Panel extends React.Component<PanelProps> {
|
||||
};
|
||||
|
||||
const footerDoms = [];
|
||||
const actions = this.renderActions();
|
||||
actions &&
|
||||
footerDoms.push(
|
||||
<div
|
||||
key="actions"
|
||||
className={cx(
|
||||
`Panel-btnToolbar`,
|
||||
actionsClassName || `Panel-footer`,
|
||||
actionsControlClassName
|
||||
)}
|
||||
>
|
||||
{actions}
|
||||
</div>
|
||||
);
|
||||
const collapsed = this.state.collapsed;
|
||||
|
||||
footer &&
|
||||
footerDoms.push(
|
||||
<div
|
||||
key="footer"
|
||||
className={cx(
|
||||
footerClassName || `Panel-footer`,
|
||||
actionsControlClassName
|
||||
)}
|
||||
>
|
||||
{render('footer', footer, subProps)}
|
||||
</div>
|
||||
);
|
||||
if (!collapsed) {
|
||||
const actions = this.renderActions();
|
||||
actions &&
|
||||
footerDoms.push(
|
||||
<div
|
||||
key="actions"
|
||||
className={cx(
|
||||
`Panel-btnToolbar`,
|
||||
actionsClassName || `Panel-footer`,
|
||||
actionsControlClassName
|
||||
)}
|
||||
>
|
||||
{actions}
|
||||
</div>
|
||||
);
|
||||
|
||||
footer &&
|
||||
footerDoms.push(
|
||||
<div
|
||||
key="footer"
|
||||
className={cx(
|
||||
footerClassName || `Panel-footer`,
|
||||
actionsControlClassName
|
||||
)}
|
||||
>
|
||||
{render('footer', footer, subProps)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let footerDom = footerDoms.length ? (
|
||||
<div
|
||||
@ -273,20 +288,42 @@ export default class Panel extends React.Component<PanelProps> {
|
||||
<div
|
||||
className={cx(
|
||||
headerClassName || `Panel-heading`,
|
||||
headerControlClassName
|
||||
headerControlClassName,
|
||||
{
|
||||
'is-collapsible': collapsible
|
||||
}
|
||||
)}
|
||||
>
|
||||
<h3 className={cx(`Panel-title`, headerTitleControlClassName)}>
|
||||
{render('title', title, subProps)}
|
||||
</h3>
|
||||
{collapsible ? (
|
||||
<span
|
||||
className={cx('Panel-arrow-wrap')}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
collapsed: !collapsed
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon="down-arrow-bold"
|
||||
className={cx('Panel-arrow', 'icon', {
|
||||
'is-collapsed': collapsed
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cx(bodyClassName || `Panel-body`, bodyControlClassName)}
|
||||
>
|
||||
{this.renderBody()}
|
||||
</div>
|
||||
{!collapsed ? (
|
||||
<div
|
||||
className={cx(bodyClassName || `Panel-body`, bodyControlClassName)}
|
||||
>
|
||||
{this.renderBody()}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{footerDom}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user