Files
lilishop-ui/manager/src/components/editor/upload-image.vue
pikachu1995@126.com 6be934d00b feat(member): 优化等级权益管理与买家展示
- 优化客户权益配置、编辑表单及独立发放明细页面
- 完善客户等级图标、背景配置提示与 PC 权益展示
- 复用图片选择器并补齐素材元数据回传
2026-11-05 21:27:37 +08:00

315 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="wrapper">
<el-button @click="handleClickUploadImage">上传图片</el-button>
<el-dialog v-model="show" width="850px" title="上传图片" append-to-body :z-index="3500">
<div class="import-oss" @click="importOSS">从资源库中导入</div>
<div class="upload-images-wrap">
<vuedraggable
v-model="images"
:animation="200"
:item-key="draggableItemKey"
class="upload-images-draggable"
>
<template #item="{ element, index }">
<div class="upload-list">
<img alt="image" :src="element.url" />
<div class="upload-list-cover">
<div>
<el-icon class="action-icon" :size="30" @click="handleView(element.url)">
<ZoomIn />
</el-icon>
<el-icon
class="action-icon"
:size="30"
@click="handleRemoveGoodsPicture(index)"
>
<Delete />
</el-icon>
</div>
</div>
</div>
</template>
</vuedraggable>
<div class="upload-box">
<el-upload
ref="uploadRef"
:action="uploadFileUrl"
:headers="accessToken"
:show-file-list="false"
accept=".jpg,.jpeg,.png"
drag
multiple
:before-upload="handleBeforeUpload"
:on-success="handleSuccessGoodsPicture"
:on-error="handleUploadError"
>
<div class="upload-trigger">
<el-icon :size="20"><Plus /></el-icon>
</div>
</el-upload>
</div>
</div>
<template #footer>
<el-button @click="show = false">取消</el-button>
<el-button type="primary" @click="callback">确定</el-button>
</template>
</el-dialog>
<el-dialog
v-model="showOssManager"
title="选择图片"
width="1000px"
append-to-body
:z-index="3600"
destroy-on-close
@closed="resetOssSelection"
>
<OssManage
ref="ossManageRef"
:is-component="true"
:initialize="showOssManager"
:hide-select-footer="true"
@selected="handleOssSelected"
/>
<template #footer>
<el-button @click="cancelOssImport">取消</el-button>
<el-button type="primary" @click="confirmUrls">确定</el-button>
</template>
</el-dialog>
<el-dialog v-model="viewImage" title="图片预览" width="520px" append-to-body :z-index="3700">
<img :src="previewUrl" alt="预览" style="width: 100%; display: block; margin: 0 auto" />
<template #footer>
<el-button @click="viewImage = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { nextTick, onMounted, ref } from "vue";
import { Delete, Plus, ZoomIn } from "@element-plus/icons-vue";
import vuedraggable from "vuedraggable";
import { uploadFile } from "@/libs/axios";
import { getStore } from "@/libs/storage";
import { Message } from "@/utils/message";
import OssManage from "@/views/sys/oss-manage/ossManage.vue";
defineOptions({ name: "upload-image" });
const props = defineProps({ useImagePicker: { type: Boolean, default: false } });
const emit = defineEmits(["callback"]);
const show = ref(false);
const uploadFileUrl = uploadFile;
const accessToken = ref({});
const showOssManager = ref(false);
const images = ref([]);
const selectedImage = ref([]);
const viewImage = ref(false);
const previewUrl = ref("");
// 模板 ref:upload/ossManage 原名与顶层变量无冲突,但统一改名避免与后续变量互相覆盖
const uploadRef = ref(null);
const ossManageRef = ref(null);
onMounted(() => {
accessToken.value = {
accessToken: getStore("accessToken"),
};
});
function draggableItemKey(item) {
return item?.url || item;
}
function handleClickUploadImage() {
if (props.useImagePicker) {
importOSS();
return;
}
show.value = true;
}
function handleView(url) {
previewUrl.value = url;
viewImage.value = true;
}
function callback() {
const formatImages = images.value.map((item) => item.url);
emit("callback", formatImages);
show.value = false;
}
function handleRemoveGoodsPicture(__index) {
images.value.splice(__index, 1);
}
function handleBeforeUpload(file) {
const okType = ["image/jpeg", "image/png", "image/jpg"].includes(file.type);
if (!okType) {
Message.warning("文件 " + file.name + " 的格式不正确,请选择 jpg/jpeg/png");
return false;
}
if (file.size / 1024 / 1024 > 10) {
Message.warning("图片大小不能超过10MB");
return false;
}
return true;
}
function handleSuccessGoodsPicture(res) {
const url = res?.result ?? res?.data?.result;
if (url) {
images.value.push({ url });
} else {
Message.error(res?.message || "上传失败");
}
}
function handleUploadError(err) {
Message.error(err?.message || String(err));
}
function confirmUrls() {
if (props.useImagePicker) {
const urls = [...new Set(selectedImage.value.map(parseOssSelectionUrl).filter(Boolean))];
if (!urls.length) {
Message.warning("请选择图片");
return;
}
emit("callback", urls);
} else {
applySelectedImages();
}
showOssManager.value = false;
}
function cancelOssImport() {
selectedImage.value = [];
showOssManager.value = false;
}
function resetOssSelection() {
selectedImage.value = [];
}
function handleOssSelected(list) {
selectedImage.value = Array.isArray(list) ? list : [];
}
function parseOssSelectionUrl(item) {
if (!item) {
return "";
}
if (typeof item === "string") {
const index = item.indexOf(",");
return index >= 0 ? item.slice(index + 1) : item;
}
return item.url || "";
}
function applySelectedImages() {
(selectedImage.value || []).forEach((item) => {
const url = parseOssSelectionUrl(item);
if (url) {
images.value.push({ url });
}
});
}
function importOSS() {
selectedImage.value = [];
showOssManager.value = true;
nextTick(() => {
if (ossManageRef.value) {
ossManageRef.value.selectImage = true;
}
});
}
</script>
<style scoped lang="scss">
.import-oss {
margin-bottom: 10px;
text-align: right;
color: $theme_color;
cursor: pointer;
}
.wrapper {
margin: 10px 0;
}
$upload-item-size: 150px;
.upload-images-wrap {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
}
.upload-images-draggable {
display: flex;
flex-wrap: wrap;
}
.upload-list {
width: $upload-item-size;
height: $upload-item-size;
text-align: center;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: inline-block;
background: #fff;
position: relative;
margin: 0 4px 4px 0;
overflow: hidden;
flex-shrink: 0;
}
.upload-box {
width: $upload-item-size;
height: $upload-item-size;
margin: 0 4px 4px 0;
display: inline-block;
flex-shrink: 0;
:deep(.el-upload) {
width: $upload-item-size;
height: $upload-item-size;
display: block;
}
:deep(.el-upload-dragger) {
width: $upload-item-size;
height: $upload-item-size;
min-height: $upload-item-size;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
box-sizing: border-box;
}
}
.upload-trigger {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.upload-list img {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-list-cover {
display: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
justify-content: space-between;
align-items: center;
flex-direction: column;
}
.upload-list:hover .upload-list-cover {
display: flex;
}
.upload-list-cover div {
margin-top: 50px;
width: 100%;
display: flex;
justify-content: center;
gap: 8px;
}
.action-icon {
color: #fff;
cursor: pointer;
}
</style>