diff --git a/example/app.json b/example/app.json index c5de3e8a8..ebfc409aa 100644 --- a/example/app.json +++ b/example/app.json @@ -56,7 +56,8 @@ "pages/navbar/navbar", "pages/date-time-picker/date-time-picker", "pages/action-sheet/action-sheet", - "pages/notice-bar/notice-bar" + "pages/notice-bar/notice-bar", + "pages/image-viewer/image-viewer" ], "usingComponents": { "t-demo": "../../components/demo-block/index", @@ -123,7 +124,8 @@ "t-navbar": "tdesign-miniprogram/navbar/navbar", "t-date-time-picker": "tdesign-miniprogram/date-time-picker/date-time-picker", "t-action-sheet": "tdesign-miniprogram/action-sheet/action-sheet", - "t-notice-bar": "tdesign-miniprogram/notice-bar/notice-bar" + "t-notice-bar": "tdesign-miniprogram/notice-bar/notice-bar", + "t-image-viewer": "tdesign-miniprogram/image-viewer/image-viewer" }, "window": { "backgroundTextStyle": "light", diff --git a/example/pages/home/data/display.ts b/example/pages/home/data/display.ts index 89a6d6ed7..339fd0bf1 100644 --- a/example/pages/home/data/display.ts +++ b/example/pages/home/data/display.ts @@ -54,10 +54,10 @@ const display = { name: 'Image', label: '图片', }, - // { - // name: 'Preview', - // label: '图片预览', - // }, + { + name: 'ImageViewer', + label: '图片预览', + }, { name: 'Swiper', label: '轮播图', diff --git a/example/pages/image-viewer/image-viewer.json b/example/pages/image-viewer/image-viewer.json new file mode 100644 index 000000000..708cd5713 --- /dev/null +++ b/example/pages/image-viewer/image-viewer.json @@ -0,0 +1,7 @@ +{ + "navigationBarTitleText": "ImageViewer", + "navigationBarBackgroundColor": "#fff", + "usingComponents": { + "t-demo": "../../components/demo-block/index" + } +} diff --git a/example/pages/image-viewer/image-viewer.less b/example/pages/image-viewer/image-viewer.less new file mode 100644 index 000000000..5e22e43af --- /dev/null +++ b/example/pages/image-viewer/image-viewer.less @@ -0,0 +1,62 @@ +.image-viewer { + background-color: #fff; + font-size: 32rpx; + line-height: 48rpx; + color: rgba(0, 0, 0, 0.9); + padding: 48rpx 0rpx 96rpx 0rpx; + height: 100vh; + + .slot-wrap { + background-color: #fff; + padding: 10px; + } + + .title { + font-weight: bold; + font-size: 40rpx; + line-height: 56rpx; + color: rgba(0, 0, 0, 0.9); + padding: 0 32rpx; + } + + .desc { + margin-top: 16rpx; + font-size: 26rpx; + line-height: 36rpx; + color: rgba(0, 0, 0, 0.4); + padding: 0 32rpx; + } + + .sub-title { + margin-top: 40rpx; + font-weight: bold; + padding: 0 32rpx; + } + + .t-button { + width: 686rpx; + height: 96rpx; + border-radius: 8rpx; + display: flex; + align-items: center; + justify-content: center; + margin-top: 32rpx; + + &::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 200%; + height: 200%; + transform: scale(0.5); + transform-origin: 0 0; + box-sizing: border-box; + border: 2rpx solid #dcdcdc; + } + } + + .demo-block__oper { + padding: 0 32rpx; + } +} diff --git a/example/pages/image-viewer/image-viewer.ts b/example/pages/image-viewer/image-viewer.ts new file mode 100644 index 000000000..c7b1801c5 --- /dev/null +++ b/example/pages/image-viewer/image-viewer.ts @@ -0,0 +1,136 @@ +import Toast from 'tdesign-miniprogram/toast/index'; + +Page({ + data: { + visible: false, + showIndex: false, + closeBtn: false, + deleteBtn: false, + images: [], + operList1: [ + { + title: '图片预览类型', + btns: [ + { + type: 'basic', + text: '基础图片预览', + }, + { + type: 'withDeleteBasic', + text: '有删除操作', + }, + { + type: 'withOverHeightBasic', + text: '图片超高情况', + }, + { + type: 'withOverWidthBasic', + text: '图片超宽情况', + }, + ], + }, + ], + }, + + onReady() { + // + }, + + clickHandle(e: any) { + const { detail } = e; + switch (detail) { + case 'basic': + this.setData({ + images: [ + ...Array.from({ length: 6 }).fill( + 'https://oteam-tdesign-1258344706.cos.ap-guangzhou.myqcloud.com/miniprogram/images/preview1.png', + ), + ], + showIndex: true, + visible: true, + }); + break; + case 'withDeleteBasic': + this.setData({ + images: [ + ...Array.from({ length: 6 }).fill( + 'https://oteam-tdesign-1258344706.cos.ap-guangzhou.myqcloud.com/miniprogram/images/preview4.png', + ), + ], + showIndex: true, + visible: true, + closeBtn: true, + deleteBtn: true, + }); + break; + case 'withOverHeightBasic': + this.setData({ + images: [ + ...Array.from({ length: 6 }).fill( + 'https://oteam-tdesign-1258344706.cos.ap-guangzhou.myqcloud.com/miniprogram/images/preview3.png', + ), + ], + showIndex: true, + visible: true, + }); + break; + case 'withOverWidthBasic': + this.setData({ + images: [ + ...Array.from({ length: 6 }).fill( + 'https://oteam-tdesign-1258344706.cos.ap-guangzhou.myqcloud.com/miniprogram/images/preview2.png', + ), + ], + showIndex: true, + visible: true, + }); + break; + default: + break; + } + }, + + onChange(e: any) { + const { + detail: { index }, + } = e; + Toast({ + context: this, + selector: '#t-toast', + message: `翻到第${index + 1}个`, + }); + }, + + onDelete(e: any) { + const { + detail: { index }, + } = e; + Toast({ + context: this, + selector: '#t-toast', + message: `删除第${index + 1}个`, + }); + }, + + onClose(e: any) { + const { + detail: { trigger }, + } = e; + if (trigger === 'overlay') { + Toast({ + context: this, + selector: '#t-toast', + message: '点击overlay关闭', + }); + } else if (trigger === 'button') { + Toast({ + context: this, + selector: '#t-toast', + message: `点击button关闭`, + }); + } + this.setData({ + visible: false, + }); + }, +}); diff --git a/example/pages/image-viewer/image-viewer.wxml b/example/pages/image-viewer/image-viewer.wxml new file mode 100644 index 000000000..a92e42df3 --- /dev/null +++ b/example/pages/image-viewer/image-viewer.wxml @@ -0,0 +1,16 @@ + + ImageViewer 图片预览 + 图片全屏放大预览效果,包含全屏背景色、页码位置样式、增加操作等规范 + + + + diff --git a/example/project.config.json b/example/project.config.json index ac76fc5cb..42a3e1528 100644 --- a/example/project.config.json +++ b/example/project.config.json @@ -86,6 +86,12 @@ "name": "actionSheet", "pathName": "pages/action-sheet/action-sheet", "scene": null + }, + { + "id": -1, + "name": "image-viewer", + "pathName": "pages/image-viewer/image-viewer", + "scene": null } ] } diff --git a/src/image-viewer/README.md b/src/image-viewer/README.md new file mode 100644 index 000000000..44c28b5b5 --- /dev/null +++ b/src/image-viewer/README.md @@ -0,0 +1,100 @@ +--- +title: ImageViewer 图片预览 +description: 图片全屏放大预览效果,包含全屏背景色、页码位置样式、增加操作等规范。 +spline: data +isComponent: true +--- + +## 引入 + +全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。 + +```json +"usingComponents": { + "t-image-viewer": "tdesign-miniprogram/image-viewer/image-viewer", +} +``` + +## 代码演示 + +### 基础用法 + +```html + +``` + +### 显示页码 + +```html + +``` + +### 带删除操作 + +```html + +``` + +### 支持自定义操作按钮 + +```html + + 我是自定义的关闭内容 + 我是自定义的删除内容 + +``` + + +## API + +### ImageViewer Props + +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +background-color | String / Number | rgba(0, 0, 0, 1) | 遮罩的背景颜色 | N +images | Array | [] | 图片数组。TS 类型:`Array` | N +initial-index | Number | 0 | 默认展示第几项 | N +show-index | Boolean | false | 是否显示页码 | N +delete-btn | Boolean | false | 是否显示删除操作,前提需要开启页码 | N +close-btn | Boolean | false | 是否显示关闭操作,前提需要开启页码 | N +visible | Boolean | false | 隐藏/显示预览 | N +default-visible | Boolean | undefined | 隐藏/显示预览。非受控属性 | N + + + +### ImageViewer Events + +名称 | 参数 | 描述 +-- | -- | -- +change | `(index: Number)` | 翻页时回调 +close | `(trigger: 'overlay' | 'button' , visible: Boolean, index: Number)` | 点击操作按钮button或者overlay时触发 +delete | `(index: Number)` | 点击删除操作按钮时触发 + diff --git a/src/image-viewer/image-viewer.json b/src/image-viewer/image-viewer.json new file mode 100644 index 000000000..57fe7ea66 --- /dev/null +++ b/src/image-viewer/image-viewer.json @@ -0,0 +1,9 @@ +{ + "component": true, + "usingComponents": { + "t-image": "../image/image", + "t-icon": "../icon/icon", + "t-swiper": "../swiper/swiper", + "t-swiper-item": "../swiper/swiper-item" + } +} diff --git a/src/image-viewer/image-viewer.less b/src/image-viewer/image-viewer.less new file mode 100644 index 000000000..57d73c355 --- /dev/null +++ b/src/image-viewer/image-viewer.less @@ -0,0 +1,81 @@ +@import '../common/style/index.less'; + +@image-viewer: ~'@{prefix}-image-viewer'; +@image-viewer-nav-height: 48rpx; +@image-viewer-nav-margin: 36rpx; + +.@{image-viewer} { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 1001; + height: 100%; + transform: translateZ(0); + overflow: hidden; + + &__mask { + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + } + + &__content { + width: 100vw; + display: inline-block; + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 1005; + } + + &__image { + width: 100%; + display: inline-block; + position: absolute; + top: 50%; + transform: translateY(-50%); + image { + width: inherit !important; + height: inherit !important; + display: block; + } + } + + &__nav { + width: 100%; + position: fixed; + display: flex; + align-items: center; + justify-content: space-between; + height: @image-viewer-nav-height; + top: 68rpx; + left: 0; + color: #ffffff; + z-index: 1005; + + &-icon { + flex: 0 0 @image-viewer-nav-height; + width: @image-viewer-nav-height; + height: @image-viewer-nav-height; + } + + &-close { + margin-left: @image-viewer-nav-margin; + } + + &-delete { + margin-right: @image-viewer-nav-margin; + } + + &-index { + flex: 1; + font-size: 28rpx; + text-align: center; + } + } +} diff --git a/src/image-viewer/image-viewer.ts b/src/image-viewer/image-viewer.ts new file mode 100644 index 000000000..79ce39b5e --- /dev/null +++ b/src/image-viewer/image-viewer.ts @@ -0,0 +1,150 @@ +import { styles } from '../common/utils'; +import { SuperComponent, wxComponent } from '../common/src/index'; +import config from '../common/config'; +import props from './props'; + +const { prefix } = config; +const name = `${prefix}-image-viewer`; + +@wxComponent() +export default class ImageViewer extends SuperComponent { + externalClasses = [`${prefix}-class`]; + + properties = { + ...props, + }; + + data = { + prefix, + classPrefix: name, + currentSwiperIndex: 0, + windowHeight: 0, + windowWidth: 0, + imagesShape: {}, + }; + + options = { + multipleSlots: true, + }; + + controlledProps = [ + { + key: 'visible', + event: 'close', + }, + ]; + + ready() { + this.saveScreenSize(); + } + + observers = { + visible(value) { + this.setData({ + currentSwiperIndex: value ? this.properties.initialIndex : 0, + }); + }, + }; + + methods = { + saveScreenSize() { + const { windowHeight, windowWidth } = wx.getSystemInfoSync(); + this.setData({ + windowHeight, + windowWidth, + }); + }, + + calcImageDisplayStyle(imageWidth, imageHeight) { + const { windowWidth, windowHeight } = this.data; + // 图片宽高都小于屏幕宽高 + if (imageWidth < windowWidth && imageHeight < windowHeight) { + return { + mode: 'scaleToFill', + styleObj: { + width: '100%', + height: `${imageHeight * 2}rpx`, + }, + }; + } + + // 图片宽高都大等于屏幕宽高 + if (imageWidth >= windowWidth && imageHeight >= windowHeight) { + return { + mode: 'aspectFit', + styleObj: { + width: '100%', + height: `${(imageHeight / (imageWidth / windowWidth)) * 2}rpx`, + }, + }; + } + + // 图片超高:图片宽小于屏幕宽,图片高大于等于屏幕高 + if (imageWidth < windowWidth && imageHeight >= windowHeight) { + return { + mode: 'widthFix', + styleObj: { + width: `${(imageWidth / (imageHeight / windowHeight)) * 2}rpx`, + height: '100vh', + left: '50%', + transform: 'translate(-50%, -50%)', + }, + }; + } + + // 图片超宽:图片宽大于等于屏幕宽,图片高小于屏幕高 + if (imageWidth >= windowWidth && imageHeight < windowHeight) { + return { + mode: 'heightFix', + styleObj: { + width: '100%', + height: `${(imageHeight / (imageWidth / windowWidth)) * 2}rpx`, + }, + }; + } + }, + + onImageLoadSuccess(e: WechatMiniprogram.TouchEvent) { + const { + detail: { width, height }, + currentTarget: { + dataset: { index }, + }, + } = e; + const { mode, styleObj } = this.calcImageDisplayStyle(width, height); + const origin = this.data.imagesShape; + this.setData({ + imagesShape: { + ...origin, + [index]: { + mode, + style: styles({ ...styleObj }), + }, + }, + }); + }, + + onSwiperChange(e: WechatMiniprogram.TouchEvent) { + const { + detail: { current }, + } = e; + this.setData({ + currentSwiperIndex: current, + }); + this._trigger('change', { index: current }); + }, + + onClose(e: WechatMiniprogram.TouchEvent) { + const { + target: { + dataset: { source }, + }, + } = e; + this._trigger('close', { visible: false, trigger: source, index: this.data.currentSwiperIndex }); + }, + + onDelete() { + this._trigger('delete', { index: this.data.currentSwiperIndex }); + }, + }; +} diff --git a/src/image-viewer/image-viewer.wxml b/src/image-viewer/image-viewer.wxml new file mode 100644 index 000000000..2ce474e4c --- /dev/null +++ b/src/image-viewer/image-viewer.wxml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + {{currentSwiperIndex + 1}}/{{images.length}} + + + + + + diff --git a/src/image-viewer/props.ts b/src/image-viewer/props.ts new file mode 100644 index 000000000..4dc23fa01 --- /dev/null +++ b/src/image-viewer/props.ts @@ -0,0 +1,52 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdImageViewerProps } from './type'; +const props: TdImageViewerProps = { + /** 遮罩的背景颜色 */ + backgroundColor: { + type: String, + optionalTypes: [Number], + value: 'rgba(0, 0, 0, 1)', + }, + /** 图片数组 */ + images: { + type: Array, + value: [], + }, + /** 默认展示第几项 */ + initialIndex: { + type: Number, + value: 0, + }, + /** 是否显示页码 */ + showIndex: { + type: Boolean, + value: false, + }, + /** 是否显示删除操作 */ + deleteBtn: { + type: Boolean, + value: false, + }, + /** 是否显示关闭操作 */ + closeBtn: { + type: Boolean, + value: false, + }, + /** 隐藏/显示预览 */ + visible: { + type: Boolean, + value: null, + }, + /** 隐藏/显示预览,非受控属性 */ + defaultVisible: { + type: Boolean, + value: false, + }, +}; + +export default props; diff --git a/src/image-viewer/type.ts b/src/image-viewer/type.ts new file mode 100644 index 000000000..ba6af79ff --- /dev/null +++ b/src/image-viewer/type.ts @@ -0,0 +1,67 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +export interface TdImageViewerProps { + /** + * 遮罩的背景颜色 + * @default rgba(0, 0, 0, .6) + */ + backgroundColor?: { + type: StringConstructor; + optionalTypes: Array; + value?: string | number; + }; + /** + * 图片数组 + * @default [] + */ + images?: { + type: ArrayConstructor; + value?: Array; + }; + /** + * 默认展示第几项 + * @default 0 + */ + initialIndex?: { + type: NumberConstructor; + value?: number; + }; + /** + * 是否显示页码 + * @default false + */ + showIndex?: { + type: BooleanConstructor; + value?: boolean; + }; + /** 是否显示删除操作 */ + deleteBtn?: { + type: BooleanConstructor; + value: false; + }; + /** 是否显示关闭操作 */ + closeBtn?: { + type: BooleanConstructor; + value: false; + }; + /** + * 隐藏/显示预览 + * @default false + */ + visible?: { + type: BooleanConstructor; + value?: boolean; + }; + /** + * 隐藏/显示预览,非受控属性 + * @default false + */ + defaultVisible?: { + type: BooleanConstructor; + value?: boolean; + }; +}