u-album.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <template>
  2. <view class="u-album">
  3. <view class="u-album__row" ref="u-album__row" v-for="(arr, index) in showUrls" :forComputedUse="albumWidth"
  4. :key="index">
  5. <view class="u-album__row__wrapper" v-for="(item, index1) in arr" :key="index1"
  6. :style="[imageStyle(index + 1, index1 + 1)]" @tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''">
  7. <image :src="getSrc(item)" :mode="
  8. urls.length === 1
  9. ? imageHeight > 0
  10. ? singleMode
  11. : 'widthFix'
  12. : multipleMode
  13. " :style="[
  14. {
  15. width: imageWidth,
  16. height: imageHeight
  17. }
  18. ]"></image>
  19. <view v-if="
  20. showMore &&
  21. urls.length > rowCount * showUrls.length &&
  22. index === showUrls.length - 1 &&
  23. index1 === showUrls[showUrls.length - 1].length - 1
  24. " class="u-album__row__wrapper__text">
  25. <!-- <u--text
  26. :text="`+${urls.length - maxCount}`"
  27. color="#fff"
  28. :size="multipleSize * 0.3"
  29. align="center"
  30. customStyle="justify-content: center"
  31. ></u--text> -->
  32. <view class="text" :style="{fontSize:multipleSize * 0.3+'px'}">{{`+${urls.length - maxCount}`}}</view>
  33. </view>
  34. </view>
  35. </view>
  36. </view>
  37. </template>
  38. <script>
  39. // #ifdef APP-NVUE
  40. // 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
  41. const dom = uni.requireNativePlugin('dom')
  42. // #endif
  43. /**
  44. * Album 相册
  45. * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
  46. * @tutorial https://www.uviewui.com/components/album.html
  47. *
  48. * @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
  49. * @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
  50. * @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
  51. * @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
  52. * @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
  53. * @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
  54. * @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
  55. * @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
  56. * @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
  57. * @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
  58. * @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
  59. *
  60. * @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
  61. * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
  62. */
  63. export default {
  64. name: 'uAlbum',
  65. data() {
  66. return {
  67. // 单图的宽度
  68. singleWidth: 0,
  69. // 单图的高度
  70. singleHeight: 0,
  71. // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
  72. singlePercent: 0.6
  73. }
  74. },
  75. props: {
  76. // 图片地址,Array<String>|Array<Object>形式
  77. urls: {
  78. type: Array,
  79. default: []
  80. },
  81. // 指定从数组的对象元素中读取哪个属性作为图片地址
  82. keyName: {
  83. type: String,
  84. default: 0
  85. },
  86. // 单图时,图片长边的长度
  87. singleSize: {
  88. type: [String, Number],
  89. default: 180
  90. },
  91. // 多图时,图片边长
  92. multipleSize: {
  93. type: [String, Number],
  94. default:140
  95. },
  96. // 多图时,图片水平和垂直之间的间隔
  97. space: {
  98. type: [String, Number],
  99. default: 6
  100. },
  101. // 单图时,图片缩放裁剪的模式
  102. singleMode: {
  103. type: String,
  104. default: "scaleToFill"
  105. },
  106. // 多图时,图片缩放裁剪的模式
  107. multipleMode: {
  108. type: String,
  109. default: "aspectFill"
  110. },
  111. // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
  112. maxCount: {
  113. type: [String, Number],
  114. default: 9
  115. },
  116. // 是否可以预览图片
  117. previewFullImage: {
  118. type: Boolean,
  119. default: true
  120. },
  121. // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
  122. rowCount: {
  123. type: [String, Number],
  124. default: 3
  125. },
  126. // 超出maxCount时是否显示查看更多的提示
  127. showMore: {
  128. type: Boolean,
  129. default: true
  130. }
  131. },
  132. watch: {
  133. urls: {
  134. immediate: true,
  135. handler(newVal) {
  136. if (newVal.length === 1) {
  137. this.getImageRect()
  138. }
  139. }
  140. }
  141. },
  142. computed: {
  143. imageStyle() {
  144. return (index1, index2) => {
  145. const {
  146. space,
  147. rowCount,
  148. multipleSize,
  149. urls
  150. } = this, {
  151. addUnit,
  152. addStyle
  153. } = uni.$u,
  154. rowLen = this.showUrls.length,
  155. allLen = this.urls.length
  156. const style = {
  157. marginRight: addUnit(space),
  158. marginBottom: addUnit(space)
  159. }
  160. // 如果为最后一行,则每个图片都无需下边框
  161. if (index1 === rowLen) style.marginBottom = 0
  162. // 每行的最右边一张和总长度的最后一张无需右边框
  163. if (
  164. index2 === rowCount ||
  165. (index1 === rowLen &&
  166. index2 === this.showUrls[index1 - 1].length)
  167. )
  168. style.marginRight = 0
  169. return style
  170. }
  171. },
  172. // 将数组划分为二维数组
  173. showUrls() {
  174. const arr = []
  175. this.urls.map((item, index) => {
  176. // 限制最大展示数量
  177. if (index + 1 <= this.maxCount) {
  178. // 计算该元素为第几个素组内
  179. const itemIndex = Math.floor(index / this.rowCount)
  180. // 判断对应的索引是否存在
  181. if (!arr[itemIndex]) {
  182. arr[itemIndex] = []
  183. }
  184. arr[itemIndex].push(item)
  185. }
  186. })
  187. return arr
  188. },
  189. imageWidth() {
  190. return uni.$u.addUnit(
  191. this.urls.length === 1 ? this.singleWidth : this.multipleSize
  192. )
  193. },
  194. imageHeight() {
  195. return uni.$u.addUnit(
  196. this.urls.length === 1 ? this.singleHeight : this.multipleSize
  197. )
  198. },
  199. // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
  200. // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
  201. albumWidth() {
  202. let width = 0
  203. if (this.urls.length === 1) {
  204. width = this.singleWidth
  205. } else {
  206. width =
  207. this.showUrls[0].length * this.multipleSize +
  208. this.space * (this.showUrls[0].length - 1)
  209. }
  210. this.$emit('albumWidth', width)
  211. return width
  212. }
  213. },
  214. methods: {
  215. // 预览图片
  216. onPreviewTap(url) {
  217. const urls = this.urls.map((item) => {
  218. return this.getSrc(item)
  219. })
  220. uni.previewImage({
  221. current: url,
  222. urls
  223. })
  224. },
  225. // 获取图片的路径
  226. getSrc(item) {
  227. return uni.$u.test.object(item) ?
  228. (this.keyName && item[this.keyName]) || item.src :
  229. item
  230. },
  231. // 单图时,获取图片的尺寸
  232. // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
  233. // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
  234. getImageRect() {
  235. const src = this.getSrc(this.urls[0])
  236. uni.getImageInfo({
  237. src,
  238. success: (res) => {
  239. // 判断图片横向还是竖向展示方式
  240. const isHorizotal = res.width >= res.height
  241. this.singleWidth = isHorizotal ?
  242. this.singleSize :
  243. (res.width / res.height) * this.singleSize
  244. this.singleHeight = !isHorizotal ?
  245. this.singleSize :
  246. (res.height / res.width) * this.singleWidth
  247. },
  248. fail: () => {
  249. this.getComponentWidth()
  250. }
  251. })
  252. },
  253. // 获取组件的宽度
  254. async getComponentWidth() {
  255. // 延时一定时间,以获取dom尺寸
  256. // await uni.$u.sleep(30)
  257. setTimeout(()=>{
  258. // #ifndef APP-NVUE
  259. this.$uGetRect('.u-album__row').then((size) => {
  260. this.singleWidth = size.width * this.singlePercent
  261. })
  262. // #endif
  263. // #ifdef APP-NVUE
  264. // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
  265. const ref = this.$refs['u-album__row'][0]
  266. ref &&
  267. dom.getComponentRect(ref, (res) => {
  268. this.singleWidth = res.size.width * this.singlePercent
  269. })
  270. // #endif
  271. },30)
  272. },
  273. // 查询节点信息
  274. // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21)
  275. // 解决办法为在组件根部再套一个没有任何作用的view元素
  276. $uGetRect(selector, all) {
  277. return new Promise((resolve) => {
  278. uni.createSelectorQuery()
  279. .in(this)[all ? 'selectAll' : 'select'](selector)
  280. .boundingClientRect((rect) => {
  281. if (all && Array.isArray(rect) && rect.length) {
  282. resolve(rect)
  283. }
  284. if (!all && rect) {
  285. resolve(rect)
  286. }
  287. })
  288. .exec()
  289. })
  290. },
  291. }
  292. }
  293. </script>
  294. <style lang="scss" scoped>
  295. @import '../components/components.scss';
  296. .u-album {
  297. @include flex(column);
  298. &__row {
  299. @include flex(row);
  300. flex-wrap: wrap;
  301. &__wrapper {
  302. position: relative;
  303. &__text {
  304. position: absolute;
  305. top: 0;
  306. left: 0;
  307. right: 0;
  308. bottom: 0;
  309. background-color: rgba(0, 0, 0, 0.3);
  310. @include flex(row);
  311. justify-content: center;
  312. align-items: center;
  313. }
  314. }
  315. }
  316. }
  317. .text{
  318. display: flex;
  319. justify-content: center;
  320. align-items: center;
  321. }
  322. </style>