# PhotoView 图片预览

- 来源：[E10 UI 组件库](https://www.e-cology.com.cn/ui/photo-view/freepass)
- 平台：PC
- 维护者：屈正巍
- 设计稿：http://pd.e-cology.com.cn/pd/E10/interface/e10xiaoguotu/PC/E10zjys/#s50
- Issue：http://gitlab.ecology10.cn/FRONTEND/weapp-ui/-/issues?state=all&search=[PhotoView]

## 代码演示

### 直接使用

采用 showImage 函数，自定义预览方式

```jsx
import { PureComponent } from 'react';
import { PhotoView, Button, constants, PhotoViewData } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/1.jpg',
    // src: '/api/file/preview?type=redirect&imgFormat=image&fileId=1149132338570665986',
    originSrc: '/build/ui/demo/assets/photo-view/7.png',
    // originSrc: '/api/file/preview?type=redirect&imgFormat=image&fileId=1149132338570665986',
    intro: '这是两只小肥羊',
    user: '屈正巍',
    time: '2021-10-22 18:00:00',
    imgSize: 1024,
  },
  {
    id: '2',
    src: '/build/ui/demo/assets/photo-view/2.jpeg',
    originSrc: '/build/ui/demo/assets/photo-view/8.jpg',
  },
  {
    id: '3',
    src: '/build/ui/demo/assets/photo-view/3.jpeg',
  },
  {
    id: '4',
    src: '/build/ui/demo/assets/photo-view/4.jpeg',
  },
  {
    id: '5',
    src: "/api/file/preview?fileId=1143900054845579267&type=redirect",
    fileId: '1143900054845579267',
    intro: '测试fileId2',
    user: '屈正巍2',
    time: '2023-03-14 18:00:00'
  },
  {
    id: '6',
    src: "/api/file/preview?fileId=1143922096542244868&type=redirect",
    fileId: '1143922096542244868',
    intro: '测试fileId2',
    user: '屈正巍2',
    time: '2023-03-14 18:00:00'
  },
  {
    id: '7',
    src: "",
  },
]

export default class PhotoViewDemo extends PureComponent<{}> {

  showImage0 = () => { PhotoView.showImage(images) }
  showImage1 = () => { PhotoView.showImage(images, 1) }
  showImage2 = () => { PhotoView.showImage(images, 2) }
  showImage3 = () => { PhotoView.showImage(images, 3) }
  showImage4 = () => { PhotoView.showImage(images, 4) }
  showImage5 = () => { PhotoView.showImage(images, 5) }
  showImage6 = () => { PhotoView.showImage(images, 6) }
  // showImage7 = () => { PhotoView.showImage(images, 7) }

  render() {
    return (
      <div className={prefixCls}>
        <Button onClick={this.showImage0}>预览第1张</Button>
        <Button onClick={this.showImage1}>预览第2张</Button>
        <Button onClick={this.showImage2}>预览第3张</Button>
        <Button onClick={this.showImage3}>预览第4张</Button>
        <Button onClick={this.showImage4}>预览第5张</Button>
        <Button onClick={this.showImage5}>预览第6张</Button>
        <Button onClick={this.showImage6}>预览第7张</Button>
        {/* <Button onClick={this.showImage7}>预览第8张</Button> */}
      </div>
    )
  }
}
```

### 引入组件

采用 PhotoView 组件，自定义控制visible

```jsx
import { PureComponent } from 'react';
import { PhotoView, constants, PhotoViewData } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/5.jpeg',
    intro: <div style={{ border: '1px solid red', height: "100%", color: 'red' }}>图片描述</div>,
    user: <span style={{ border: '1px solid red', color: 'red' }}>作者</span>,
    time: <span style={{ border: '1px solid red', color: 'red' }}>时间</span>,
    imgSize: 2048,
  },
  {
    id: '2',
    src: '/build/ui/demo/assets/photo-view/transparent.png',
  },
  {
    id: '3',
    src: '/build/ui/demo/assets/photo-view/3.jpeg',
    imgSize: 10240,
    intro: '这是一只狐狸',
    user: '屈正巍',
    time: '2021-10-22 18:00:00'
  },
  {
    id: '4',
    src: '/build/ui/demo/assets/photo-view/4.jpeg',
  }
]

const Img = (props: any) => {
  const { index, onClick } = props
  function preview() {
    onClick(index)
  }
  return <img style={{ maxHeight: 100 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0
  }

  preview = (index: number) => {
    this.setState({ index }, () => {
      this.setState({ visible: true })
    })
  }

  onClose = () => {
    this.setState({ visible: false })
  }

  render() {
    return (
      <div className={prefixCls}>
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.index}
          data={images}
          onClose={this.onClose} />
      </div>
    )
  }
}
```

### 查看原图

可以通过传入originSrc参数，点击原图按钮切换原图

```jsx
import { PureComponent } from 'react';
import { PhotoView, Input, Button, constants, PhotoViewData } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const Img = (props: any) => {
  const { index, onClick } = props;
  function preview() {
    onClick(index);
  }
  return <img style={{ maxHeight: 100 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0,
    src: '/build/ui/demo/assets/photo-view/1.jpg',
    // src: '/api/file/preview?type=redirect&imgFormat=image&fileId=1152830915191767044',
    originSrc: '/build/ui/demo/assets/photo-view/9.jpg',
    // originSrc: '/api/file/preview?type=redirect&imgFormat=image&fileId=1149132338570665986',
    src2: '/build/ui/demo/assets/photo-view/2.jpeg',
    originSrc2: '/build/ui/demo/assets/photo-view/8.jpg'
  }
  preview = (index: number) => { this.setState({ index }, () => { this.setState({ visible: true }) }) }
  onClose = () => this.setState({ visible: false })
  onSrcChange = (value: any) => this.setState({ src: value })
  onOriginSrcChange = (value: any) => this.setState({ originSrc: value })
  onSrc2Change = (value: any) => this.setState({ src2: value })
  onOriginSrc2Change = (value: any) => this.setState({ originSrc2: value })
  getImages = () => {
    const { src, originSrc, src2, originSrc2 } = this.state
    const image: PhotoViewData = {
      id: '1',
      src: src,
      imgSize: 1024,
      intro: '这是两只小肥羊',
      user: '屈正巍',
      time: '2021-10-22 18:00:00'
    }
    if (originSrc) image.originSrc = originSrc
    const image2: PhotoViewData = {
      id: '2',
      src: src2,
      imgSize: 2048,
      intro: '这是两只小肥羊吗，不是吧',
      user: '屈正巍',
      time: '2021-10-22 18:00:00'
    }
    if (originSrc2) image2.originSrc = originSrc2
    return [image, image2];
  }
  showImage1 = () => { PhotoView.showImage(this.getImages()) }
  showImage2 = () => { PhotoView.showImage(this.getImages(), 1) }
  render() {
    const { src, originSrc, src2, originSrc2 } = this.state
    const images = this.getImages()
    return (
      <div className={prefixCls}>
        <div style={{ display: 'flex', alignItems: 'center', minHeight: 40 }}>
          <div style={{ marginRight: 5 }}>图片src地址：</div>
          <div style={{ flex: 1 }}>
            <Input
              placeholder="请输入图片地址"
              value={src}
              onChange={this.onSrcChange}
            />
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', minHeight: 40 }}>
          <div style={{ marginRight: 5 }}>原图originSrc地址：</div>
          <div style={{ flex: 1 }}>
            <Input
              placeholder="请输入原图地址"
              value={originSrc}
              onChange={this.onOriginSrcChange}
            />
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', minHeight: 40 }}>
          <div style={{ marginRight: 5 }}>图片2src地址：</div>
          <div style={{ flex: 1 }}>
            <Input
              placeholder="请输入图片2地址"
              value={src2}
              onChange={this.onSrc2Change}
            />
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', minHeight: 40 }}>
          <div style={{ marginRight: 5 }}>原图2originSrc地址：</div>
          <div style={{ flex: 1 }}>
            <Input
              placeholder="请输入原图2地址"
              value={originSrc2}
              onChange={this.onOriginSrc2Change}
            />
          </div>
        </div>
        <h4>通过showImage查看原图</h4>
        <Button onClick={this.showImage1}>点击查看原图</Button>
        <Button onClick={this.showImage2}>点击查看原图2</Button>
        <br />
        <h4>通过PhotoView组件查看原图</h4>
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.index}
          data={images}
          onClose={this.onClose}
        />
      </div>
    )
  }
}
```

### 加载失败

可自定义渲染加载失败展示

```jsx
import { PureComponent } from 'react';
import { PhotoView, constants, PhotoViewData, Button } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '',
    hideDownload: true,
    hideRotate: true
  },
]

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible1: false,
    visible2: false,
    index1: 0,
    index2: 0
  }

  preview1 = () => {
    this.setState({ index1: 0 }, () => {
      this.setState({ visible1: true })
    })
  }
  onClose1 = () => {
    this.setState({ visible1: false })
  }
  preview2 = () => {
    this.setState({ index2: 0 }, () => {
      this.setState({ visible2: true })
    })
  }
  onClose2 = () => {
    this.setState({ visible2: false })
  }
  renderBroken = () => (<div style={{ width: 150, height: 150, background: 'pink' }}>渲染失败</div>)

  render() {
    const { visible1, visible2, index1, index2 } = this.state
    return (
      <div className={prefixCls}>
        <Button onClick={this.preview1}>默认渲染失败</Button>
        <Button onClick={this.preview2}>自定义渲染失败</Button>
        <PhotoView
         
          visible={visible1}
          photoIndex={index1}
          data={images}
          onClose={this.onClose1} />
        <PhotoView
         
          visible={visible2}
          photoIndex={index2}
          data={images}
          brokenElement={this.renderBroken}
          onClose={this.onClose2} />
      </div>
    )
  }
}
```

### 自定义下载

走自定义下载方法，用于其他组件记录下载日志

```jsx
import { PureComponent } from 'react';
import { PhotoView, constants, PhotoViewData, Dialog } from '@weapp/ui';

const { message } = Dialog

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/6.png',
  },
]

const Img = (props: any) => {
  const { index, onClick } = props;
  function preview() {
    onClick(index);
  }
  return <img style={{ maxHeight: 100 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0
  }

  preview = (index: number) => {
    this.setState({ index }, () => {
      this.setState({ visible: true })
    })
  }

  onClose = () => {
    this.setState({ visible: false })
  }

  customDownload = (data: PhotoViewData) => {
    console.info('data', data)
    message({
      type: 'success',
      content: '自定义下载function'
    })
  }

  render() {
    return (
      <div className={prefixCls}>
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.index}
          data={images}
          onClose={this.onClose}
          customDownload={this.customDownload}
        />
      </div>
    )
  }
}
```

### 自定义按钮

自定义图片顶部工具栏操作按钮，可以隐藏下载、旋转、放大缩小等功能

```jsx
import { PureComponent } from 'react';
import { PhotoView, constants, PhotoViewData } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/8.jpg',
    hideShrink: true,
    hideRotate: true,
    hideAntiRotate: true,
    hideDownload: true,
  }
]

const Img = (props: any) => {
  const { index, onClick } = props;
  function preview() {
    onClick(index);
  }
  return <img style={{ maxHeight: 100 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0
  }

  preview = (index: number) => {
    this.setState({ index }, () => {
      this.setState({ visible: true })
    })
  }

  onClose = () => {
    this.setState({ visible: false })
  }

  render() {
    return (
      <div className={prefixCls}>
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.index}
          data={images}
          onClose={this.onClose} />
      </div>
    )
  }
}
```

### 通过upload预览

可以通过只传入filedId的方式，在upload组件内部预览

```jsx
import React, { PureComponent } from 'react';
import { Button, constants, Upload, Input } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const { filePreview } = Upload

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    fileId: ''
  }

  onInputChange = (value: React.ReactText) => this.setState({ fileId: String(value) })

  filePreview = () => {
    const { fileId } = this.state
    const prevData = Object.assign({},
      {
        fileId: fileId,
        fileIds: [fileId],
        isImage: true
      })
    filePreview(prevData)
  }

  render() {
    const { fileId } = this.state
    return (
      <div className={prefixCls}>
        输入预览的filedId：
        <Input style={{ width: 200 }} value={fileId} onChange={this.onInputChange} />
        <Button onClick={this.filePreview} type={'primary'}>通过upload进行预览</Button>
      </div>
    )
  }
}
```

### 浏览器全屏

可以通过showFullButton来控制展示浏览器全屏

```jsx
import { PureComponent } from 'react';
import { PhotoView, constants, PhotoViewData } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/12.png',
    imgSize: 1024,
    intro: '这是两只小肥羊',
    user: '屈正巍',
    time: '2021-10-22 18:00:00'
  },
]

const Img = (props: any) => {
  const { index, onClick } = props;
  function preview() {
    onClick(index);
  }
  return <img style={{ maxHeight: 100 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0
  }
  preview = (index: number) => {
    this.setState({ index }, () => {
      this.setState({ visible: true })
    })
  }
  onClose = () => this.setState({ visible: false })
  render() {
    return (
      <div className={prefixCls}>
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.index}
          data={images}
          onClose={this.onClose}
          showFullButton={true}
          showShrinkButton={false}
        />
      </div>
    )
  }
}
```

### 自定义配置全屏展示比例

可以通过fullScreenScale控制全屏的展示比例

```jsx
import { PureComponent, ReactText } from 'react';
import { PhotoView, constants, PhotoViewData, Input, Button } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const Img = (props: any) => {
  const { index, onClick } = props;
  function preview() {
    onClick(index)
  }
  return <img style={{ maxHeight: 100, maxWidth: 200, margin: 10 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0,
    fullScreenScale: 60,
    photoViewCanDrag: true,
    customFileId: '0'
  }

  preview = (index: number) => {
    this.setState({ index }, () => {
      this.setState({ visible: true })
    })
  }

  onClose = () => { this.setState({ visible: false }) }
  onInputChange = (value: ReactText) => this.setState({ fullScreenScale: Number(value) })
  onFileIdChange = (value: ReactText) => this.setState({ customFileId: String(value) })
  changeCanDrag = () => this.setState({ photoViewCanDrag: !this.state.photoViewCanDrag })

  getImages = () => {
    const { customFileId } = this.state
    const images: PhotoViewData[] = [
      {
        id: '1',
        src: '/build/ui/demo/assets/photo-view/1.jpg',
        originSrc: '/build/ui/demo/assets/photo-view/3.jpeg',
        imgSize: 1024,
        intro: '这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊这是两只小肥羊',
        user: '屈正巍',
        time: '2021-10-22 18:00:00'
      },
      {
        id: '2',
        src: '/build/ui/demo/assets/photo-view/2.jpeg',
        imgSize: 10240,
        intro: '这是好多云',
        user: '屈正巍',
        time: '2021-10-22 18:00:00'
      },
      {
        id: '3',
        src: '/build/ui/demo/assets/photo-view/4.jpeg',
        intro: '这是一座山',
        user: '屈正巍',
        time: '2021-10-22 18:00:00'
      },
      {
        id: '4',
        src: '/build/ui/demo/assets/photo-view/10.jpg',
        intro: '长图',
        user: '屈正巍',
        time: '2023-08-23 18:00:00'
      },
      {
        id: '5',
        src: '/build/ui/demo/assets/photo-view/11.jpg',
        intro: '长图',
        user: '屈正巍',
        time: '2023-08-23 18:00:00'
      },
      {
        id: '5',
        src: `/api/file/preview?fileId=${customFileId}&type=redirect`,
        fileId: customFileId,
        intro: '测试fileId',
        user: '屈正巍',
        time: '2023-02-27 18:00:00'
      },
    ]
    return images
  }

  render() {
    const { fullScreenScale, customFileId, photoViewCanDrag } = this.state
    const images = this.getImages()
    return (
      <div className={prefixCls}>
        <div>
          <span>请输入需要全屏的比例：</span>
          <Input onChange={this.onInputChange} value={String(fullScreenScale)} style={{ width: 200 }} />
        </div>
        <div>
          <span>请输入系统内自定义图片预览filedId（demo默认走"/api/file/preview?fileId={`fileId`}&type=redirect"接口）：</span>
          <Input onChange={this.onFileIdChange} value={customFileId} style={{ width: 200 }} />
        </div>
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <span>当开启可拖拽时，因需控制dialog的拖拽定位，所以在自适应窗口大小时由于绝对定位原因，会出现预览窗口偏移的问题：</span>
          <Button type={photoViewCanDrag ? 'success' : 'danger'} onClick={this.changeCanDrag}>点击切换图片可拖拽：{photoViewCanDrag ? '' : '不'}可拖拽</Button>
        </div>
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.index}
          data={images}
          onClose={this.onClose}
          fullScreenScale={fullScreenScale}
          photoViewCanDrag={photoViewCanDrag}
        />
      </div>
    )
  }
}
```

### 比例改变自动居中

可以通过scaleChangeToCenter来控制大小改变时的图片位置

```jsx
import { PureComponent } from 'react';
import { PhotoView, constants, PhotoViewData } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/2.jpeg',
  },
]

const Img = (props: any) => {
  const { index, onClick } = props;
  function preview() {
    onClick(index);
  }
  return <img style={{ maxHeight: 100 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0
  }

  preview = (index: number) => {
    this.setState({ index }, () => {
      this.setState({ visible: true })
    })
  }

  onClose = () => {
    this.setState({ visible: false })
  }

  render() {
    return (
      <div className={prefixCls}>
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.index}
          data={images}
          onClose={this.onClose}
          scaleChangeToCenter
        />
      </div>
    )
  }
}
```

### 单页预览模式

可以通过控制展示单页预览模式

```jsx
import { CSSProperties, PureComponent } from 'react';
import { PhotoView, constants, PhotoViewData } from '@weapp/ui';
const rtl = window.weappUtils?.needRTL?.()
const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/5.jpeg',
    intro: '这是一只小狐狸',
    user: '屈正巍',
    time: '2023-09-04 18:00:00',
    imgSize: 2048,
  },
  {
    id: '2',
    src: '/build/ui/demo/assets/photo-view/2.jpeg',
  },
  {
    id: '3',
    src: '/build/ui/demo/assets/photo-view/3.jpeg',
  },
  {
    id: '4',
    src: '/build/ui/demo/assets/photo-view/4.jpeg',
  },
  {
    id: '5',
    src: "/api/file/preview?fileId=1143900054845579267&type=redirect",
    fileId: '1143900054845579267',
    intro: '测试fileId2',
    user: '屈正巍2',
    time: '2023-03-14 18:00:00'
  },
  {
    id: '6',
    src: "/api/file/preview?fileId=1143922096542244868&type=redirect",
    fileId: '1143922096542244868',
    intro: '测试fileId2',
    user: '屈正巍2',
    time: '2023-03-14 18:00:00'
  },
  {
    id: '7',
    src: "",
  },
]

export default class PhotoViewDemo extends PureComponent<{}> {

  render() {
    const style: CSSProperties = { width: 800, height: 500, position: 'relative', border: '1px solid black' }
    if (rtl) {
      style.transform = 'scaleX(-1)'
    }
    return (
      <div className={prefixCls}>
        <p>注意：在使用previewMode单页模式时，外层容器请加上 position: 'relative' 的样式进行定位</p>
        <div style={style}>
          <PhotoView
            visible={false}
            data={images}
            showFullButton
            previewMode
          />
        </div>
      </div>
    )
  }
}
```

### 视频-图片综合预览

可以在一个预览组件中同时查看视频和图片

```jsx
import { PureComponent, RefAttributes, FC, useCallback } from 'react';
import { PhotoView, Button, Icon, constants, PhotoViewData, Dialog } from '@weapp/ui';

const { message } = Dialog

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '0',
    src: 'https://www.w3schools.com/html/mov_bbb.mp4',
    isVideo: true,
    intro: '测试测试视频',
    user: '屈正巍',
    time: '2021-10-22 18:00:00',
    imgSize: 1024,
    hideInfo: false,
  },
  {
    id: '1',
    src: '',
    isVideo: true
  },
  {
    id: '2',
    src: `/papi/file/preview?fileId=3083188269356208671&type=video`,
    isVideo: true
  },
  {
    id: '3',
    src: '/build/ui/demo/assets/photo-view/1.jpg',
    originSrc: '/build/ui/demo/assets/photo-view/1.jpg',
    intro: '这是两只小肥羊',
    user: '屈正巍',
    time: '2021-10-22 18:00:00',
    imgSize: 1024,
    hideInfo: false,
  },
  {
    id: '4',
    src: '',
    isVideo: true
  },
  {
    id: '5',
    src: ``,
    isVideo: true
  },
  {
    id: '6',
    src: '/build/ui/demo/assets/photo-view/1.jpg',
    originSrc: '/build/ui/demo/assets/photo-view/1.jpg',
    intro: '这是两只小肥羊',
    user: '屈正巍',
    time: '2021-10-22 18:00:00',
    imgSize: 1024,
  },
  {
    id: '7',
    src: `https://cloudstore.e-cology.cn/page/resource/userfile/video/tuzi.mp4`,
    isVideo: true
  },
]

const ShareButton: FC<any & RefAttributes<any>> = (props: any) => {
  const { params, image } = props
  const onButtonClick = useCallback(() => {
    console.log('props', props)
    message({
      type: "info",
      content: (
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          <span>文件详情</span>
          <span>{`当前为第${params?.index + 1}张`}</span>
          <span>{`当前地址为${image?.src}`}</span>
        </div>
      )
    })
  }, [image?.src, params?.index, props])
  return <Icon className={`PhotoView-toolBarBtn`} name={'Icon-share02'} onClick={onButtonClick} size={'lg'} title={'转发'} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  customRenderShareButton = (_: any, content: any) => {
    return (
      <ShareButton
       
        {..._}
      />
    )
  }

  showImage0 = () => {
    PhotoView.showImage(images, 0, {
      showShareButton: true,
      customRenderShareButton: this.customRenderShareButton
    })
  }
  showImage1 = () => {
    PhotoView.showImage(images, 1, {
      showShareButton: true,
      customRenderShareButton: this.customRenderShareButton
    })
  }
  showImage2 = () => {
    PhotoView.showImage(images, 2, {
      showShareButton: true,
      customRenderShareButton: this.customRenderShareButton
    })
  }
  showImage3 = () => {
    PhotoView.showImage(images, 3, {
      showShareButton: true,
      customRenderShareButton: this.customRenderShareButton
    })
  }

  render() {
    return (
      <div className={prefixCls}>
        <Button onClick={this.showImage0}>预览第1张</Button>
        <Button onClick={this.showImage1}>预览第2张</Button>
        <Button onClick={this.showImage2}>预览第3张</Button>
        <Button onClick={this.showImage3}>预览第4张</Button>
      </div>
    )
  }
}
```

### 手动输入视频地址

```jsx
import { PureComponent, RefAttributes, FC, useCallback } from 'react';
import { PhotoView, Button, Icon, Input, constants, PhotoViewData, Dialog } from '@weapp/ui';
import { getLabel } from '@weapp/utils'

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const { message } = Dialog

const ShareButton: FC<any & RefAttributes<any>> = (props: any) => {
  const { params, image } = props
  const onButtonClick = useCallback(() => {
    console.log('props', props)
    message({
      type: "info",
      content: (
        <div style={{ display: 'flex', flexDirection: 'column' }}>
          <span>文件详情</span>
          <span>{`当前为第${params?.index + 1}张`}</span>
          <span>{`当前地址为${image?.src}`}</span>
        </div>
      )
    })
  }, [image?.src, params?.index, props])
  return <Icon className={`PhotoView-toolBarBtn`} name={'Icon-share02'} onClick={onButtonClick} size={'lg'} title={getLabel('57111', '转发')} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    src: 'https://www.w3schools.com/html/mov_bbb.mp4',
  }

  customRenderShareButton = (_: any, content: any) => {
    return (
      <ShareButton
       
        {..._}
      />
    )
  }

  showVideo = () => {
    const { src } = this.state
    const images: PhotoViewData[] = [
      {
        id: '1',
        src: src,
        isVideo: true,
      },
    ]
    PhotoView.showImage(images, 0, {
      showShareButton: true,
      customRenderShareButton: this.customRenderShareButton,
    })
  }

  onInputChange = (value: any) => this.setState({ src: value })

  render() {
    const { src } = this.state
    return (
      <div className={prefixCls}>
        <div style={{ display: 'flex' }}>
          <span style={{ width: 120, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>输入视频链接</span>
          <Input value={src} onChange={this.onInputChange} />
        </div>
        <Button onClick={this.showVideo}>预览视频</Button>
      </div>
    )
  }
}
```

### 使用视频组件预览

```jsx
import { Component } from 'react'
import { Video, Button, constants } from '@weapp/ui'

const prefixCls = `${constants.uiPcClsPrefix}-demo-video`;

class VideoDemo extends Component<any, any> {

  state = {
    baseSrc: 'https://www.w3schools.com/html/mov_bbb.mp4',
  }

  showVideo = () => {
    const { baseSrc } = this.state
    Video.showVideo(baseSrc, {
      useVideoJs: true,
      downloadSrc: baseSrc,
      // ...otherParams
    })
  }

  customRenderTrigger = (_this: any, content: any) => (
    <Button
     
      type={'primary'}
    >
      预览弹层视频
    </Button>
  )

  render() {
    const { baseSrc } = this.state
    return (
      <div className={prefixCls}>
        <h3>注意：</h3>
        <h3>1.为兼容历史数据，videojs组件在没有指定宽高时会默认按照视频原始宽高展示</h3>
        <h3>2.如需根据容器的宽高来动态适配，请在videoJsOptions中配置流式布局fluid：true</h3>
        <h4>直接使用</h4>
        <Video
         
          useVideoJs
          src={baseSrc}
        />
        <h4>弹层预览模式</h4>
        <Video
         
          isDialog
          useVideoJs
          customRender={this.customRenderTrigger}
          src={baseSrc}
        />
        <h4>调用Video.showVideo</h4>
        <Button
         
          onClick={this.showVideo}
          type={'success'}
        >
          showVideo预览视频
        </Button>
      </div>
    );
  }
}

export default VideoDemo;
```

### tiff预览测试

```jsx
import { PureComponent, CSSProperties } from 'react';
import { PhotoView, constants, PhotoViewData, Input, Checkbox } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/1.jpg',
    originSrc: '/build/ui/demo/assets/photo-view/7.png',
  },
  {
    id: '2',
    src: '/build/ui/demo/assets/photo-view/transparent.png',
  },
  {
    id: '3',
    src: "/api/file/preview?type=redirect&fileId=1243680288294092801&cusMenuId=2225822942057721075&urlPageTitle=5YWo6YOo55%2Bl6K%2BG&docId=1243680133714755584&editLinkType=editPage&customParam=%7B%22fromPCDoc%22%3A1%7D&authModule=document&version=1",
    useTiff: true, // 这张图启用 tiff 预览（会自动检测是否为 tiff，并进行特殊处理）
  },
  {
    id: '4',
    src: '/build/ui/demo/assets/photo-view/4.jpeg',
  },
  {
    id: '5',
    src: '/build/ui/demo/assets/photo-view/5.jpeg',
  },
]

const Img = (props: any) => {
  const { index, onClick } = props
  function preview() {
    onClick(index)
  }
  return <img style={{ maxHeight: 100 }} alt={props.src} src={props.src} onClick={preview} />
}

// 定义ValueType类型，匹配Input组件的onChange参数类型
type ValueType = string | number | boolean | undefined | null;

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    originalIndex: 0, // 原始图片数组的索引（用于UI）
    validIndex: 0,    // 有效图片数组的索引（用于PhotoView）
    images: [...images]
  }

  // 获取过滤后的有效图片（src不为空）
  getValidImages = (): PhotoViewData[] => {
    return this.state.images.filter(item => item.src && item.src.trim() !== '');
  }

  // 计算原始索引对应的有效索引
  getValidIndexByOriginal = (originalIndex: number): number => {
    const validImages = this.getValidImages();
    // 没有有效图片时返回-1
    if (validImages.length === 0) return -1;

    // 构建原始索引到有效索引的映射表
    const indexMap: Record<number, number> = {};
    let validIdx = 0;

    this.state.images.forEach((item, idx) => {
      if (item.src && item.src.trim() !== '') {
        indexMap[idx] = validIdx;
        validIdx++;
      }
    });

    // 如果当前原始索引有对应的有效索引则返回，否则返回0
    return indexMap[originalIndex] !== undefined ? indexMap[originalIndex] : 0;
  }

  // 性能优化：创建带索引的事件处理函数，适配正确的参数类型
  createInputChangeHandler = (index: number) => {
    return (value: ValueType, event: any) => {
      this.handleInputChange(index, value);
    };
  }

  // 处理输入框内容变化，接收正确类型的value参数
  handleInputChange = (index: number, value: ValueType) => {
    const srcValue = value ? String(value).trim() : ''

    const newImages = this.state.images.map((it, idx) => {
      if (idx !== index) return it
      return { ...it, src: srcValue } // ✅ 拷贝 item 再改
    })

    this.setState({ images: newImages }, () => {
      const newValidIndex = this.getValidIndexByOriginal(this.state.originalIndex)
      this.setState({ validIndex: newValidIndex })
    })
  }

  toggleUseTiff = (index: number) => {
    return (checkedOrEvent: any) => {
      const checked =
        typeof checkedOrEvent === 'boolean'
          ? checkedOrEvent
          : !!checkedOrEvent?.target?.checked

      const newImages = this.state.images.map((it, idx) => {
        if (idx !== index) return it
        // ✅ 勾选：加 useTiff；取消：移除 useTiff 字段
        if (checked) return { ...it, useTiff: true } as any
        const { useTiff, ...rest } = it as any
        return rest
      })

      this.setState({ images: newImages })
    }
  }

  // 处理图片预览，正确映射索引
  preview = (originalIndex: number) => {
    const validImages = this.getValidImages()
    // 如果没有有效图片，不执行预览
    if (validImages.length === 0) return

    // 计算当前原始索引对应的有效索引
    const validIndex = this.getValidIndexByOriginal(originalIndex)

    // 同步更新两个索引并显示预览
    this.setState({
      originalIndex: originalIndex,
      validIndex: validIndex,
      visible: true
    });
  }

  onClose = () => {
    this.setState({ visible: false })
  }

  render() {
    const validImages = this.getValidImages()
    const style: CSSProperties = { width: 800, height: 500, position: 'relative', border: '1px solid black' }

    return (
      <div className={prefixCls}>
        {/* 图片地址输入框 */}
        {this.state.images.map((item, index) => {
          return (
            <div key={`input-${item.id}`} style={{ display: 'flex', marginBottom: 8, gap: 8 }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                第{index + 1}张：
              </div>
              <Checkbox
               
                value={!!(item as any).useTiff}
                onChange={this.toggleUseTiff(index)}
              >
                useTiff
              </Checkbox>
              <Input
               
                value={item.src}
                onChange={this.createInputChangeHandler(index)}
                style={{ flex: 1 }}
                placeholder="请输入图片地址，为空则不参与预览"
              />
            </div>
          )
        })}
        {/* 原有图片展示区域 */}
        {this.state.images.map((item, index) => {
          if (item.src) {
            return (
              <div key={item.id} style={{ marginBottom: 8 }}>
                <Img
                 
                  index={index}
                  src={item.src}
                  onClick={this.preview} />
                <br />
              </div>
            )
          } else {
            return null
          }
        })}
        {/* PhotoView - 使用有效索引和有效图片数组 */}
        <PhotoView
          visible={this.state.visible}
          photoIndex={this.state.validIndex}
          data={validImages}
          onClose={this.onClose} />
        <div style={style}>
          <PhotoView
            visible={false}
            photoIndex={this.state.validIndex}
            data={validImages}
            showFullButton
            previewMode
          />
        </div>
      </div>
    )
  }
}
```

### 图片窗口适配

```jsx
import { PureComponent } from 'react';
import { PhotoView, Button, constants, PhotoViewData } from '@weapp/ui';

const prefixCls = `${constants.uiPcClsPrefix}-demo-photoview`;

const images: PhotoViewData[] = [
  {
    id: '1.1',
    src: '/build/ui/demo/assets/photo-view/1.jpg',
    originSrc: '/build/ui/demo/assets/photo-view/7.png',
    intro: '这是两只小肥羊',
    user: '屈正巍',
    time: '2021-10-22 18:00:00',
    imgSize: 1024,
  },
  {
    id: '1',
    src: '/build/ui/demo/assets/photo-view/10.jpg',
    intro: '很高很高的图',
    user: '屈正巍',
    time: '2023-08-23 18:00:00'
  },
  {
    id: '2',
    src: '/build/ui/demo/assets/photo-view/11.jpg',
    intro: '很宽很宽的图',
    user: '屈正巍',
    time: '2023-08-23 18:00:00'
  },
]

const Img = (props: any) => {
  const { index, onClick } = props
  function preview() {
    onClick(index)
  }
  return <img style={{ maxHeight: 200, maxWidth: 200 }} alt={props.src} src={props.src} onClick={preview} />
}

export default class PhotoViewDemo extends PureComponent<{}> {

  state = {
    visible: false,
    index: 0,
    defaultPhotoFitDialog: false,
  }

  showImage0 = () => PhotoView.showImage(images, 0, { defaultPhotoFitDialog: this.state.defaultPhotoFitDialog })

  showImage1 = () => PhotoView.showImage(images, 1, { defaultPhotoFitDialog: this.state.defaultPhotoFitDialog })

  showImage2 = () => PhotoView.showImage(images, 2, { defaultPhotoFitDialog: this.state.defaultPhotoFitDialog })

  preview = (index: number) => {
    this.setState({ index }, () => {
      this.setState({ visible: true })
    })
  }

  onClose = () => this.setState({ visible: false })

  handleChangeDefaultPhotoFitDialog = () => this.setState({ defaultPhotoFitDialog: !this.state.defaultPhotoFitDialog })

  render() {
    const { visible, index, defaultPhotoFitDialog } = this.state
    console.log('defaultPhotoFitDialog', defaultPhotoFitDialog)
    return (
      <div className={prefixCls}>
        <Button
         
          onClick={this.handleChangeDefaultPhotoFitDialog}
          type={defaultPhotoFitDialog ? 'primary' : 'danger'}
        >
          点击切换默认图片尺寸：{defaultPhotoFitDialog ? '适应窗口' : '原始尺寸'}
        </Button>
        <br />
        <Button onClick={this.showImage0}>预览第1张</Button>
        <Button onClick={this.showImage1}>预览第2张</Button>
        <Button onClick={this.showImage2}>预览第3张</Button>
        <br />
        {images.map((item, index) =>
          <Img
            key={item.id}
            index={index}
            src={item.src}
            onClick={this.preview} />
        )}
        <PhotoView
          visible={visible}
          photoIndex={index}
          data={images}
          onClose={this.onClose}
          defaultPhotoFitDialog={defaultPhotoFitDialog}
        />
      </div>
    )
  }
}
```

## API

### 下载说明：

```
图片下载功能说明：
1.当有fileId时，优先默认走下载接口；
2.当存在downloadSrc时，默认调用a标签的下载方法；
3.当只存在src时，调用weappSDK.openLink方法，用户需要在新页面中手动右键保存图片；

fileId说明：
1.当有fileId时，优先默认走下载接口；
2.当有fileId时，会根据图文识别开启标识额外展示图文识别操作按钮，点击后可开启图文识别功能；
```

### 类型说明：（请从@weapp/ui 中导出）

```typescript
type PhotoViewData = {
  id: string; // 数据id
  src: string; // 图片地址
  fileId?: string; // 调用下载接口和开启图文识别传的内部文件fileId
  module?: string; //用于走下载接口的module
  downloadParams?: string; //用于走下载接口的其他参数
  downloadSrc?: string; // 专门用于下载的图片地址
  intro?: React.ReactNode | string; //图片介绍
  user?: React.ReactNode | string; //图片人员名称
  time?: React.ReactNode | string; //图片时间
  imgSize?: number; //图片大小
  hideEnlarge?: boolean; // 隐藏放大按钮
  hideShrink?: boolean; // 隐藏缩小按钮
  hideRotate?: boolean; // 隐藏顺时针旋转
  hideAntiRotate?: boolean; // 隐藏逆时针旋转
  hideDownload?: boolean; // 隐藏下载按钮
  hideInfo?: boolean; // 隐藏信息按钮
  useTiff?: boolean: // 是否使用tiff地址 -> 如果为true需要额外单独转换
};
```

### tiff图片格式转换说明：（260301基线后支持）

```
图片预览组件内置了可以结构并重新渲染预览tiff多页图片格式的功能
注意：默认缓存时间 window.TIFF_CACHE_TTL_MS = 600000 
1.全量检测
满足以下任意条件时，会对所有的图片地址尝试进行tiff转换
- `window.WEAPP_UI_PHOTO_VIEW_ALL_CHECK_TIFF === true`
- 或组件 props：`allCheckTiff === true`

> 行为：会对 `images/rawInnerPhotoData` 中每一个 item 调用 TIFF 探测逻辑（HEAD/GET + 接口判断），命中 TIFF 则拆页转 PNG

适用场景：
- 无法预先知道哪些链接是 TIFF（后端 Content-Type 不稳定、URL 无后缀等）
- “一劳永逸”，保证所有 TIFF 都能自动预览

缺点：  
- 在没有tiff格式的图片组打开的时候会影响渲染速度

2.单独开启（未开全局/props 开关时的节能模式）

- 只有当 `images/rawInnerPhotoData` 中存在任意 `item.useTiff === true` 时，才会触发 TIFF 请求/转换
- 并且只会对 `useTiff === true` 的 item 做探测/转换；其他 item 直接原样展示（不发 HEAD/GET，不解码）

> 行为：如果所有 item 都没有 `useTiff` 标记，则完全跳过 TIFF 处理（`tiffCompleted` 直接为 true），性能开销最小。

适用场景：
- 能在业务侧明确标记哪些资源“可能是 TIFF”（或确定是 TIFF）
- 尽量减少不必要的 HEAD/GET 请求与解码计算，提升低配电脑打开速度

缺点：
- 需要模块在images入参图片列表中指定useTiff参数

3.其余全局参数
`window?.WEAPP_UI_PHOTO_VIEW_TIFF_COPY_ENABLE` tiff类型图片是否开启复制按钮
`window?.WEAPP_UI_PHOTO_VIEW_TIFF_GRAPHIC_ENABLE` tiff类型图片是否开启图文识别按钮
```

### 参数说明如下：

| 参数 | 说明 | 类型 | 是否必填 | 默认值 |
| --- | --- | --- | --- | --- |
| className | 自定义类名 | string | 否 |  |
| maskClassName | 遮罩层类名 | boolean | 否 | false |
| viewClassName | 图片容器类名 | boolean | 否 | false |
| imageClassName | 图片类名 | boolean | 否 | false |
| data | 图片数据 | PhotoViewData[] | 是 |  |
| loginFree | 是否为免登录 -> 决定调用哪个下载接口 | boolean |  |  |
| visible | 是否打开预览 | boolean | 否 |  |
| photoIndex | 打开第几图片 | number | 否 |  |
| onClose | 当相册浏览关闭的回调函数 | () => void | 否 |  |
| customToolBar | 自定义添加操作栏按钮 | ReactNode | 否 |  |
| maskClosable | 是否可点击背景关闭预览 | boolean | 否 | true |
| photoClosable | 是否可点击图片关闭预览 | boolean | 否 | false |
| loadingElement | 自定义 loading | ReactNode | 否 |  |
| brokenElement | 加载失败 Element | ReactNode | 否 |  |
| videoBrokenElement | 视频加载失败 Element | ReactNode | 否 |  |
| customDownload | 自定义下载方法 | (data: PhotoViewData) => void | 否 |  |
| customRender | 自定义渲染 | (_: any, ele: React.ReactNode) => React.ReactNode | 否 |  |
| options | 自定义配置项 | {string:any} | 否 |  |
| openGraphic | 是否开启图文识别功能 | boolean | 否 | true |
| fullScreenScale | 自定义配置全屏展示比例 | number | 否 | 浏览100 客户端85 |
| photoViewCanDrag | 是否可以拖拽 -> 注意用法：参照最后一个demo | boolean | 否 | true |
| showCopy | 是否展示复制按钮 | boolean | 否 | true |
| showClose | 是否展示关闭按钮 | boolean | 否 | true |
| shareClosePhotoView | 点击分享是否关闭图片预览 | boolean | 否 | true |
| showFullButton | 网页端是否展示浏览器全屏按钮 | boolean | 否 | false |
| showShrinkButton | 网页端是否展示缩放按钮（默认缩放到60，当传入了fullScreenScale则默认关闭） | boolean | 否 | true |
| shrinkScale | 网页端缩放后的比例 | number | 否 | 60 |
| showCounter | 是否展示计数按钮 | boolean | 否 | true |
| showCounterForSingleImage | 在单页的时候是否展示页码 | boolean | 否 | true |
| previewMode | 是否开启单页预览模式 | boolean | 否 | false |
| scaleChangeToCenter | 在图片大小改变时图片是否自动居中 | boolean | 否 | false |
| disableSingleImageTouch | 在移动设备只有一张图片时是否禁止触摸事件 | boolean | 否 | false |
| documentClickWhiteList | 全屏点击关闭拦截白名单 | string[] | 否 |  |
| photoViewBase | 照片预览的基础定位元素 | 'window' \| 'document' | 否 | 'window' |
| allCheckTiff | 当前组件是否开启所有图片地址tiff检测 | boolean | 否 | false |
| tiffCacheTTL | TIFF 处理结果的缓存时间 | number | 否 | 600000 |
| defaultPhotoFitDialog | 是否默认开启照片适应弹出框 | boolean | 否 | true |
### PhotoView.showImage

通过 function 调用方式预览图片(具体请参照 demo2)

```typescript
//其中otherParams为需要传递的参数
function (items: Array<PhotoViewData>, index: number, otherParams: PhotoViewProps) {
  ...
}
```
