cl-image.uvue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <view
  3. class="cl-image"
  4. :class="[pt.className]"
  5. :style="{
  6. width: getUnit(width),
  7. height: getUnit(height)
  8. }"
  9. >
  10. <view
  11. class="cl-image__error"
  12. :class="[
  13. {
  14. 'is-dark': isDark
  15. },
  16. pt.error?.className
  17. ]"
  18. v-if="isError"
  19. >
  20. <slot name="error">
  21. <cl-icon
  22. :name="pt.error?.name ?? 'close-line'"
  23. :size="pt.error?.size ?? 20"
  24. :pt="{
  25. className: parseClass(['!text-surface-400', pt.error?.className])
  26. }"
  27. ></cl-icon>
  28. </slot>
  29. </view>
  30. <view
  31. class="cl-image__loading"
  32. :class="[
  33. {
  34. 'is-dark': isDark
  35. },
  36. pt.loading?.className
  37. ]"
  38. v-else-if="isLoading && showLoading"
  39. >
  40. <slot name="loading">
  41. <cl-loading :loading="true"></cl-loading>
  42. </slot>
  43. </view>
  44. <image
  45. class="cl-image__inner"
  46. :class="[pt.inner?.className]"
  47. :src="src"
  48. :mode="mode"
  49. :lazy-load="lazyLoad"
  50. :webp="webp"
  51. :show-menu-by-longpress="showMenuByLongpress"
  52. @load="onLoad"
  53. @error="onError"
  54. @tap="onTap"
  55. />
  56. <slot></slot>
  57. </view>
  58. </template>
  59. <script setup lang="ts">
  60. import { computed, ref, type PropType } from "vue";
  61. import type { PassThroughProps } from "../../types";
  62. import { isDark, isEmpty, parseClass, parsePt, getUnit } from "@/.cool";
  63. import type { ClIconProps } from "../cl-icon/props";
  64. defineOptions({
  65. name: "cl-image"
  66. });
  67. const props = defineProps({
  68. // 透传样式
  69. pt: {
  70. type: Object,
  71. default: () => ({})
  72. },
  73. // 图片源
  74. src: {
  75. type: String,
  76. default: ""
  77. },
  78. // 图片裁剪、缩放的模式
  79. mode: {
  80. type: String as PropType<
  81. | "scaleToFill"
  82. | "aspectFit"
  83. | "aspectFill"
  84. | "widthFix"
  85. | "heightFix"
  86. | "top"
  87. | "bottom"
  88. | "center"
  89. | "left"
  90. | "right"
  91. | "top left"
  92. | "top right"
  93. | "bottom left"
  94. | "bottom right"
  95. >,
  96. default: "aspectFill"
  97. },
  98. // 是否显示边框
  99. border: {
  100. type: Boolean,
  101. default: false
  102. },
  103. // 是否预览
  104. preview: {
  105. type: Boolean,
  106. default: false
  107. },
  108. // 预览图片列表
  109. previewList: {
  110. type: Array as PropType<string[]>,
  111. default: () => []
  112. },
  113. // 图片高度
  114. height: {
  115. type: [String, Number] as PropType<string | number>,
  116. default: 60
  117. },
  118. // 图片宽度
  119. width: {
  120. type: [String, Number] as PropType<string | number>,
  121. default: 60
  122. },
  123. // 是否显示加载状态
  124. showLoading: {
  125. type: Boolean,
  126. default: true
  127. },
  128. // 是否懒加载
  129. lazyLoad: {
  130. type: Boolean,
  131. default: false
  132. },
  133. // 图片显示动画效果
  134. fadeShow: {
  135. type: Boolean,
  136. default: false
  137. },
  138. // 是否解码webp格式
  139. webp: {
  140. type: Boolean,
  141. default: false
  142. },
  143. // 是否长按显示菜单
  144. showMenuByLongpress: {
  145. type: Boolean,
  146. default: false
  147. }
  148. });
  149. // 事件定义
  150. const emit = defineEmits(["load", "error"]);
  151. // 透传样式类型
  152. type PassThrough = {
  153. className?: string;
  154. inner?: PassThroughProps;
  155. error?: ClIconProps;
  156. loading?: PassThroughProps;
  157. };
  158. // 解析透传样式
  159. const pt = computed(() => parsePt<PassThrough>(props.pt));
  160. // 加载状态
  161. const isLoading = ref(true);
  162. // 加载失败状态
  163. const isError = ref(false);
  164. // 图片加载成功
  165. function onLoad(e: UniEvent) {
  166. isLoading.value = false;
  167. isError.value = false;
  168. emit("load", e);
  169. }
  170. // 图片加载失败
  171. function onError(e: UniEvent) {
  172. isLoading.value = false;
  173. isError.value = true;
  174. emit("error", e);
  175. }
  176. // 图片点击
  177. function onTap() {
  178. if (props.preview) {
  179. const urls = isEmpty(props.previewList) ? [props.src] : props.previewList;
  180. uni.previewImage({
  181. urls,
  182. current: props.src
  183. });
  184. }
  185. }
  186. </script>
  187. <style lang="scss" scoped>
  188. .cl-image {
  189. @apply relative flex flex-row items-center justify-center;
  190. &__inner {
  191. @apply w-full h-full rounded-xl;
  192. }
  193. &__loading,
  194. &__error {
  195. @apply absolute h-full w-full bg-surface-200 rounded-xl;
  196. @apply flex flex-col items-center justify-center;
  197. &.is-dark {
  198. @apply bg-surface-700;
  199. }
  200. }
  201. }
  202. </style>