album-list.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <a-list :grid="{ gutter: 20, column: 3 }" :data-source="current">
  3. <template
  4. #renderItem="{
  5. item,
  6. index
  7. }: {
  8. item: string | typeof addMarked,
  9. index: number
  10. }"
  11. >
  12. <a-list-item class="scene-item">
  13. <div v-if="item === addMarked" class="add-album">
  14. <a-upload
  15. v-model:file-list="albumFile"
  16. name="file"
  17. accept=".png,.jpg,.jpeg"
  18. :show-upload-list="false"
  19. action="https://v4-test.4dkankan.com/takelook/upload/file"
  20. :multiple="true"
  21. :before-upload="handleBeforeUpload"
  22. @change="handleABlumChange"
  23. >
  24. <div class="add-item-icon">
  25. <a-button shape="circle" class="button" type="primary">
  26. <plus-outlined class="add-room-icon" />
  27. </a-button>
  28. </div>
  29. </a-upload>
  30. </div>
  31. <div v-else class="scene-sign">
  32. <a-image v-if="item" :src="item" />
  33. <span class="delete-scene" @click="deleteAblum(index)">
  34. <close-outlined class="delete-scene-icon" />
  35. </span>
  36. </div>
  37. </a-list-item>
  38. </template>
  39. </a-list>
  40. </template>
  41. <script lang="ts" setup>
  42. import { computed, ref, watch, unref } from 'vue'
  43. import { Modal, message } from 'ant-design-vue'
  44. import type { UploadChangeParam } from 'ant-design-vue'
  45. import { useI18n } from '@/hook/useI18n'
  46. import { watchEffect } from 'vue'
  47. export interface FileItem {
  48. uid: string
  49. response:
  50. | {
  51. ok?: number
  52. data: string
  53. }
  54. | undefined
  55. name?: string
  56. status?: string
  57. url?: string
  58. type?: string
  59. size?: number
  60. originFileObj?: any
  61. }
  62. const { t } = useI18n()
  63. defineOptions<{ name: 'RoomSceneList' }>()
  64. const props = defineProps<{ data: string[] }>()
  65. const addMarked = Symbol('add-album')
  66. const albumFile = ref<any[]>([])
  67. const albumFileExist = ref<any[]>([])
  68. const current = computed(() => [addMarked, ...albumFileExist.value])
  69. const deleteAblum = (index: number) => {
  70. console.log('index', index)
  71. if (index - 1 > -1) {
  72. albumFileExist.value.splice(index - 1, 1)
  73. }
  74. const syncData = albumFileExist.value.length ? albumFileExist.value : []
  75. emit('syncList', syncData)
  76. }
  77. const emit = defineEmits(['syncList'])
  78. watchEffect(() => {
  79. if (props.data?.length) {
  80. albumFileExist.value = props.data
  81. }
  82. })
  83. const handleABlumChange = (info: UploadChangeParam) => {
  84. if (info.file.status === 'done') {
  85. const { code, data } = info.file.response
  86. if (code === 0) {
  87. albumFileExist.value.push(data)
  88. }
  89. const syncData = albumFileExist.value.length ? albumFileExist.value : []
  90. emit('syncList', syncData)
  91. } else if (info.file.status === 'error') {
  92. message.error(`${info.file.name} file upload failed.`)
  93. }
  94. }
  95. const handleBeforeUpload = (file: any) => {
  96. const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
  97. if (!isJpgOrPng) {
  98. message.error('You can only upload JPG file!')
  99. }
  100. const isLt5M = file.size / 1024 / 1024 < 5
  101. if (!isLt5M) {
  102. message.error('Image must smaller than 5MB!')
  103. }
  104. return isJpgOrPng && isLt5M
  105. }
  106. </script>
  107. <style lang="scss" scoped>
  108. .add-album {
  109. border: 1px solid #ebedf0;
  110. border-radius: 4px;
  111. height: 100%;
  112. width: 100%;
  113. cursor: pointer;
  114. display: flex;
  115. align-items: center;
  116. justify-content: center;
  117. .button {
  118. width: 40px;
  119. height: 40px;
  120. line-height: 34px;
  121. background: linear-gradient(144deg, #00aefb 0%, #0076f6 100%);
  122. }
  123. .add-room-icon {
  124. font-size: 18px;
  125. }
  126. }
  127. .scene-sign {
  128. height: 100%;
  129. border-radius: 4px;
  130. overflow: hidden;
  131. position: relative;
  132. img {
  133. width: 100%;
  134. height: 100%;
  135. display: block;
  136. object-fit: cover;
  137. }
  138. .title {
  139. position: absolute;
  140. bottom: 0;
  141. left: 0;
  142. right: 0;
  143. height: 24px;
  144. background: rgba(0, 0, 0, 0.5);
  145. padding: 5px;
  146. font-size: 12px;
  147. color: #fff;
  148. margin: 0;
  149. overflow: hidden;
  150. text-overflow: ellipsis;
  151. white-space: nowrap;
  152. }
  153. .delete-scene {
  154. z-index: 2;
  155. position: absolute;
  156. right: 0;
  157. top: 0;
  158. width: 52px;
  159. height: 52px;
  160. background-color: rgba(0, 0, 0, 0.5);
  161. color: #fa5555;
  162. font-size: 14px;
  163. border-radius: 50%;
  164. display: flex;
  165. align-items: flex-end;
  166. transform: translate(100%, -100%);
  167. transition: all 0.3s ease;
  168. opacity: 0;
  169. cursor: pointer;
  170. .delete-scene-icon {
  171. padding: 10px;
  172. }
  173. }
  174. &:hover .delete-scene {
  175. transform: translate(50%, -50%);
  176. opacity: 1;
  177. }
  178. .status-cover {
  179. z-index: 1;
  180. position: absolute;
  181. left: 0;
  182. top: 0;
  183. right: 0;
  184. bottom: 0;
  185. background-color: rgba(0, 0, 0, 0.5);
  186. display: flex;
  187. align-items: center;
  188. justify-content: center;
  189. p {
  190. color: #fff;
  191. }
  192. }
  193. }
  194. </style>
  195. <style>
  196. .scene-item {
  197. height: 120px;
  198. }
  199. </style>