u-album.vue 13 KB

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