|
|
@@ -15,20 +15,31 @@
|
|
|
<!-- 团购图片上传 prop="imageValueStr" 本地服务测不了上传图片 先去掉必填-->
|
|
|
<el-form-item label="图片">
|
|
|
<el-upload
|
|
|
+ ref="uploadRef"
|
|
|
v-model:file-list="storeInfoModel.imageValueStr"
|
|
|
- :action="uploadUrl"
|
|
|
list-type="picture-card"
|
|
|
:accept="'.jpg,.png'"
|
|
|
- :limit="9"
|
|
|
+ :limit="uploadMaxCount"
|
|
|
+ :auto-upload="false"
|
|
|
+ :disabled="hasUnuploadedImages"
|
|
|
+ multiple
|
|
|
+ :on-change="handleUploadChange"
|
|
|
+ :on-exceed="handleUploadExceed"
|
|
|
:on-preview="handlePictureCardPreview"
|
|
|
:before-remove="handleBeforeRemove"
|
|
|
:on-remove="handleRemove"
|
|
|
- :on-success="handleSuccess"
|
|
|
:show-file-list="true"
|
|
|
>
|
|
|
- <el-icon>
|
|
|
- <Plus />
|
|
|
- </el-icon>
|
|
|
+ <template #trigger>
|
|
|
+ <div
|
|
|
+ v-if="(storeInfoModel.imageId?.length || 0) < uploadMaxCount"
|
|
|
+ class="upload-trigger-card el-upload--picture-card"
|
|
|
+ >
|
|
|
+ <el-icon>
|
|
|
+ <Plus />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</el-upload>
|
|
|
</el-form-item>
|
|
|
<!-- 团购名称 -->
|
|
|
@@ -387,8 +398,8 @@
|
|
|
</el-form>
|
|
|
<!-- 底部按钮区域 -->
|
|
|
<div class="button-container">
|
|
|
- <el-button @click="handleSubmit('cg')"> 存草稿 </el-button>
|
|
|
- <el-button type="primary" @click="handleSubmit()"> 确定 </el-button>
|
|
|
+ <el-button @click="handleSubmit('cg')" :disabled="hasUnuploadedImages"> 存草稿 </el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmit()" :disabled="hasUnuploadedImages"> 确定 </el-button>
|
|
|
</div>
|
|
|
<!-- 图片预览 -->
|
|
|
<el-image-viewer
|
|
|
@@ -452,18 +463,16 @@
|
|
|
import { ref, reactive, onMounted, watch, nextTick, computed } from "vue";
|
|
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
|
import { Plus, Delete, ArrowDown, ArrowUp, Picture } from "@element-plus/icons-vue";
|
|
|
-import {
|
|
|
- saveDraft,
|
|
|
- getStoreDetail,
|
|
|
- getHolidayList,
|
|
|
- getUserByPhone,
|
|
|
- getMenuByStoreId,
|
|
|
- getThaliById,
|
|
|
- saveThali
|
|
|
-} from "@/api/modules/groupPackageManagement";
|
|
|
+import { saveDraft, getHolidayList, getMenuByStoreId, getThaliById, saveThali } from "@/api/modules/groupPackageManagement";
|
|
|
import { useRouter, useRoute } from "vue-router";
|
|
|
-import type { UploadProps, FormInstance } from "element-plus";
|
|
|
-import { localGet, localSet } from "@/utils";
|
|
|
+import type { UploadProps, FormInstance, UploadInstance, UploadFile } from "element-plus";
|
|
|
+import { localGet } from "@/utils";
|
|
|
+import {
|
|
|
+ validatePositiveInteger,
|
|
|
+ validateDateRangeArray,
|
|
|
+ validateConditionalRequired,
|
|
|
+ validateDateListArray
|
|
|
+} from "@/utils/eleValidate";
|
|
|
|
|
|
// ==================== 响应式数据定义 ====================
|
|
|
|
|
|
@@ -481,7 +490,23 @@ const type = ref<string>(""); // 页面类型:add-新增, edit-编辑
|
|
|
const id = ref<string>(""); // 页面ID参数
|
|
|
|
|
|
// 文件上传地址
|
|
|
-const uploadUrl = ref(`${import.meta.env.VITE_API_URL_PLATFORM}/file/uploadImg`);
|
|
|
+const uploadUrl = ref(`${import.meta.env.VITE_API_URL_STORE}/file/uploadImg`);
|
|
|
+
|
|
|
+const imgType = ref(16);
|
|
|
+const uploadMaxCount = 9;
|
|
|
+const uploadRef = ref<UploadInstance>();
|
|
|
+const uploading = ref(false);
|
|
|
+const pendingUploadFiles = ref<UploadFile[]>([]);
|
|
|
+const generateImgSort = (() => {
|
|
|
+ let seed = Date.now();
|
|
|
+ return () => {
|
|
|
+ seed += 1;
|
|
|
+ return seed;
|
|
|
+ };
|
|
|
+})();
|
|
|
+
|
|
|
+// ==================== 验证辅助函数 ====================
|
|
|
+// 验证函数已从 @/utils/eleValidate 导入
|
|
|
|
|
|
// ==================== 表单验证规则 ====================
|
|
|
const rules = reactive({
|
|
|
@@ -549,22 +574,7 @@ const rules = reactive({
|
|
|
inventoryNum: [
|
|
|
{ required: true, message: "请填写库存数量" },
|
|
|
{
|
|
|
- validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (!value || value.toString().trim() === "") {
|
|
|
- callback();
|
|
|
- return;
|
|
|
- }
|
|
|
- const num = Number(value);
|
|
|
- if (isNaN(num) || num <= 0) {
|
|
|
- callback(new Error("库存数量必须为正整数"));
|
|
|
- return;
|
|
|
- }
|
|
|
- if (!Number.isInteger(num)) {
|
|
|
- callback(new Error("库存数量必须为正整数"));
|
|
|
- return;
|
|
|
- }
|
|
|
- callback();
|
|
|
- },
|
|
|
+ validator: validatePositiveInteger("库存数量必须为正整数", { required: false }),
|
|
|
trigger: "blur"
|
|
|
}
|
|
|
],
|
|
|
@@ -578,17 +588,10 @@ const rules = reactive({
|
|
|
callback(new Error("请输入自定义限购数量"));
|
|
|
return;
|
|
|
}
|
|
|
- const num = Number(value);
|
|
|
- if (isNaN(num) || num <= 0) {
|
|
|
- callback(new Error("自定义限购数量必须为正整数"));
|
|
|
- return;
|
|
|
- }
|
|
|
- if (!Number.isInteger(num)) {
|
|
|
- callback(new Error("自定义限购数量必须为正整数"));
|
|
|
- return;
|
|
|
- }
|
|
|
+ validatePositiveInteger("自定义限购数量必须为正整数")(rule, value, callback);
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
}
|
|
|
- callback();
|
|
|
},
|
|
|
trigger: "blur"
|
|
|
}
|
|
|
@@ -654,17 +657,19 @@ const rules = reactive({
|
|
|
{
|
|
|
required: true,
|
|
|
validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (storeInfoModel.value.expirationDate === 0) {
|
|
|
+ if (storeInfoModel.value.effectiveDateType === 0) {
|
|
|
if (value === null || value === undefined || value === "") {
|
|
|
callback(new Error("请输入用户购买天数"));
|
|
|
return;
|
|
|
}
|
|
|
- if (value <= 0) {
|
|
|
- callback(new Error("天数必须大于0"));
|
|
|
- return;
|
|
|
- }
|
|
|
+ validatePositiveInteger("用户购买天数必须为正整数", { required: false, checkLeadingZero: false })(
|
|
|
+ rule,
|
|
|
+ value,
|
|
|
+ callback
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
}
|
|
|
- callback();
|
|
|
},
|
|
|
trigger: "blur"
|
|
|
}
|
|
|
@@ -678,26 +683,10 @@ const rules = reactive({
|
|
|
callback(new Error("请选择指定时间段"));
|
|
|
return;
|
|
|
}
|
|
|
- // 验证开始时间和结束时间不能早于当前时间
|
|
|
- const today = new Date();
|
|
|
- today.setHours(0, 0, 0, 0);
|
|
|
- const startDate = new Date(value[0]);
|
|
|
- const endDate = new Date(value[1]);
|
|
|
- if (startDate < today) {
|
|
|
- callback(new Error("开始时间不能早于当前时间"));
|
|
|
- return;
|
|
|
- }
|
|
|
- if (endDate < today) {
|
|
|
- callback(new Error("结束时间不能早于当前时间"));
|
|
|
- return;
|
|
|
- }
|
|
|
- // 验证开始时间必须早于结束时间
|
|
|
- if (startDate >= endDate) {
|
|
|
- callback(new Error("开始时间必须早于结束时间"));
|
|
|
- return;
|
|
|
- }
|
|
|
+ validateDateRangeArray("开始时间必须早于结束时间", true, "时间不能早于当前时间")(rule, value, callback);
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
}
|
|
|
- callback();
|
|
|
},
|
|
|
trigger: "change"
|
|
|
}
|
|
|
@@ -706,30 +695,14 @@ const rules = reactive({
|
|
|
unavailableWeekdays: [
|
|
|
{
|
|
|
required: true,
|
|
|
- validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (storeInfoModel.value.disableDateType === 1) {
|
|
|
- if (!value || value.length === 0) {
|
|
|
- callback(new Error("至少需要选择一个星期"));
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- callback();
|
|
|
- },
|
|
|
+ validator: validateConditionalRequired(() => storeInfoModel.value.disableDateType === 1, "至少需要选择一个星期"),
|
|
|
trigger: "change"
|
|
|
}
|
|
|
],
|
|
|
unavailableHolidays: [
|
|
|
{
|
|
|
required: true,
|
|
|
- validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (storeInfoModel.value.disableDateType === 1) {
|
|
|
- if (!value || value.length === 0) {
|
|
|
- callback(new Error("至少需要选择一个节日"));
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- callback();
|
|
|
- },
|
|
|
+ validator: validateConditionalRequired(() => storeInfoModel.value.disableDateType === 1, "至少需要选择一个节日"),
|
|
|
trigger: "change"
|
|
|
}
|
|
|
],
|
|
|
@@ -742,33 +715,15 @@ const rules = reactive({
|
|
|
callback(new Error("至少需要添加一个自定义不可用日期"));
|
|
|
return;
|
|
|
}
|
|
|
- const today = new Date();
|
|
|
- today.setHours(0, 0, 0, 0);
|
|
|
- // 验证每个日期项是否已填写
|
|
|
- for (let i = 0; i < storeInfoModel.value.disableDateList.length; i++) {
|
|
|
- if (!storeInfoModel.value.disableDateList[i] || storeInfoModel.value.disableDateList[i].length !== 2) {
|
|
|
- callback(new Error(`第${i + 1}个日期项未完整填写`));
|
|
|
- return;
|
|
|
- }
|
|
|
- // 验证开始时间和结束时间不能早于当前时间
|
|
|
- const startDate = new Date(storeInfoModel.value.disableDateList[i][0]);
|
|
|
- const endDate = new Date(storeInfoModel.value.disableDateList[i][1]);
|
|
|
- if (startDate < today) {
|
|
|
- callback(new Error(`第${i + 1}个日期项的开始时间不能早于当前时间`));
|
|
|
- return;
|
|
|
- }
|
|
|
- if (endDate < today) {
|
|
|
- callback(new Error(`第${i + 1}个日期项的结束时间不能早于当前时间`));
|
|
|
- return;
|
|
|
- }
|
|
|
- // 验证开始时间必须早于结束时间
|
|
|
- if (startDate >= endDate) {
|
|
|
- callback(new Error(`第${i + 1}个日期项的开始时间必须早于结束时间`));
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
+ validateDateListArray(
|
|
|
+ () => storeInfoModel.value.disableDateList,
|
|
|
+ "日期项未完整填写",
|
|
|
+ "开始时间必须早于结束时间",
|
|
|
+ true
|
|
|
+ )(rule, value, callback);
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
}
|
|
|
- callback();
|
|
|
},
|
|
|
trigger: "change"
|
|
|
}
|
|
|
@@ -778,22 +733,7 @@ const rules = reactive({
|
|
|
applicableNum: [
|
|
|
{ required: true, message: "请输入适用人数" },
|
|
|
{
|
|
|
- validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (!value || value.toString().trim() === "") {
|
|
|
- callback();
|
|
|
- return;
|
|
|
- }
|
|
|
- const num = Number(value);
|
|
|
- if (isNaN(num) || num <= 0) {
|
|
|
- callback(new Error("适用人数必须为正整数"));
|
|
|
- return;
|
|
|
- }
|
|
|
- if (!Number.isInteger(num)) {
|
|
|
- callback(new Error("适用人数必须为正整数"));
|
|
|
- return;
|
|
|
- }
|
|
|
- callback();
|
|
|
- },
|
|
|
+ validator: validatePositiveInteger("适用人数必须为正整数", { required: false }),
|
|
|
trigger: "blur"
|
|
|
}
|
|
|
],
|
|
|
@@ -939,6 +879,21 @@ const visibleGroups = computed(() => {
|
|
|
return lifeGroupBuyThalis.value.map((group, index) => ({ group, originalIndex: index }));
|
|
|
});
|
|
|
|
|
|
+// 计算属性:检查是否有未上传完成的图片
|
|
|
+const hasUnuploadedImages = computed(() => {
|
|
|
+ // 检查是否有正在上传的文件
|
|
|
+ if (uploading.value || pendingUploadFiles.value.length > 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // 检查文件列表中是否有状态为 "ready"(待上传)或 "uploading"(上传中)的图片
|
|
|
+ if (storeInfoModel.value.imageValueStr && storeInfoModel.value.imageValueStr.length > 0) {
|
|
|
+ return storeInfoModel.value.imageValueStr.some((file: any) => {
|
|
|
+ return file.status === "ready" || file.status === "uploading";
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+});
|
|
|
+
|
|
|
// ==================== 监听器 ====================
|
|
|
|
|
|
/**
|
|
|
@@ -1017,31 +972,6 @@ watch(
|
|
|
onMounted(async () => {
|
|
|
id.value = (route.query.id as string) || "";
|
|
|
type.value = (route.query.type as string) || "";
|
|
|
- // 不要删除-开始
|
|
|
- // let param = {
|
|
|
- // // phone: localGet("iphone")
|
|
|
- // phone: "18641153170"
|
|
|
- // };
|
|
|
- // const resP: any = await getUserByPhone(param);
|
|
|
- // if (resP.data && resP.data.storeId) {
|
|
|
- // localSet("createdId", resP.data.storeId);
|
|
|
- // const resD: any = await getDetail({
|
|
|
- // id: localGet("createdId")
|
|
|
- // });
|
|
|
- // if (resD.data && resD.data.commissionRate) {
|
|
|
- // localSet("commissionRate", resD.data.commissionRate);
|
|
|
- // }
|
|
|
- // if (resD.data && resD.data.businessSection) {
|
|
|
- // localSet("businessSection", resD.data.businessSection);
|
|
|
- // }
|
|
|
- // } else {
|
|
|
- // ElMessage.warning("请完成商家入驻后再进行新建团购");
|
|
|
- // }
|
|
|
- // if (!getGroupCombination()) {
|
|
|
- // ElMessage.warning("请完成商家入驻后重新登录再进行新建团购");
|
|
|
- // return;
|
|
|
- // }
|
|
|
- // 不要删除-结束
|
|
|
let params = {
|
|
|
year: new Date().getFullYear(),
|
|
|
page: 1,
|
|
|
@@ -1129,6 +1059,26 @@ onMounted(async () => {
|
|
|
const goBack = () => {
|
|
|
router.go(-1);
|
|
|
};
|
|
|
+const beforeAvatarUpload = (file: any) => {
|
|
|
+ console.log(file);
|
|
|
+ return false;
|
|
|
+};
|
|
|
+/**
|
|
|
+ * 检查文件是否在排队中(未上传)
|
|
|
+ * @param file 文件对象
|
|
|
+ * @returns 是否在排队中
|
|
|
+ */
|
|
|
+const isFilePending = (file: any): boolean => {
|
|
|
+ // 只检查 ready 状态(排队中),不包括 uploading(正在上传)
|
|
|
+ if (file.status === "ready") {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // 检查是否在待上传队列中
|
|
|
+ if (pendingUploadFiles.value.some(item => item.uid === file.uid)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+};
|
|
|
|
|
|
/**
|
|
|
* 图片上传 - 删除前确认
|
|
|
@@ -1137,6 +1087,11 @@ const goBack = () => {
|
|
|
* @returns Promise<boolean>,true 允许删除,false 阻止删除
|
|
|
*/
|
|
|
const handleBeforeRemove = async (uploadFile: any, uploadFiles: any[]): Promise<boolean> => {
|
|
|
+ // 如果文件在排队中(未上传),禁止删除
|
|
|
+ if (isFilePending(uploadFile)) {
|
|
|
+ ElMessage.warning("图片尚未上传,请等待上传完成后再删除");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
try {
|
|
|
await ElMessageBox.confirm("确定要删除这张图片吗?", "提示", {
|
|
|
confirmButtonText: "确定",
|
|
|
@@ -1167,28 +1122,163 @@ const handleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {
|
|
|
storeInfoModel.value.imageId.splice(index, 1);
|
|
|
}
|
|
|
}
|
|
|
+ if (file.url && file.url.startsWith("blob:")) {
|
|
|
+ URL.revokeObjectURL(file.url);
|
|
|
+ }
|
|
|
+ // 同步文件列表
|
|
|
+ storeInfoModel.value.imageValueStr = [...uploadFiles];
|
|
|
// 删除成功后提示
|
|
|
ElMessage.success("图片已删除");
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
- * 图片上传 - 上传成功回调
|
|
|
- * @param response 上传响应数据
|
|
|
- * @param uploadFile 上传的文件对象
|
|
|
- * @param uploadFiles 当前文件列表
|
|
|
+ * 上传文件超出限制提示
|
|
|
+ */
|
|
|
+const handleUploadExceed: UploadProps["onExceed"] = () => {
|
|
|
+ ElMessage.warning(`最多只能上传${uploadMaxCount}张图片`);
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * el-upload 文件变更(选中或移除)
|
|
|
+ */
|
|
|
+const handleUploadChange: UploadProps["onChange"] = async (uploadFile, uploadFiles) => {
|
|
|
+ // 检查文件类型,只允许 jpg 和 png
|
|
|
+ if (uploadFile.raw) {
|
|
|
+ const fileType = uploadFile.raw.type.toLowerCase();
|
|
|
+ const fileName = uploadFile.name.toLowerCase();
|
|
|
+ const validTypes = ["image/jpeg", "image/jpg", "image/png"];
|
|
|
+ const validExtensions = [".jpg", ".jpeg", ".png"];
|
|
|
+
|
|
|
+ // 检查 MIME 类型或文件扩展名
|
|
|
+ const isValidType = validTypes.includes(fileType) || validExtensions.some(ext => fileName.endsWith(ext));
|
|
|
+
|
|
|
+ if (!isValidType) {
|
|
|
+ // 从文件列表中移除不符合类型的文件
|
|
|
+ const index = storeInfoModel.value.imageValueStr.findIndex((f: any) => f.uid === uploadFile.uid);
|
|
|
+ if (index > -1) {
|
|
|
+ storeInfoModel.value.imageValueStr.splice(index, 1);
|
|
|
+ }
|
|
|
+ // 从 uploadFiles 中移除
|
|
|
+ const uploadIndex = uploadFiles.findIndex((f: any) => f.uid === uploadFile.uid);
|
|
|
+ if (uploadIndex > -1) {
|
|
|
+ uploadFiles.splice(uploadIndex, 1);
|
|
|
+ }
|
|
|
+ // 如果文件有 blob URL,释放它
|
|
|
+ if (uploadFile.url && uploadFile.url.startsWith("blob:")) {
|
|
|
+ URL.revokeObjectURL(uploadFile.url);
|
|
|
+ }
|
|
|
+ ElMessage.warning("只支持上传 JPG 和 PNG 格式的图片");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 同步文件列表到表单数据(只添加通过验证的文件)
|
|
|
+ const existingIndex = storeInfoModel.value.imageValueStr.findIndex((f: any) => f.uid === uploadFile.uid);
|
|
|
+ if (existingIndex === -1) {
|
|
|
+ storeInfoModel.value.imageValueStr.push(uploadFile);
|
|
|
+ }
|
|
|
+
|
|
|
+ const readyFiles = storeInfoModel.value.imageValueStr.filter(file => file.status === "ready");
|
|
|
+ if (readyFiles.length) {
|
|
|
+ readyFiles.forEach(file => {
|
|
|
+ if (!pendingUploadFiles.value.some(item => item.uid === file.uid)) {
|
|
|
+ pendingUploadFiles.value.push(file);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ processUploadQueue();
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 处理上传队列 - 逐个上传文件
|
|
|
*/
|
|
|
-const handleSuccess = (response: any, uploadFile: any, uploadFiles: any[]) => {
|
|
|
- const imageId = response?.data[0];
|
|
|
- // 将 imageId 添加到 storeInfoModel 的 imageId 数组中
|
|
|
- if (!storeInfoModel.value.imageId.includes(imageId)) {
|
|
|
- storeInfoModel.value.imageId.push(imageId);
|
|
|
+const processUploadQueue = async () => {
|
|
|
+ if (uploading.value || pendingUploadFiles.value.length === 0) {
|
|
|
+ return;
|
|
|
}
|
|
|
- // 将 imageId 保存到文件对象中,以便删除时使用
|
|
|
- if (uploadFile) {
|
|
|
- (uploadFile as any).imageId = imageId;
|
|
|
+ // 每次只取一个文件进行上传
|
|
|
+ const file = pendingUploadFiles.value.shift();
|
|
|
+ if (file) {
|
|
|
+ await uploadSingleFile(file);
|
|
|
+ // 继续处理队列中的下一个文件
|
|
|
+ processUploadQueue();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 单文件上传图片
|
|
|
+ * @param file 待上传的文件
|
|
|
+ */
|
|
|
+const uploadSingleFile = async (file: UploadFile) => {
|
|
|
+ if (!file.raw) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const formData = new FormData();
|
|
|
+ const storeId = Number(localGet("createdId") || 104);
|
|
|
+ const rawFile = file.raw as File;
|
|
|
+ const sortValue = generateImgSort();
|
|
|
+
|
|
|
+ formData.append("file", rawFile);
|
|
|
+ formData.append(
|
|
|
+ "list",
|
|
|
+ JSON.stringify([
|
|
|
+ {
|
|
|
+ storeId,
|
|
|
+ imgType: imgType.value,
|
|
|
+ imgSort: sortValue
|
|
|
+ }
|
|
|
+ ])
|
|
|
+ );
|
|
|
+
|
|
|
+ file.status = "uploading";
|
|
|
+ file.percentage = 0;
|
|
|
+ uploading.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(uploadUrl.value, {
|
|
|
+ method: "POST",
|
|
|
+ body: formData,
|
|
|
+ credentials: "include"
|
|
|
+ });
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error("上传失败");
|
|
|
+ }
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result?.code === 200 && result.data) {
|
|
|
+ // 处理单个文件的上传结果
|
|
|
+ const imageId = String(Array.isArray(result.data) ? result.data[0] : result.data);
|
|
|
+ file.status = "success";
|
|
|
+ file.percentage = 100;
|
|
|
+ (file as any).imageId = imageId;
|
|
|
+ file.response = { data: imageId };
|
|
|
+
|
|
|
+ if (!Array.isArray(storeInfoModel.value.imageId)) {
|
|
|
+ storeInfoModel.value.imageId = [];
|
|
|
+ }
|
|
|
+ if (!storeInfoModel.value.imageId.includes(imageId)) {
|
|
|
+ storeInfoModel.value.imageId.push(imageId);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new Error(result?.msg || "图片上传失败");
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ file.status = "fail";
|
|
|
+ if (file.url && file.url.startsWith("blob:")) {
|
|
|
+ URL.revokeObjectURL(file.url);
|
|
|
+ }
|
|
|
+ // 从文件列表中移除失败的文件
|
|
|
+ const index = storeInfoModel.value.imageValueStr.findIndex((f: any) => f.uid === file.uid);
|
|
|
+ if (index > -1) {
|
|
|
+ storeInfoModel.value.imageValueStr.splice(index, 1);
|
|
|
+ }
|
|
|
+ ElMessage.error(error?.message || "图片上传失败");
|
|
|
+ } finally {
|
|
|
+ uploading.value = false;
|
|
|
+ // 触发视图更新
|
|
|
+ storeInfoModel.value.imageValueStr = [...storeInfoModel.value.imageValueStr];
|
|
|
}
|
|
|
- ElMessage.success("图片上传成功");
|
|
|
- // 上传成功后,imageValueStr 会自动更新,response.data 包含图片URL
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
@@ -1196,12 +1286,30 @@ const handleSuccess = (response: any, uploadFile: any, uploadFiles: any[]) => {
|
|
|
* @param file 上传文件对象
|
|
|
*/
|
|
|
const handlePictureCardPreview = (file: any) => {
|
|
|
- // 获取所有图片的 URL 列表
|
|
|
- const urlList = storeInfoModel.value.imageValueStr.map((item: any) => item.url || item.response?.data || item);
|
|
|
+ // 如果文件在排队中(未上传),禁止预览
|
|
|
+ if (isFilePending(file)) {
|
|
|
+ ElMessage.warning("图片尚未上传,请等待上传完成后再预览");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 如果文件正在上传中,允许预览(使用本地预览)
|
|
|
+ if (file.status === "uploading" && file.url) {
|
|
|
+ imageViewerUrlList.value = [file.url];
|
|
|
+ imageViewerInitialIndex.value = 0;
|
|
|
+ imageViewerVisible.value = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 获取所有图片的 URL 列表(只包含已上传成功的图片)
|
|
|
+ const urlList = storeInfoModel.value.imageValueStr
|
|
|
+ .filter((item: any) => item.status === "success" && (item.url || item.response?.data))
|
|
|
+ .map((item: any) => item.url || item.response?.data);
|
|
|
// 找到当前点击的图片索引
|
|
|
- const currentIndex = urlList.findIndex((url: string) => url === file.url);
|
|
|
+ const currentIndex = urlList.findIndex((url: string) => url === (file.url || file.response?.data));
|
|
|
+ if (currentIndex < 0) {
|
|
|
+ ElMessage.warning("图片尚未上传完成,无法预览");
|
|
|
+ return;
|
|
|
+ }
|
|
|
imageViewerUrlList.value = urlList;
|
|
|
- imageViewerInitialIndex.value = currentIndex >= 0 ? currentIndex : 0;
|
|
|
+ imageViewerInitialIndex.value = currentIndex;
|
|
|
imageViewerVisible.value = true;
|
|
|
};
|
|
|
|
|
|
@@ -1565,7 +1673,7 @@ const openDishDialog = async (groupIndex: number, dishIndex: number) => {
|
|
|
currentDishIndex.value = dishIndex;
|
|
|
dishSearchKeyword.value = "";
|
|
|
const params = {
|
|
|
- storeId: 104,
|
|
|
+ storeId: localGet("createdId") || "104",
|
|
|
phoneId: 18641153170,
|
|
|
dishType: 0
|
|
|
};
|
|
|
@@ -1722,22 +1830,9 @@ const packageFormRules = computed(() => {
|
|
|
trigger: "blur"
|
|
|
},
|
|
|
{
|
|
|
- validator: (rule: any, value: any, callback: any) => {
|
|
|
- if (!value || value.toString().trim() === "") {
|
|
|
- callback();
|
|
|
- return;
|
|
|
- }
|
|
|
- const num = Number(value);
|
|
|
- if (isNaN(num) || num <= 0) {
|
|
|
- callback(new Error(`第${groupIndex + 1}个分组的第${dishIndex + 1}个菜品数量必须为正整数`));
|
|
|
- return;
|
|
|
- }
|
|
|
- if (!Number.isInteger(num)) {
|
|
|
- callback(new Error(`第${groupIndex + 1}个分组的第${dishIndex + 1}个菜品数量必须为正整数`));
|
|
|
- return;
|
|
|
- }
|
|
|
- callback();
|
|
|
- },
|
|
|
+ validator: validatePositiveInteger(`第${groupIndex + 1}个分组的第${dishIndex + 1}个菜品数量必须为正整数`, {
|
|
|
+ required: false
|
|
|
+ }),
|
|
|
trigger: "blur"
|
|
|
}
|
|
|
];
|
|
|
@@ -1752,6 +1847,11 @@ const packageFormRules = computed(() => {
|
|
|
* 验证表单,通过后调用相应的API接口
|
|
|
*/
|
|
|
const handleSubmit = async (type?) => {
|
|
|
+ // 检查是否有未上传完成的图片
|
|
|
+ if (hasUnuploadedImages.value) {
|
|
|
+ ElMessage.warning("请等待图片上传完成后再提交");
|
|
|
+ return;
|
|
|
+ }
|
|
|
let params: any = { ...storeInfoModel.value };
|
|
|
// 确保 imageId 是数组,然后转换为逗号分隔的字符串
|
|
|
if (Array.isArray(params.imageId)) {
|
|
|
@@ -1770,8 +1870,8 @@ const handleSubmit = async (type?) => {
|
|
|
lifeGroupBuyThalis: lifeGroupBuyThalis.value
|
|
|
};
|
|
|
paramsObj.lifeGroupBuyMain.status = type ? "0" : "1";
|
|
|
- paramsObj.lifeGroupBuyMain.groupType = 1;
|
|
|
- paramsObj.lifeGroupBuyMain.storeId = localGet("storeId") || "104";
|
|
|
+ paramsObj.lifeGroupBuyMain.groupType = localGet("businessSection") || "1";
|
|
|
+ paramsObj.lifeGroupBuyMain.storeId = localGet("createdId") || "104";
|
|
|
if (id.value) {
|
|
|
paramsObj.lifeGroupBuyMain.id = id.value;
|
|
|
}
|
|
|
@@ -1812,6 +1912,7 @@ const handleSubmit = async (type?) => {
|
|
|
let res: any = await saveThali(paramsObj);
|
|
|
if (res && res.code == 200) {
|
|
|
ElMessage.success("保存成功");
|
|
|
+ goBack();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
@@ -1971,6 +2072,35 @@ const handleImageParam = (list: any[], result: any[]) => {
|
|
|
object-fit: fill;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /* 排队中(未上传)的图片禁用样式 */
|
|
|
+ .el-upload-list__item[data-status="ready"],
|
|
|
+ .el-upload-list__item.is-ready {
|
|
|
+ position: relative;
|
|
|
+ pointer-events: none;
|
|
|
+ cursor: not-allowed;
|
|
|
+ opacity: 0.6;
|
|
|
+ &::after {
|
|
|
+ position: absolute;
|
|
|
+ inset: 0;
|
|
|
+ z-index: 1;
|
|
|
+ content: "";
|
|
|
+ background-color: rgb(0 0 0 / 30%);
|
|
|
+ }
|
|
|
+ .el-upload-list__item-actions {
|
|
|
+ pointer-events: none;
|
|
|
+ opacity: 0.5;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.upload-trigger-card {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ font-size: 28px;
|
|
|
+ color: #8c939d;
|
|
|
}
|
|
|
|
|
|
/* 表单容器 */
|