集成图片集查看功能

This commit is contained in:
2betop 2020-01-07 19:42:50 +08:00
parent f23f5c554d
commit 42b9910df0
16 changed files with 635 additions and 80 deletions

View File

@ -30,10 +30,13 @@ export default {
type: 'image',
label: '图片',
name: 'image',
popOver: {
title: '查看大图',
body: '<div class="w-xxl"><img class="w-full" src="${image}"/></div>'
}
enlargeAble: true,
title: '233',
thumbMode: 'cover'
// popOver: {
// title: '',
// body: '<div class="w-xxl"><img class="w-full" src="${image}"/></div>'
// }
},
{
name: 'date',

View File

@ -4,7 +4,39 @@ export default {
data: {
id: 1,
image:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg'
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3893101144,2877209892&fm=23&gp=0.jpg',
images: [
{
image:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80',
src:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg'
},
{
image:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80',
src:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg'
},
{
image:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80',
src:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg'
},
{
image:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80',
src:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg'
},
{
image:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg@s_0,w_216,l_1,f_jpg,q_80',
src:
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg'
}
]
},
body: [
{
@ -112,6 +144,16 @@ export default {
originalSrc: '${image}'
},
{
type: 'static-images',
label: '图片集',
name: 'images',
thumbMode: 'cover',
thumbRatio: '4:3',
enlargeAble: true,
originalSrc: '${src}' //
},
{
type: 'divider'
},

View File

@ -0,0 +1,204 @@
@keyframes disappear {
to {
opacity: 0;
}
}
@keyframes appear {
from {
opacity: 0;
}
}
.#{$ns}ImageGallery {
display: flex;
flex-direction: column;
background: transparent;
border: none;
border-radius: 0;
max-width: 1010px !important;
&-close {
position: absolute;
right: 0;
top: 0;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
&:hover {
color: #fff;
}
> svg {
width: px2rem(16px);
height: px2rem(16px);
}
}
&-title {
height: px2rem(30px);
vertical-align: top;
line-height: px2rem(30px);
font-size: px2rem(12px);
color: $white;
text-align: center;
}
&-main {
background: #000;
flex-basis: 0;
flex-grow: 1;
position: relative;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
> img {
display: block;
max-width: 100%;
max-height: 100%;
}
}
&-prevBtn,
&-nextBtn {
> svg {
width: px2rem(48px);
height: px2rem(48px);
}
position: absolute;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #999;
text-shadow: rgba(0, 0, 0, 0.3) 0px 0px 4px;
&:hover {
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 0px 0px 4px;
}
animation-name: disappear;
animation-delay: 3s;
animation-duration: 0.35s;
animation-fill-mode: both;
&.is-disabled {
pointer-events: none;
}
}
&-main:hover &-prevBtn,
&-main:hover &-nextBtn {
animation-name: appear;
animation-delay: 0s;
animation-duration: 0.35s;
}
&-prevBtn {
left: px2rem(20px);
}
&-nextBtn {
right: px2rem(20px);
}
&-footer {
height: px2rem(74px);
background: #222;
display: flex;
flex-direction: row;
user-select: none;
}
&-prevList,
&-nextList {
width: px2rem(20px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background: rgba(0, 0, 0, 0.3);
color: #fff;
&.is-disabled {
background: rgba(0, 0, 0, 0.3);
color: rgba(255, 255, 255, 0.1);
pointer-events: none;
}
&:hover {
background: rgba(0, 0, 0, 1);
color: #fff;
}
}
&-itemsWrap {
flex-grow: 1;
flex-basis: 0;
width: 0;
overflow: hidden;
align-items: center;
justify-content: center;
display: flex;
}
&-items {
display: inline-block;
white-space: nowrap;
}
&-item {
margin: 10px 5px;
width: 54px;
height: 54px;
display: inline-flex;
position: relative;
border: 1px solid #666;
justify-content: center;
align-items: center;
cursor: pointer;
> img {
display: block;
max-width: 100%;
max-height: 100%;
}
@supports (object-fit: cover) {
> img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
&:after {
position: absolute;
content: '';
display: block;
background: rgba(0, 0, 0, 0.5);
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
&:hover {
border: 1px solid #e5e5e5;
&:after {
display: none;
}
}
&.is-active {
border: 1px solid #108cee;
&:after {
display: none;
}
}
}
}

View File

@ -219,14 +219,22 @@
color: $danger;
}
.#{$ns}Modal--full .#{$ns}Modal-content {
width: calc(100% - 60px);
height: calc(100% - 60px);
max-width: unset;
margin: px2rem(30px);
.#{$ns}Modal--full {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
> .#{$ns}Modal-body {
height: 0;
overflow: auto;
> .#{$ns}Modal-content {
flex-basis: 0;
flex-grow: 1;
margin: 30px;
width: calc(100% - 60px);
max-width: unset;
> .#{$ns}Modal-body {
height: 0;
overflow: auto;
}
}
}

View File

@ -230,7 +230,7 @@
}
&[data-position='left']:hover:after {
margin: 0 0 0 $Tooltip--attr-gap;
margin: 0 $Tooltip--attr-gap 0 0;
}
&[data-position='top']:after {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,177 @@
import React from 'react';
import {themeable, ClassNamesFn} from '../theme';
import {autobind} from '../utils/helper';
import Modal from './Modal';
import {Icon} from './icons';
export interface ImageGalleryProps {
classnames: ClassNamesFn;
classPrefix: string;
children: React.ReactNode;
}
export interface ImageGalleryState {
isOpened: boolean;
index: number;
items: Array<{
src: string;
originalSrc: string;
title?: string;
caption?: string;
}>;
}
export class ImageGallery extends React.Component<
ImageGalleryProps,
ImageGalleryState
> {
state: ImageGalleryState = {
isOpened: false,
index: -1,
items: []
};
@autobind
handleImageEnlarge(info: {
src: string;
originalSrc: string;
list?: Array<{
src: string;
originalSrc: string;
title?: string;
caption?: string;
}>;
title?: string;
caption?: string;
index?: number;
}) {
this.setState({
isOpened: true,
items: info.list ? info.list : [info],
index: info.index || 0
});
}
@autobind
close() {
this.setState({
isOpened: false
});
}
@autobind
prev() {
const index = this.state.index;
this.setState({
index: index - 1
});
}
@autobind
next() {
const index = this.state.index;
this.setState({
index: index + 1
});
}
@autobind
handleItemClick(e: React.MouseEvent<HTMLDivElement>) {
const index = parseInt(e.currentTarget.getAttribute('data-index')!, 10);
this.setState({
index
});
}
render() {
const {children, classnames: cx} = this.props;
const {index, items} = this.state;
return (
<>
{React.cloneElement(children as any, {
onImageEnlarge: this.handleImageEnlarge
})}
<Modal
closeOnEsc
size="full"
onHide={this.close}
show={this.state.isOpened}
contentClassName={cx('ImageGallery')}
>
<a
data-tooltip="关闭"
data-position="left"
className={cx('ImageGallery-close')}
onClick={this.close}
>
<Icon icon="close" />
</a>
{~index && items[index] ? (
<>
<div className={cx('ImageGallery-title')}>
{items[index].title}
</div>
<div className={cx('ImageGallery-main')}>
<img src={items[index].originalSrc} />
{items.length > 1 ? (
<>
<a
className={cx(
'ImageGallery-prevBtn',
index <= 0 ? 'is-disabled' : ''
)}
onClick={this.prev}
>
<Icon icon="prev" />
</a>
<a
className={cx(
'ImageGallery-nextBtn',
index >= items.length - 1 ? 'is-disabled' : ''
)}
onClick={this.next}
>
<Icon icon="next" />
</a>
</>
) : null}
</div>
</>
) : null}
{items.length > 1 ? (
<div className={cx('ImageGallery-footer')}>
<a className={cx('ImageGallery-prevList is-disabled')}>
<Icon icon="prev" />
</a>
<div className={cx('ImageGallery-itemsWrap')}>
<div className={cx('ImageGallery-items')}>
{items.map((item, i) => (
<div
key={i}
data-index={i}
onClick={this.handleItemClick}
className={cx(
'ImageGallery-item',
i === index ? 'is-active' : ''
)}
>
<img src={item.src} />
</div>
))}
</div>
</div>
<a className={cx('ImageGallery-nextList is-disabled')}>
<Icon icon="next" />
</a>
</div>
) : null}
</Modal>
</>
);
}
}
export default themeable(ImageGallery);

View File

@ -17,6 +17,7 @@ import {ClassNamesFn, themeable} from '../theme';
export interface ModalProps {
className?: string;
contentClassName?: string;
size?: any;
overlay?: boolean;
onHide: () => void;
@ -83,6 +84,7 @@ export class Modal extends React.Component<ModalProps, ModalState> {
render() {
const {
className,
contentClassName,
children,
container,
show,
@ -92,6 +94,7 @@ export class Modal extends React.Component<ModalProps, ModalState> {
} = this.props;
return (
// @ts-ignore
<Portal container={container}>
<Transition
mountOnEnter
@ -116,7 +119,13 @@ export class Modal extends React.Component<ModalProps, ModalState> {
{overlay ? (
<div className={cx(`${ns}Modal-overlay`, fadeStyles[status])} />
) : null}
<div className={cx(`${ns}Modal-content`, fadeStyles[status])}>
<div
className={cx(
`${ns}Modal-content`,
contentClassName,
fadeStyles[status]
)}
>
{children}
</div>
</div>

View File

@ -104,6 +104,8 @@ registerIcon('play', PlayIcon);
registerIcon('pause', PauseIcon);
registerIcon('left-arrow', LeftArrowIcon);
registerIcon('right-arrow', RightArrowIcon);
registerIcon('prev', LeftArrowIcon);
registerIcon('next', RightArrowIcon);
registerIcon('check', CheckIcon);
registerIcon('plus', PlusIcon);
registerIcon('minus', MinusIcon);

View File

@ -38,6 +38,7 @@ import {getTheme, ThemeInstance, ClassNamesFn, ThemeContext} from './theme';
import find = require('lodash/find');
import Alert from './components/Alert2';
import {LazyComponent} from './components';
import ImageGallery from './components/ImageGallery';
export interface TestFunc {
(
@ -383,26 +384,28 @@ export class RootRenderer extends React.Component<RootRendererProps> {
return (
<RootStoreContext.Provider value={rootStore}>
<ThemeContext.Provider value={this.props.theme || 'default'}>
{
renderChild(
pathPrefix || '',
isPlainObject(schema)
? {
type: 'page',
...(schema as Schema)
}
: schema,
{
...rest,
resolveDefinitions: this.resolveDefinitions,
location: location,
data: finalData,
env,
classnames: theme.classnames,
classPrefix: theme.classPrefix
}
) as JSX.Element
}
<ImageGallery>
{
renderChild(
pathPrefix || '',
isPlainObject(schema)
? {
type: 'page',
...(schema as Schema)
}
: schema,
{
...rest,
resolveDefinitions: this.resolveDefinitions,
location: location,
data: finalData,
env,
classnames: theme.classnames,
classPrefix: theme.classPrefix
}
) as JSX.Element
}
</ImageGallery>
</ThemeContext.Provider>
</RootStoreContext.Provider>
);

View File

@ -403,6 +403,7 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
{showCloseButton !== false && !store.loading ? (
<a
data-tooltip="关闭弹窗"
data-position="left"
onClick={this.handleSelfClose}
className={cx('Modal-close')}
>

View File

@ -9,17 +9,11 @@ export interface ImageThumbProps {
src: string;
originalSrc?: string; // 原图
enlargeAble?: boolean;
onEnlarge?: (info: {
src: string;
originalSrc: string;
title?: string;
caption?: string;
thumbMode?: 'w-full' | 'h-full' | 'contain' | 'cover';
thumbRatio?: '1:1' | '4:3' | '16:9';
}) => void;
onEnlarge?: (info: ImageThumbProps) => void;
showDimensions?: boolean;
title?: string;
alt?: string;
index?: number;
className?: string;
imageClassName?: string;
caption?: string;
@ -33,26 +27,8 @@ export interface ImageThumbProps {
export class ImageThumb extends React.Component<ImageThumbProps> {
@autobind
handleEnlarge() {
const {
onEnlarge,
src,
originalSrc,
title,
caption,
thumbMode,
thumbRatio
} = this.props;
onEnlarge &&
originalSrc &&
onEnlarge({
src,
originalSrc,
title,
caption,
thumbMode,
thumbRatio
});
const {onEnlarge, ...rest} = this.props;
onEnlarge && onEnlarge(rest);
}
render() {
@ -123,14 +99,17 @@ export interface ImageFieldProps extends RendererProps {
thumbRatio: '1:1' | '4:3' | '16:9';
originalSrc?: string; // 原图
enlargeAble?: boolean;
onEnlarge?: (info: {
src: string;
originalSrc: string;
title?: string;
caption?: string;
thumbMode?: 'w-full' | 'h-full' | 'contain' | 'cover';
thumbRatio?: '1:1' | '4:3' | '16:9';
}) => void;
onImageEnlarge?: (
info: {
src: string;
originalSrc: string;
title?: string;
caption?: string;
thumbMode?: 'w-full' | 'h-full' | 'contain' | 'cover';
thumbRatio?: '1:1' | '4:3' | '16:9';
},
target: any
) => void;
showDimensions?: boolean;
}
@ -146,6 +125,31 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
placeholder: '-'
};
@autobind
handleEnlarge({
src,
originalSrc,
title,
caption,
thumbMode,
thumbRatio
}: ImageThumbProps) {
const {onImageEnlarge} = this.props;
onImageEnlarge &&
onImageEnlarge(
{
src,
originalSrc: originalSrc || src,
title,
caption,
thumbMode,
thumbRatio
},
this.props
);
}
render() {
const {
className,
@ -178,9 +182,9 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
caption={filter(imageCaption, data)}
thumbMode={thumbMode}
thumbRatio={thumbRatio}
originalSrc={originalSrc}
originalSrc={filter(originalSrc, data, '| raw')}
enlargeAble={enlargeAble}
onEnlarge={onEnlarge}
onEnlarge={this.handleEnlarge}
showDimensions={showDimensions}
/>
) : (

View File

@ -2,7 +2,8 @@ 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';
import Image, {ImageThumbProps} from './Image';
import {autobind} from '../utils/helper';
export interface ImagesProps extends RendererProps {
className: string;
@ -10,11 +11,22 @@ export interface ImagesProps extends RendererProps {
placeholder: string;
delimiter: string;
thumbMode: 'w-full' | 'h-full' | 'contain' | 'cover';
thumbRatio: '1-1' | '4-3' | '16-9';
thumbRatio: '1:1' | '4:3' | '16:9';
name?: string;
value?: any;
source?: string;
src?: string;
originalSrc?: string; // 原图
enlargeAble?: boolean;
onEnlarge?: (
info: ImageThumbProps & {
list?: Array<
Pick<ImageThumbProps, 'src' | 'originalSrc' | 'title' | 'caption'>
>;
}
) => void;
showDimensions?: boolean;
}
export class ImagesField extends React.Component<ImagesProps> {
@ -33,9 +45,35 @@ export class ImagesField extends React.Component<ImagesProps> {
'https://fex.bdstatic.com/n/static/amis/renderers/crud/field/placeholder_cfad9b1.png',
placehoder: '-',
thumbMode: 'contain',
thumbRatio: '1-1'
thumbRatio: '1:1'
};
list: Array<any> = [];
@autobind
handleEnlarge(info: ImageThumbProps) {
const {onImageEnlarge, src, originalSrc} = this.props;
onImageEnlarge &&
onImageEnlarge(
{
...info,
originalSrc: info.originalSrc || info.src,
list: this.list.map(item => ({
src: src
? filter(src, item, '| raw')
: (item && item.image) || item,
originalSrc: originalSrc
? filter(originalSrc, item, '| raw')
: item && item.src,
title: item && item.title,
caption: item && (item.description || item.caption)
}))
},
this.props
);
}
render() {
const {
className,
@ -48,7 +86,10 @@ export class ImagesField extends React.Component<ImagesProps> {
placeholder,
classnames: cx,
source,
delimiter
delimiter,
enlargeAble,
src,
originalSrc
} = this.props;
let list: any;
@ -67,19 +108,33 @@ export class ImagesField extends React.Component<ImagesProps> {
list = [list];
}
this.list = list;
return (
<div className={cx('ImagesField', className)}>
{Array.isArray(list) ? (
<div className={cx('Images')}>
{list.map((item: any, index: number) => (
<Image
index={index}
className={cx('Images-item')}
key={index}
src={(item && item.image) || item}
src={
src
? filter(src, item, '| raw')
: (item && item.image) || item
}
originalSrc={
originalSrc
? filter(originalSrc, item, '| raw')
: item && item.src
}
title={item && item.title}
description={item && item.description}
caption={item && (item.description || item.caption)}
thumbMode={thumbMode}
thumbRatio={thumbRatio}
enlargeAble={enlargeAble!}
onEnlarge={this.handleEnlarge}
/>
))}
</div>

View File

@ -89,6 +89,7 @@ export interface TableProps extends RendererProps {
) => void;
onSaveOrder?: (moved: Array<object>, items: Array<object>) => void;
onQuery: (values: object) => void;
onImageEnlarge?: (data: any, target: any) => void;
buildItemProps?: (item: any, index: number) => any;
checkOnItemClick?: boolean;
hideCheckToggler?: boolean;
@ -860,6 +861,48 @@ export default class Table extends React.Component<TableProps, object> {
);
}
@autobind
handleImageEnlarge(info: any, target: {rowIndex: number; colIndex: number}) {
const onImageEnlarge = this.props.onImageEnlarge;
// 如果已经是多张了,直接跳过
if (Array.isArray(info.list)) {
return onImageEnlarge && onImageEnlarge(info, target);
}
// 从列表中收集所有图片,然后作为一个图片集合派送出去。
const store = this.props.store;
const column = store.filteredColumns[target.colIndex].pristine;
const list: Array<any> = [];
store.rows.forEach(row => {
const src = resolveVariable(column.name, row.data);
list.push({
src,
originalSrc: column.originalSrc
? filter(column.originalSrc, row.data)
: src,
title: column.title ? filter(column.title, row.data) : undefined,
caption: column.caption ? filter(column.caption, row.data) : undefined
});
});
if (list.length > 1) {
onImageEnlarge &&
onImageEnlarge(
{
...info,
list,
index: target.rowIndex
},
target
);
} else {
onImageEnlarge && onImageEnlarge(info, target);
}
}
renderHeading() {
let {
title,
@ -1186,7 +1229,8 @@ export default class Table extends React.Component<TableProps, object> {
popOverContainer: this.getPopOverContainer,
rowSpan: item.rowSpans[column.name as string],
quickEditFormRef: this.subFormRef,
prefix
prefix,
onImageEnlarge: this.handleImageEnlarge
};
delete subProps.label;