albumUploadPopup.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <template>
  2. <popup
  3. v-model:show="showValue"
  4. round
  5. :close-on-click-overlay="false"
  6. :z-index="20000"
  7. class="album-upload-popup"
  8. @closed="handleClosed"
  9. >
  10. <div class="album-upload-popup__content">
  11. <div class="album-upload-popup__title">上传照片</div>
  12. <uploader
  13. class="album-upload-popup__uploader"
  14. v-model="fileList"
  15. multiple
  16. :max-count="maxCount"
  17. :after-read="afterRead"
  18. @delete="onDelete"
  19. >
  20. <div class="album-upload-popup__add">
  21. <el-icon size="22" color="rgba(0, 0, 0, 0.6)"><Plus /></el-icon>
  22. <!-- <span class="album-upload-popup__add-icon">+</span> -->
  23. </div>
  24. </uploader>
  25. <div class="album-upload-popup__date">
  26. <div class="album-upload-popup__date-label">上传日期</div>
  27. <el-date-picker
  28. v-model="uploadDate"
  29. type="date"
  30. format="YYYY-MM-DD"
  31. value-format="YYYY-MM-DD"
  32. placeholder="请选择上传日期"
  33. :editable="false"
  34. :disabled-date="disableAfterToday"
  35. style="width: 100%"
  36. size="large"
  37. />
  38. </div>
  39. <el-input
  40. v-model="description"
  41. class="album-upload-popup__desc"
  42. type="textarea"
  43. :rows="4"
  44. resize="none"
  45. placeholder="为您的照片添加描述吧~"
  46. maxlength="200"
  47. />
  48. <div class="album-upload-popup__actions">
  49. <div class="album-upload-popup__btn album-upload-popup__btn--cancel" @click="handleCancel">
  50. 取消
  51. </div>
  52. <div
  53. class="album-upload-popup__btn album-upload-popup__btn--confirm"
  54. :class="{ disabled: confirming }"
  55. @click="handleConfirm"
  56. >
  57. {{ confirming ? "上传中..." : "确认上传" }}
  58. </div>
  59. </div>
  60. </div>
  61. </popup>
  62. </template>
  63. <script setup>
  64. import { ref, watch } from "vue";
  65. import { Popup, Uploader } from "vant";
  66. import { ElMessage } from "element-plus";
  67. import { getFileExt } from "@/utils/util";
  68. import UploadFile from "@/utils/upliadFile";
  69. import { useStore } from "vuex";
  70. import "vant/lib/uploader/style";
  71. const props = defineProps({
  72. show: {
  73. type: Boolean,
  74. default: false,
  75. },
  76. maxCount: {
  77. type: Number,
  78. default: 9,
  79. },
  80. });
  81. const emit = defineEmits(["update:show", "confirm", "cancel"]);
  82. const store = useStore();
  83. const miniUserId = store.state.home.miniUserId || localStorage.getItem("MINI_USER_ID");
  84. const uploadFileObj = new UploadFile();
  85. const showValue = ref(false);
  86. const fileList = ref([]);
  87. const uploadedKeys = ref([]);
  88. const description = ref("");
  89. const uploadDate = ref("");
  90. const confirming = ref(false);
  91. function formatDate(date = new Date()) {
  92. const year = date.getFullYear();
  93. const month = String(date.getMonth() + 1).padStart(2, "0");
  94. const day = String(date.getDate()).padStart(2, "0");
  95. return `${year}-${month}-${day}`;
  96. }
  97. const disableAfterToday = (date) => {
  98. const today = new Date();
  99. today.setHours(0, 0, 0, 0);
  100. return date.getTime() > today.getTime();
  101. };
  102. watch(
  103. () => props.show,
  104. (val) => {
  105. showValue.value = val;
  106. if (val) {
  107. resetForm();
  108. }
  109. },
  110. { immediate: true },
  111. );
  112. watch(showValue, (val) => {
  113. emit("update:show", val);
  114. });
  115. function resetForm() {
  116. fileList.value = [];
  117. uploadedKeys.value = [];
  118. description.value = "";
  119. uploadDate.value = formatDate();
  120. confirming.value = false;
  121. }
  122. const afterRead = (file) => {
  123. const files = Array.isArray(file) ? file : [file];
  124. files.forEach((item) => {
  125. const fileVal = item.file;
  126. if (!fileVal) return;
  127. item.status = "uploading";
  128. item.message = "上传中...";
  129. const ext = getFileExt(fileVal.name);
  130. const key = `birdseye-look-mini/${miniUserId}/${Date.now()}_${Math.random()
  131. .toString(36)
  132. .slice(2, 8)}.${ext}`;
  133. uploadFileObj
  134. .put(key, fileVal)
  135. .then((resFilename) => {
  136. item.status = "done";
  137. item.message = "";
  138. item.resFilename = resFilename || key;
  139. uploadedKeys.value.push(item.resFilename);
  140. })
  141. .catch(() => {
  142. item.status = "failed";
  143. item.message = "上传失败";
  144. ElMessage.error("图片上传失败,请稍后再试!");
  145. });
  146. });
  147. };
  148. const onDelete = (file) => {
  149. const key = file?.resFilename;
  150. if (!key) return;
  151. uploadedKeys.value = uploadedKeys.value.filter((item) => item !== key);
  152. };
  153. const handleCancel = () => {
  154. showValue.value = false;
  155. emit("cancel");
  156. };
  157. const handleConfirm = () => {
  158. if (confirming.value) return;
  159. const uploading = fileList.value.some((item) => item.status === "uploading");
  160. if (uploading) {
  161. ElMessage.warning("图片上传中,请稍候");
  162. return;
  163. }
  164. const failed = fileList.value.some((item) => item.status === "failed");
  165. if (failed) {
  166. ElMessage.warning("存在上传失败的图片,请删除后重试");
  167. return;
  168. }
  169. if (!uploadedKeys.value.length) {
  170. ElMessage.warning("请先上传照片");
  171. return;
  172. }
  173. confirming.value = true;
  174. emit("confirm", {
  175. images: [...uploadedKeys.value],
  176. description: description.value.trim(),
  177. date: uploadDate.value,
  178. });
  179. confirming.value = false;
  180. showValue.value = false;
  181. };
  182. const handleClosed = () => {
  183. resetForm();
  184. };
  185. </script>
  186. <style scoped lang="scss">
  187. .album-upload-popup {
  188. width: 90%;
  189. max-width: 420px;
  190. border-radius: 12px;
  191. background: #fff;
  192. overflow: hidden;
  193. &__content {
  194. padding: 20px 16px 16px;
  195. box-sizing: border-box;
  196. }
  197. &__title {
  198. margin-bottom: 14px;
  199. font-size: 16px;
  200. line-height: 22px;
  201. font-weight: 500;
  202. color: #111;
  203. }
  204. &__uploader {
  205. width: 100%;
  206. margin-bottom: 12px;
  207. :deep(.van-uploader__wrapper) {
  208. gap: 8px;
  209. }
  210. :deep(.van-uploader__preview),
  211. :deep(.van-uploader__upload),
  212. :deep(.van-uploader__preview-image) {
  213. width: calc((100vw * 0.9 - 32px - 24px) / 4);
  214. height: calc((100vw * 0.9 - 32px - 24px) / 4);
  215. margin: 0;
  216. border-radius: 6px;
  217. overflow: hidden;
  218. }
  219. :deep(.van-uploader__upload) {
  220. background: transparent;
  221. }
  222. :deep(.van-uploader__preview-delete) {
  223. top: 0;
  224. right: 0;
  225. }
  226. }
  227. :deep(.van-uploader__input-wrapper) {
  228. width: calc(25% - 8px);
  229. min-height: 66px;
  230. }
  231. &__add {
  232. width: 100%;
  233. height: 100%;
  234. display: flex;
  235. align-items: center;
  236. justify-content: center;
  237. background: #F6F6F6;
  238. border: 1px dashed #DDDDDD;
  239. border-radius: 6px;
  240. box-sizing: border-box;
  241. }
  242. &__add-icon {
  243. font-size: 28px;
  244. line-height: 1;
  245. color: #999;
  246. font-weight: 300;
  247. }
  248. &__date {
  249. margin-bottom: 12px;
  250. &-label {
  251. margin-bottom: 8px;
  252. font-size: 14px;
  253. line-height: 20px;
  254. color: #333;
  255. }
  256. }
  257. &__desc {
  258. border: 1px solid rgba(220, 220, 220, 1);
  259. margin-bottom: 16px;
  260. border-radius: 3px;
  261. :deep(.el-textarea__inner) {
  262. min-height: 96px;
  263. padding: 10px 12px;
  264. border-radius: 6px;
  265. border-color: #e5e5e5;
  266. box-shadow: none;
  267. font-size: 14px;
  268. line-height: 20px;
  269. color: #333;
  270. &::placeholder {
  271. color: rgba(0, 0, 0, 0.3);
  272. }
  273. }
  274. }
  275. &__actions {
  276. display: flex;
  277. gap: 10px;
  278. }
  279. &__btn {
  280. height: 40px;
  281. line-height: 40px;
  282. border-radius: 4px;
  283. text-align: center;
  284. font-size: 15px;
  285. box-sizing: border-box;
  286. cursor: pointer;
  287. &--cancel {
  288. width: 96px;
  289. flex-shrink: 0;
  290. color: #666;
  291. background: #fff;
  292. border: 1px solid #dcdfe6;
  293. }
  294. &--confirm {
  295. flex: 1;
  296. color: #fff;
  297. background: #2199f8;
  298. &.disabled {
  299. opacity: 0.6;
  300. pointer-events: none;
  301. }
  302. }
  303. }
  304. }
  305. </style>