Browse Source

素材管理、banner管理 上传图片

urbanu 1 tháng trước cách đây
mục cha
commit
6db97b0507

+ 96 - 3
src/views/daytask/content/banner.vue

@@ -60,8 +60,22 @@
       <el-form-item label="标题" prop="title">
         <el-input v-model="formData.title" placeholder="请输入标题" />
       </el-form-item>
-      <el-form-item label="图片URL" prop="image">
-        <el-input v-model="formData.image" placeholder="请输入图片URL" />
+      <el-form-item label="Banner图片" prop="image">
+        <div class="upload-wrapper">
+          <el-upload
+            class="banner-uploader"
+            action="#"
+            :show-file-list="false"
+            :http-request="handleBannerUpload"
+            :before-upload="beforeBannerUpload"
+            accept="image/*"
+          >
+            <el-image v-if="formData.image" :src="formData.image" class="banner-preview" fit="cover" />
+            <el-icon v-else class="banner-uploader-icon"><Plus /></el-icon>
+          </el-upload>
+          <el-button v-if="formData.image" type="danger" size="small" @click="formData.image = ''">删除</el-button>
+        </div>
+        <div class="upload-tip">建议尺寸: 750 x 350 px,支持 jpg/png/gif 格式</div>
       </el-form-item>
       <el-form-item label="位置" prop="position">
         <el-select v-model="formData.position" placeholder="请选择位置" style="width: 100%">
@@ -101,10 +115,11 @@
 <script setup>
 import { ref, reactive, onMounted } from "vue";
 import dayjs from "dayjs";
-import { Refresh } from "@element-plus/icons-vue";
+import { Refresh, Plus } from "@element-plus/icons-vue";
 import { ElNotification, ElMessageBox } from "element-plus";
 import Pagination from "@/components/Pangination/Pagination.vue";
 import { getBannerList, createBanner, updateBanner, deleteBanner } from "@/api/modules/daytask.js";
+import { uploadImg } from "@/api/modules/upload.js";
 
 const tableData = ref([]);
 const pageable = reactive({ pageNum: 1, pageSize: 30, total: 0 });
@@ -255,4 +270,82 @@ const handleDelete = async (row) => {
     }
   }
 };
+
+// ==================== 图片上传 ====================
+const beforeBannerUpload = (file) => {
+  const isImage = file.type.startsWith("image/");
+  const isLt5M = file.size / 1024 / 1024 < 5;
+
+  if (!isImage) {
+    ElNotification.warning("只能上传图片文件");
+    return false;
+  }
+  if (!isLt5M) {
+    ElNotification.warning("图片大小不能超过 5MB");
+    return false;
+  }
+  return true;
+};
+
+const handleBannerUpload = async (options) => {
+  const uploadFormData = new FormData();
+  uploadFormData.append("file", options.file);
+
+  try {
+    const res = await uploadImg(uploadFormData);
+    if (res.code === 200) {
+      formData.value.image = res.data.path;
+      ElNotification.success("图片上传成功");
+    } else {
+      ElNotification.error(res.msg || "图片上传失败");
+    }
+  } catch (error) {
+    console.error("上传失败", error);
+    ElNotification.error("图片上传失败");
+  }
+};
 </script>
+
+<style scoped>
+.upload-wrapper {
+  display: flex;
+  align-items: flex-end;
+  gap: 10px;
+}
+
+.banner-uploader {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  overflow: hidden;
+  transition: border-color 0.3s;
+}
+
+.banner-uploader:hover {
+  border-color: #409eff;
+}
+
+.banner-uploader :deep(.el-upload) {
+  width: 200px;
+  height: 100px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.banner-preview {
+  width: 200px;
+  height: 100px;
+}
+
+.banner-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+}
+
+.upload-tip {
+  font-size: 12px;
+  color: #909399;
+  margin-top: 5px;
+}
+</style>

+ 176 - 1
src/views/daytask/material/materialList.vue

@@ -29,6 +29,7 @@
       <div class="table-header">
         <div class="header-button-lf">
           <el-button type="primary" icon="Plus" @click="openDialog()">新增素材</el-button>
+          <el-button type="success" icon="Upload" @click="openUploadDialog()">上传文件</el-button>
         </div>
         <div class="header-button-ri">
           <el-button :icon="Refresh" circle @click="refresh" />
@@ -130,15 +131,64 @@
       <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
     </template>
   </el-dialog>
+
+  <!-- 上传弹窗 -->
+  <el-dialog v-model="uploadDialogVisible" title="上传文件" width="600px" center destroy-on-close>
+    <el-form ref="uploadFormRef" :model="uploadForm" :rules="uploadFormRules" label-width="100px">
+      <el-form-item label="分组" prop="group_id">
+        <el-select v-model="uploadForm.group_id" placeholder="请选择分组" style="width: 100%">
+          <el-option v-for="item in groupOptions" :key="item.id" :label="item.name" :value="item.id" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="上传文件" prop="file">
+        <el-upload
+          ref="uploadRef"
+          class="upload-area"
+          drag
+          action="#"
+          :auto-upload="false"
+          :on-change="handleFileChange"
+          :on-remove="handleFileRemove"
+          :limit="1"
+          :file-list="fileList"
+          accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
+        >
+          <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+          <div class="el-upload__text">
+            拖拽文件到此处 或 <em>点击上传</em>
+          </div>
+          <template #tip>
+            <div class="el-upload__tip">
+              支持图片、视频、音频、文档等格式,单个文件不超过50MB
+            </div>
+          </template>
+        </el-upload>
+      </el-form-item>
+      <el-form-item label="预览" v-if="uploadPreviewUrl">
+        <el-image
+          v-if="uploadForm.type === 'image'"
+          :src="uploadPreviewUrl"
+          style="max-width: 200px; max-height: 200px"
+          fit="contain"
+        />
+        <span v-else>{{ uploadForm.name }}</span>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="uploadDialogVisible = false">取消</el-button>
+      <el-button type="primary" :loading="uploadLoading" @click="handleUpload">上传并保存</el-button>
+    </template>
+  </el-dialog>
 </template>
 
 <script setup>
 import { ref, reactive, onMounted } from "vue";
 import dayjs from "dayjs";
-import { Refresh } from "@element-plus/icons-vue";
+import { Refresh, UploadFilled } from "@element-plus/icons-vue";
 import { ElNotification, ElMessageBox } from "element-plus";
 import Pagination from "@/components/Pangination/Pagination.vue";
 import { getMaterialList, createMaterial, updateMaterial, deleteMaterial, getMaterialGroupList } from "@/api/modules/daytask.js";
+import { uploadImg } from "@/api/modules/upload.js";
 
 const searchForm = ref({ group_id: null, name: null, type: null });
 const tableData = ref([]);
@@ -323,6 +373,119 @@ const handleDelete = async (row) => {
     }
   }
 };
+
+// ==================== 上传相关 ====================
+const uploadDialogVisible = ref(false);
+const uploadLoading = ref(false);
+const uploadRef = ref(null);
+const uploadFormRef = ref(null);
+const fileList = ref([]);
+const uploadPreviewUrl = ref("");
+const uploadForm = ref({
+  group_id: null,
+  name: "",
+  type: "image",
+  file: null,
+  size: 0
+});
+const uploadFormRules = ref({
+  group_id: [{ required: true, message: "请选择分组", trigger: "change" }]
+});
+
+const openUploadDialog = () => {
+  uploadForm.value = {
+    group_id: null,
+    name: "",
+    type: "image",
+    file: null,
+    size: 0
+  };
+  fileList.value = [];
+  uploadPreviewUrl.value = "";
+  uploadDialogVisible.value = true;
+};
+
+const getFileType = (file) => {
+  const mimeType = file.type || "";
+  if (mimeType.startsWith("image/")) return "image";
+  if (mimeType.startsWith("video/")) return "video";
+  if (mimeType.startsWith("audio/")) return "audio";
+  return "document";
+};
+
+const handleFileChange = (file) => {
+  uploadForm.value.file = file.raw;
+  uploadForm.value.name = file.name;
+  uploadForm.value.size = file.size;
+  uploadForm.value.type = getFileType(file.raw);
+
+  // 如果是图片,生成预览
+  if (uploadForm.value.type === "image") {
+    uploadPreviewUrl.value = URL.createObjectURL(file.raw);
+  } else {
+    uploadPreviewUrl.value = "";
+  }
+};
+
+const handleFileRemove = () => {
+  uploadForm.value.file = null;
+  uploadForm.value.name = "";
+  uploadForm.value.size = 0;
+  uploadPreviewUrl.value = "";
+};
+
+const handleUpload = async () => {
+  if (!uploadFormRef.value) return;
+
+  await uploadFormRef.value.validate(async (valid) => {
+    if (!valid) return;
+
+    if (!uploadForm.value.file) {
+      ElNotification.warning("请选择要上传的文件");
+      return;
+    }
+
+    uploadLoading.value = true;
+    try {
+      // 1. 上传文件到 MinIO
+      const formData = new FormData();
+      formData.append("file", uploadForm.value.file);
+
+      const uploadRes = await uploadImg(formData);
+      if (uploadRes.code !== 200) {
+        ElNotification.error(uploadRes.msg || "文件上传失败");
+        return;
+      }
+
+      const fileUrl = uploadRes.data.path;
+
+      // 2. 创建素材记录
+      const materialData = {
+        group_id: uploadForm.value.group_id,
+        name: uploadForm.value.name,
+        type: uploadForm.value.type,
+        url: fileUrl,
+        size: uploadForm.value.size,
+        status: 1,
+        sort: 0
+      };
+
+      const createRes = await createMaterial(materialData);
+      if (createRes.code === 200) {
+        ElNotification.success("上传成功");
+        uploadDialogVisible.value = false;
+        getList();
+      } else {
+        ElNotification.error(createRes.msg || "保存素材失败");
+      }
+    } catch (error) {
+      console.error("上传失败", error);
+      ElNotification.error("上传失败");
+    } finally {
+      uploadLoading.value = false;
+    }
+  });
+};
 </script>
 
 <style scoped>
@@ -330,4 +493,16 @@ const handleDelete = async (row) => {
   display: inline-block;
   vertical-align: middle;
 }
+
+.upload-area {
+  width: 100%;
+}
+
+.upload-area :deep(.el-upload) {
+  width: 100%;
+}
+
+.upload-area :deep(.el-upload-dragger) {
+  width: 100%;
+}
 </style>