Images 样式调整

This commit is contained in:
2betop 2019-12-24 14:30:31 +08:00
parent 44f966e791
commit a5befaf368
10 changed files with 256 additions and 68 deletions

View File

@ -0,0 +1,67 @@
.#{$ns}Images {
display: flex;
flex-wrap: wrap;
margin: -$gap-xs;
&-item {
display: flex;
margin: $gap-xs;
}
}
.#{$ns}Image {
display: inline-block;
border: $borderWidth solid $borderColor;
padding: $gap-xs;
&-thumb {
width: px2rem(108px);
height: px2rem(108px);
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI0MDAiPgogICAgPGRlZnM+CiAgICAgICAgPHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgICAgICAgIDxyZWN0IGZpbGw9ImJsYWNrIiB4PSIwIiB5PSIwIiB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIG9wYWNpdHk9IjAuMSIgLz4KICAgICAgICAgICAgPHJlY3QgZmlsbD0id2hpdGUiIHg9IjEwIiB5PSIwIiB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIC8+CiAgICAgICAgICAgIDxyZWN0IGZpbGw9ImJsYWNrIiB4PSIxMCIgeT0iMTAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgb3BhY2l0eT0iMC4xIiAvPgogICAgICAgICAgICA8cmVjdCBmaWxsPSJ3aGl0ZSIgeD0iMCIgeT0iMTAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgLz4KICAgICAgICA8L3BhdHRlcm4+CiAgICA8L2RlZnM+CiAgICA8cmVjdCBmaWxsPSJ1cmwoI2dyaWQpIiB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiAvPgo8L3N2Zz4=');
overflow: hidden;
position: relative;
> img {
position: absolute;
left: 50%;
top: 50%;
height: 100%;
width: auto;
transform: translate(-50%, -50%);
}
}
&-thumb--w-full > img {
width: 100%;
height: auto;
}
@supports (object-fit: contain) {
&-thumb--contain > img {
position: static;
width: 100%;
height: 100%;
transform: translate(0, 0);
top: 0;
left: 0;
object-fit: contain;
}
}
@supports (object-fit: cover) {
&-thumb--cover > img {
position: static;
width: 100%;
height: 100%;
transform: translate(0, 0);
top: 0;
left: 0;
object-fit: cover;
}
}
}
.#{$ns}ImageField {
display: inline-block;
position: relative;
}

View File

@ -70,28 +70,19 @@
&-item { &-item {
border: $borderWidth solid $borderColor; border: $borderWidth solid $borderColor;
vertical-align: top; vertical-align: top;
padding: px2rem(5px); padding: $gap-xs;
display: inline-block; display: inline-block;
margin-right: px2rem(15px); margin-right: $gap-base;
margin-bottom: px2rem(15px); margin-bottom: $gap-base;
position: relative; position: relative;
} }
&-itemImageWrap { &-image {
width: px2rem(108px); width: 100%;
height: px2rem(108px); height: 100%;
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI0MDAiPgogICAgPGRlZnM+CiAgICAgICAgPHBhdHRlcm4gaWQ9ImdyaWQiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgICAgICAgIDxyZWN0IGZpbGw9ImJsYWNrIiB4PSIwIiB5PSIwIiB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIG9wYWNpdHk9IjAuMSIgLz4KICAgICAgICAgICAgPHJlY3QgZmlsbD0id2hpdGUiIHg9IjEwIiB5PSIwIiB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIC8+CiAgICAgICAgICAgIDxyZWN0IGZpbGw9ImJsYWNrIiB4PSIxMCIgeT0iMTAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgb3BhY2l0eT0iMC4xIiAvPgogICAgICAgICAgICA8cmVjdCBmaWxsPSJ3aGl0ZSIgeD0iMCIgeT0iMTAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgLz4KICAgICAgICA8L3BhdHRlcm4+CiAgICA8L2RlZnM+CiAgICA8cmVjdCBmaWxsPSJ1cmwoI2dyaWQpIiB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiAvPgo8L3N2Zz4='); padding: 0;
overflow: hidden; border: none;
position: relative; display: block;
> img {
position: absolute;
left: 50%;
top: 50%;
height: 100%;
width: auto;
transform: translate(-50%, -50%);
}
} }
&-itemOverlay { &-itemOverlay {

View File

@ -534,6 +534,7 @@ $Card-actions-onChecked-onHover-bg: $white;
@import '../components/wrapper'; @import '../components/wrapper';
@import '../components/status'; @import '../components/status';
@import '../components/carousel'; @import '../components/carousel';
@import '../components/images';
@import '../components/form/fieldset'; @import '../components/form/fieldset';
@import '../components/form/group'; @import '../components/form/group';

View File

@ -200,6 +200,7 @@ pre {
@import '../components/wrapper'; @import '../components/wrapper';
@import '../components/status'; @import '../components/status';
@import '../components/carousel'; @import '../components/carousel';
@import '../components/images';
@import '../components/form/fieldset'; @import '../components/form/fieldset';
@import '../components/form/group'; @import '../components/form/group';

View File

@ -65,6 +65,7 @@ $Form-input-borderColor: #cfdadd;
@import '../components/wrapper'; @import '../components/wrapper';
@import '../components/status'; @import '../components/status';
@import '../components/carousel'; @import '../components/carousel';
@import '../components/images';
@import '../components/form/fieldset'; @import '../components/form/fieldset';
@import '../components/form/group'; @import '../components/form/group';

View File

@ -136,6 +136,7 @@ import './renderers/Grid';
import './renderers/HBox'; import './renderers/HBox';
import './renderers/VBox'; import './renderers/VBox';
import './renderers/Image'; import './renderers/Image';
import './renderers/Images';
import './renderers/List'; import './renderers/List';
import './renderers/Operation'; import './renderers/Operation';
import './renderers/Page'; import './renderers/Page';

View File

@ -14,6 +14,7 @@ import Button from '../../components/Button';
// @ts-ignore // @ts-ignore
import accepts from 'attr-accept'; import accepts from 'attr-accept';
import {getNameFromUrl} from './File'; import {getNameFromUrl} from './File';
import ImageComponent from '../Image';
let preventEvent = (e: any) => e.stopPropagation(); let preventEvent = (e: any) => e.stopPropagation();
@ -990,19 +991,17 @@ export default class ImageControl extends React.Component<
</> </>
) : ( ) : (
<> <>
<div <ImageComponent
key="image" key="image"
className={cx('ImageControl-itemImageWrap')} className={cx('ImageControl-image')}
> onLoad={this.handleImageLoaded.bind(
<img this,
onLoad={this.handleImageLoaded.bind( key
this, )}
key src={file.preview || file.url}
)} alt={file.name}
src={file.preview || file.url} thumbMode="contain"
alt={file.name} />
/>
</div>
<div <div
key="overlay" key="overlay"

View File

@ -1,24 +1,78 @@
import React from 'react'; import React from 'react';
import {Renderer, RendererProps} from '../factory'; import {Renderer, RendererProps} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service';
import {Api, SchemaNode} from '../types';
import {filter} from '../utils/tpl'; import {filter} from '../utils/tpl';
import cx from 'classnames'; import {ClassNamesFn, themeable} from '../theme';
import moment from 'moment';
export interface ImageProps extends RendererProps { export interface ImageProps {
src: string;
title?: string;
alt?: string;
className?: string;
imageClassName?: string;
description?: string;
thumbMode?: 'w-full' | 'h-full' | 'contain' | 'cover';
classnames: ClassNamesFn;
classPrefix: string;
onLoad?: React.EventHandler<any>;
}
export class Image extends React.Component<ImageProps> {
render() {
const {
classnames: cx,
className,
imageClassName,
thumbMode,
src,
alt,
title,
description,
onLoad
} = this.props;
return (
<div className={cx('Image', className)}>
<div
className={cx(
'Image-thumb',
thumbMode ? `Image-thumb--${thumbMode}` : ''
)}
>
<img
onLoad={onLoad}
className={cx(imageClassName)}
src={src}
alt={alt}
/>
</div>
{title || description ? (
<div key="caption" className={cx('Image-caption')}>
{title ? <div className={cx('Image-title')}>{title}</div> : null}
{description ? (
<div className={cx('Image-description')}>{description}</div>
) : null}
</div>
) : null}
</div>
);
}
}
const ThemedImage = themeable(Image);
export default ThemedImage;
export interface ImageFieldProps extends RendererProps {
className?: string; className?: string;
imageClassName?: string; imageClassName?: string;
placeholder?: string; placeholder?: string;
description?: string; description?: string;
thumbMode: 'w-full' | 'h-full' | 'contain' | 'cover';
} }
export class ImageField extends React.Component<ImageProps, object> { export class ImageField extends React.Component<ImageFieldProps, object> {
static defaultProps: Partial<ImageProps> = { static defaultProps: Pick<ImageFieldProps, 'defaultImage' | 'thumbMode'> = {
className: 'thumb-lg',
imageClassName: 'r',
defaultImage: defaultImage:
'https://fex.bdstatic.com/n/static/amis/renderers/crud/field/placeholder_cfad9b1.png' 'https://fex.bdstatic.com/n/static/amis/renderers/crud/field/placeholder_cfad9b1.png',
thumbMode: 'contain'
}; };
render() { render() {
@ -27,11 +81,11 @@ export class ImageField extends React.Component<ImageProps, object> {
defaultImage, defaultImage,
description, description,
title, title,
render,
data, data,
imageClassName, imageClassName,
classnames: cx, classnames: cx,
src src,
thumbMode
} = this.props; } = this.props;
const finnalSrc = src ? filter(src, data, '| raw') : ''; const finnalSrc = src ? filter(src, data, '| raw') : '';
@ -39,18 +93,13 @@ export class ImageField extends React.Component<ImageProps, object> {
return ( return (
<div className={cx('ImageField', className)}> <div className={cx('ImageField', className)}>
<img <ThemedImage
className={imageClassName} imageClassName={imageClassName}
src={finnalSrc || value || defaultImage} src={finnalSrc || value || defaultImage}
title={filter(title, data)}
description={filter(description, data)}
thumbMode={thumbMode}
/> />
{title || description ? (
<div key="caption" className={cx('ImageField-caption')}>
{title ? (
<div className="text-md">{filter(title, data)}</div>
) : null}
{render('description', description as string)}
</div>
) : null}
</div> </div>
); );
} }
@ -61,18 +110,3 @@ export class ImageField extends React.Component<ImageProps, object> {
name: 'image' name: 'image'
}) })
export class ImageFieldRenderer extends ImageField {} export class ImageFieldRenderer extends ImageField {}
@Renderer({
test: /(^|\/)images$/
})
export class ImagesFieldRenderer extends ImageField {
static defaultProps: Partial<ImageProps> = {
...ImageField.defaultProps,
multiple: true,
delimiter: ','
};
render() {
return <p>Todo</p>;
}
}

93
src/renderers/Images.tsx Normal file
View File

@ -0,0 +1,93 @@
import React from 'react';
import {Renderer, RendererProps} from '../factory';
import {filter} from '../utils/tpl';
import {resolveVariable, isPureVariable} from '../utils/tpl-builtin';
import Image from './Image';
export interface ImagesProps extends RendererProps {
className: string;
defaultImage: string;
placeholder: string;
delimiter: string;
thumbMode: 'w-full' | 'h-full' | 'contain' | 'cover';
name?: string;
value?: any;
source?: string;
}
export class ImagesField extends React.Component<ImagesProps> {
static defaultProps: Pick<
ImagesProps,
'className' | 'delimiter' | 'defaultImage' | 'placehoder' | 'thumbMode'
> = {
className: '',
delimiter: ',',
defaultImage:
'https://fex.bdstatic.com/n/static/amis/renderers/crud/field/placeholder_cfad9b1.png',
placehoder: '-',
thumbMode: 'contain'
};
render() {
const {
className,
defaultImage,
thumbMode,
data,
name,
value,
placeholder,
classnames: cx,
source,
delimiter
} = this.props;
let list: any;
if (typeof source === 'string' && isPureVariable(source)) {
list = resolveVariable(source, data) || undefined;
} else if (Array.isArray(value)) {
list = value;
} else if (name && data[name]) {
list = data[name];
}
if (typeof list === 'string') {
list = list.split(delimiter);
} else if (list && !Array.isArray(list)) {
list = [list];
}
return (
<div className={cx('ImagesField', className)}>
{Array.isArray(list) ? (
<div className={cx('Images')}>
{list.map((item: any, index: number) => (
<Image
className={cx('Images-item')}
key={index}
src={(item && item.image) || item}
title={item && item.title}
description={item && item.description}
thumbMode={thumbMode}
/>
))}
</div>
) : defaultImage ? (
<Image
className={cx('Images-item')}
src={defaultImage}
thumbMode={thumbMode}
/>
) : (
placeholder
)}
</div>
);
}
}
@Renderer({
test: /(^|\/)images$/
})
export class ImagesFieldRenderer extends ImagesField {}

View File

@ -12,7 +12,7 @@ export function reigsterTplEnginer(name: string, enginer: Enginer) {
} }
export function filter( export function filter(
tpl: string, tpl?: string,
data: object = {}, data: object = {},
...rest: Array<any> ...rest: Array<any>
): string { ): string {