|
|
@@ -101,30 +101,17 @@
|
|
|
<el-icon><Close /></el-icon>关闭
|
|
|
</el-button>
|
|
|
</div>
|
|
|
- <div class="image-edit-container" style="display: flex; height: 94vh; overflow: hidden; padding-top: 20px;">
|
|
|
+ <div class="image-edit-container">
|
|
|
<!-- 左侧:原图新图 + 输入框 -->
|
|
|
- <div class="left-column" style="flex: 0.7; display: flex; flex-direction: column; gap: 15px; padding: 15px; overflow-y: hidden;">
|
|
|
+ <div class="left-column">
|
|
|
<!-- 标题 -->
|
|
|
<div style="font-size: 18px; font-weight: bold; color: white; margin-bottom: 10px; background-color: #404040; padding: 10px; border-radius: 4px; text-align: center;">②出图</div>
|
|
|
<!-- 图片对比区域 -->
|
|
|
- <div class="image-comparison-section" style="display: flex; gap: 20px;">
|
|
|
+ <div class="image-comparison-section">
|
|
|
<!-- 原图区域 -->
|
|
|
<div class="image-preview" style="flex: 1; min-width: 120px; display: flex; flex-direction: column; align-items: center;">
|
|
|
<h3 style="margin-top: 0; margin-bottom: 8px; font-size: 14px; font-weight: bold; color: #303133; align-self: flex-start;">上传商品白底图</h3>
|
|
|
- <el-button
|
|
|
- type="text"
|
|
|
- size="small"
|
|
|
- @click="toggleProductExample"
|
|
|
- style="padding: 0; font-size: 14px; font-weight: normal; color: #409eff; align-self: flex-end; margin-bottom: 10px;margin-right: 110px;"
|
|
|
- >
|
|
|
- <template #icon>
|
|
|
- <el-icon :size="16">
|
|
|
- <component :is="productExampleVisible ? 'ArrowDown' : 'ArrowRight'" />
|
|
|
- </el-icon>
|
|
|
- </template>
|
|
|
- 商品图例
|
|
|
- </el-button>
|
|
|
- <div v-if="editFormData.original_image_url" style="width:200px; height: 200px; display: flex; justify-content: center; align-items: center; background-color: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
+ <div v-if="editFormData.original_image_url" class="upload-image-box">
|
|
|
<el-image
|
|
|
:src="formatImageUrl(editFormData.original_image_url)"
|
|
|
:preview-src-list="[formatImageUrl(editFormData.original_image_url)]"
|
|
|
@@ -132,20 +119,23 @@
|
|
|
fit="contain"
|
|
|
preview-teleported/>
|
|
|
</div>
|
|
|
- <div v-else class="image-placeholder" style="width: 100px; height: 100px; display: flex; flex-direction: column; justify-content: center; align-items: center; background-color: #f9f9f9; border-radius: 4px; border: 1px solid #e4e7ed;">
|
|
|
+ <div v-else class="image-placeholder image-placeholder-small">
|
|
|
<el-icon :size="30"><Picture /></el-icon>
|
|
|
<span style="margin-top: 5px; font-size: 10px;">暂无原图</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 按钮区域 -->
|
|
|
- <div style="display: flex; align-items: center; gap: 20px; margin-left:480px">
|
|
|
- <el-button
|
|
|
- type="text"
|
|
|
- size="small"
|
|
|
- @click="toggleCaseExample"
|
|
|
- style="padding: 0; font-size: 14px; font-weight: normal; color: #409eff;"
|
|
|
- >
|
|
|
+ <!-- 商品图例、案例:右侧一列,与图片等高上下对齐,不用固定 margin 避免小屏跑偏 -->
|
|
|
+ <div class="case-links-column">
|
|
|
+ <el-button type="text" size="small" @click="toggleProductExample" class="case-link-btn">
|
|
|
+ <template #icon>
|
|
|
+ <el-icon :size="16">
|
|
|
+ <component :is="productExampleVisible ? 'ArrowDown' : 'ArrowRight'" />
|
|
|
+ </el-icon>
|
|
|
+ </template>
|
|
|
+ 商品图例
|
|
|
+ </el-button>
|
|
|
+ <el-button type="text" size="small" @click="toggleCaseExample" class="case-link-btn">
|
|
|
<template #icon>
|
|
|
<el-icon :size="16">
|
|
|
<component :is="caseExampleVisible ? 'ArrowDown' : 'ArrowRight'" />
|
|
|
@@ -154,8 +144,6 @@
|
|
|
案例
|
|
|
</el-button>
|
|
|
</div>
|
|
|
-
|
|
|
-
|
|
|
</div>
|
|
|
|
|
|
<!-- 输入框区域 -->
|
|
|
@@ -237,16 +225,16 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 中间:产品信息和历史记录 -->
|
|
|
-<div class="middle-column" style="width: 430px; border-left: 1px solid #e4e7ed; border-right: 1px solid #e4e7ed; padding: 15px 20px; position: relative;">
|
|
|
+ <!-- 中间:产品信息和历史记录(保证两侧边框可见,不与其他列重叠) -->
|
|
|
+<div class="middle-column">
|
|
|
<!-- 历史记录覆盖层 -->
|
|
|
<div v-if="showHistoryPanel" style="position: absolute; top: 0; left: 0; right: 0; height: 100%; background: #ffffff; border-radius: 0; z-index: 1000; display: flex; justify-content: center; align-items: flex-start; box-shadow: 4px 0 20px rgba(0,0,0,0.15); border-right: 1px solid #e4e7ed;">
|
|
|
<div style="width: 95%; height: 100%; overflow-y: auto;">
|
|
|
<div style="padding: 20px;">
|
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #e4e7ed;">
|
|
|
<h3 style="margin: 0; font-size: 16px; font-weight: bold; color: #303133;">图片历史记录</h3>
|
|
|
- <el-button type="text" size="small" @click="toggleHistoryPanel">
|
|
|
- <el-icon><Close /></el-icon>
|
|
|
+ <el-button type="danger" size="small" @click="toggleHistoryPanel">
|
|
|
+ <el-icon><Close /></el-icon>关闭
|
|
|
</el-button>
|
|
|
</div>
|
|
|
|
|
|
@@ -308,7 +296,7 @@
|
|
|
<!-- 左侧:历史记录面板 (已移至覆盖层) -->
|
|
|
|
|
|
<!-- 右侧:原始内容 -->
|
|
|
- <div class="original-content" style="flex: 1; overflow-y: auto;">
|
|
|
+ <div class="original-content" style="flex: 1; overflow-y: auto; min-width: 0;">
|
|
|
<!-- 商品图例覆盖层 -->
|
|
|
<div v-if="productExampleVisible" style="position: absolute; top: 0; left: 0; right: 0; height: 100%; background: #ffffff; border-radius: 0; z-index: 1000; display: flex; justify-content: center; align-items: flex-start; box-shadow: 4px 0 20px rgba(0,0,0,0.15); border-right: 1px solid #e4e7ed;">
|
|
|
<div style="width: 95%; height: 100%; overflow-y: auto;">
|
|
|
@@ -467,7 +455,7 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 案例覆盖层 -->
|
|
|
- <div v-if="caseExampleVisible" style="position: absolute; top: 0; left: 0; width: 600px; height: 100%; background: #ffffff; border-radius: 0; z-index: 1000; display: flex; justify-content: center; align-items: flex-start; box-shadow: 4px 0 20px rgba(0,0,0,0.15); border-right: 1px solid #e4e7ed;">
|
|
|
+ <div v-if="caseExampleVisible" style="position: absolute; top: 0; left: 0; right: 0; width: 100%; max-width: 600px; height: 100%; background: #ffffff; border-radius: 0; z-index: 1000; display: flex; justify-content: center; align-items: flex-start; box-shadow: 4px 0 20px rgba(0,0,0,0.15); border-right: 1px solid #e4e7ed;">
|
|
|
<div style="width: 95%; height: 100%; overflow-y: auto;">
|
|
|
<div style="padding: 20px;">
|
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #e4e7ed;">
|
|
|
@@ -687,13 +675,13 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div style="height: 100%; display: flex; flex-direction: column;">
|
|
|
+ <div style="height: 100%; display: flex; flex-direction: column; width: 100%; min-width: 0; align-items: center;">
|
|
|
<!-- 上:留出的空白区域 -->
|
|
|
- <div style="height: 90px; background-image: url('/src/assets/top-bg.png'); background-size: cover; background-position: center; border-top-left-radius: 8px; border-top-right-radius: 8px;"></div>
|
|
|
+ <div style="width: 100%; height: 90px; background-image: url('/src/assets/top-bg.png'); background-size: cover; background-position: center; border-top-left-radius: 8px; border-top-right-radius: 8px;"></div>
|
|
|
<!-- 上中:图片显示区域 -->
|
|
|
- <div style="display: flex; flex-direction: column; align-items: center;">
|
|
|
+ <div style="display: flex; flex-direction: column; align-items: center; width: 100%;">
|
|
|
<!-- 产品图片显示(默认) -->
|
|
|
- <div style="width: 430px; height: 290px; display: flex; justify-content: center; align-items: center; background-color: white; border-radius: 4px;">
|
|
|
+ <div style="width: 100%; height: 290px; max-width: 430px; display: flex; justify-content: center; align-items: center; background-color: white; border-radius: 4px;">
|
|
|
<el-carousel v-if="newImages.length > 0" indicator-position="outside" style="width: 100%; height: 100%;">
|
|
|
<el-carousel-item v-for="(image, index) in newImages" :key="index">
|
|
|
<el-image
|
|
|
@@ -702,7 +690,11 @@
|
|
|
style="width: 100%; height: 100%; object-fit: fill;"
|
|
|
fit="fill"
|
|
|
preview-teleported
|
|
|
- />
|
|
|
+ >
|
|
|
+ <template #error>
|
|
|
+ <div style="width: 100%; height: 100%; background-color: white;" />
|
|
|
+ </template>
|
|
|
+ </el-image>
|
|
|
</el-carousel-item>
|
|
|
</el-carousel>
|
|
|
<el-image
|
|
|
@@ -712,15 +704,19 @@
|
|
|
style="width: 100%; height: 100%; object-fit: fill;"
|
|
|
fit="fill"
|
|
|
preview-teleported
|
|
|
- />
|
|
|
+ >
|
|
|
+ <template #error>
|
|
|
+ <div style="width: 100%; height: 100%; background-color: white;" />
|
|
|
+ </template>
|
|
|
+ </el-image>
|
|
|
<div v-else-if="!showProductImage || !editFormData.new_image_url" class="image-placeholder">
|
|
|
<el-icon :size="60"><Picture /></el-icon>
|
|
|
- <span style="margin-top: 10px; display: block;">暂无产品图片</span>
|
|
|
+ <span style="margin-top: 10px; display: block;">暂无效果图</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <!-- 中:商品信息表单 -->
|
|
|
- <div class="product-info-container" style="width: 430px; height: 320px;border: 12px solid #f6E0dd; border-radius: 4px; padding: 15px;">
|
|
|
+ <!-- 中:商品信息表单(宽度 100% 含边框,避免右侧边框被裁) -->
|
|
|
+ <div class="product-info-container" style="width: 100%; max-width: 430px; height: 320px; border: 12px solid #f6E0dd; border-radius: 4px; padding: 15px; box-sizing: border-box;">
|
|
|
<div class="product-info-item">
|
|
|
<span class="product-info-label">商品条码:</span>
|
|
|
<span class="product-info-value">{{ editFormData.barcode || '-' }}</span>
|
|
|
@@ -747,14 +743,14 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- 下:空出来的部分 -->
|
|
|
- <div style="width: 430px; height: 61px;"></div>
|
|
|
+ <div style="width: 100%; max-width: 430px; height: 61px;"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 右侧:模板列表 -->
|
|
|
- <div class="right-column" style="flex: 1;">
|
|
|
+ <!-- 右侧:模版列表(与中间列左右留白一致) -->
|
|
|
+ <div class="right-column">
|
|
|
<div class="right-template" style="height: 100%; display: flex; flex-direction: column;">
|
|
|
<!-- 标题 -->
|
|
|
<div style="font-size: 18px; font-weight: bold; color: white; margin-bottom: 15px; background-color: #404040; padding: 10px; border-radius: 4px; text-align: center;margin-top:15px">①模版</div>
|
|
|
@@ -795,7 +791,7 @@
|
|
|
<el-image
|
|
|
:src="template.template_image_url"
|
|
|
style="width: 100%; height: 100%;"
|
|
|
- fit="cover"
|
|
|
+ fit="contain"
|
|
|
>
|
|
|
<template #error>
|
|
|
<div class="thumbnail-error">
|
|
|
@@ -833,7 +829,8 @@
|
|
|
<!-- 图片预览组件 -->
|
|
|
<el-image-viewer
|
|
|
v-if="previewVisible"
|
|
|
- :url-list="[previewImageUrl]"
|
|
|
+ :url-list="previewImageUrl ? [previewImageUrl] : []"
|
|
|
+ :hide-on-click-modal="true"
|
|
|
@close="previewVisible = false"
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -1007,46 +1004,54 @@
|
|
|
|
|
|
<!-- 产品图片 -->
|
|
|
<el-form-item label="产品图片" prop="product_img">
|
|
|
- <div class="upload-container">
|
|
|
- <!-- 图片展示 -->
|
|
|
- <div v-if="productForm.product_img" class="image-preview">
|
|
|
- <el-image
|
|
|
- :src="productForm.product_img"
|
|
|
- :preview-src-list="[productForm.product_img]"
|
|
|
- fit="cover"
|
|
|
- class="preview-image"
|
|
|
- />
|
|
|
- <div class="image-actions">
|
|
|
- <el-button type="text" @click="handleViewImage">查看</el-button>
|
|
|
- <el-button type="text" @click="handleRemoveImage">删除</el-button>
|
|
|
+ <div class="add-dialog-upload-wrap">
|
|
|
+ <!-- 已上传:预览 + 替换/删除 -->
|
|
|
+ <div v-if="productForm.product_img" class="add-dialog-preview-box">
|
|
|
+ <div class="add-dialog-preview-img">
|
|
|
+ <img
|
|
|
+ v-if="addDialogPreviewSrc"
|
|
|
+ :src="addDialogPreviewSrc"
|
|
|
+ class="add-dialog-preview-img-inner"
|
|
|
+ alt="产品图片预览"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="add-dialog-preview-actions">
|
|
|
+ <el-upload
|
|
|
+ :show-file-list="false"
|
|
|
+ :before-upload="handleAddDialogSelectImage"
|
|
|
+ accept="image/jpeg,image/png,image/jpg,image/webp"
|
|
|
+ >
|
|
|
+ <el-button type="primary" size="small">
|
|
|
+ <el-icon><Upload /></el-icon>
|
|
|
+ 替换
|
|
|
+ </el-button>
|
|
|
+ </el-upload>
|
|
|
+ <el-button type="danger" size="small" plain @click="handleRemoveImage">
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- 上传区域 -->
|
|
|
+ <!-- 未上传:点击上传区域 -->
|
|
|
<el-upload
|
|
|
v-else
|
|
|
- class="image-uploader"
|
|
|
- :action="uploadUrl"
|
|
|
+ class="add-dialog-upload-area"
|
|
|
:show-file-list="false"
|
|
|
- :on-success="handleUploadSuccess"
|
|
|
- :on-error="handleUploadError"
|
|
|
- :before-upload="beforeUpload"
|
|
|
- accept="image/*"
|
|
|
+ :before-upload="handleAddDialogSelectImage"
|
|
|
+ accept="image/jpeg,image/png,image/jpg,image/webp"
|
|
|
>
|
|
|
- <el-button type="primary" plain>
|
|
|
- <el-icon><Plus /></el-icon>
|
|
|
- 上传图片
|
|
|
- </el-button>
|
|
|
- <div class="upload-tip">支持 JPG、PNG 格式,大小不超过 5MB</div>
|
|
|
+ <div class="add-dialog-upload-inner">
|
|
|
+ <el-icon class="add-dialog-upload-icon"><Plus /></el-icon>
|
|
|
+ <span class="add-dialog-upload-text">点击上传图片</span>
|
|
|
+ <span class="add-dialog-upload-tip">支持 JPG、PNG,不超过 5MB</span>
|
|
|
+ </div>
|
|
|
</el-upload>
|
|
|
</div>
|
|
|
- <div class="form-tip">请上传产品图片</div>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
<span class="dialog-footer">
|
|
|
- <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button @click="handleClose">取消</el-button>
|
|
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
|
|
确定
|
|
|
</el-button>
|
|
|
@@ -1060,12 +1065,12 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive, toRaw, onMounted ,watch } from 'vue'
|
|
|
+import { ref, reactive, computed, toRaw, onMounted, watch } from 'vue'
|
|
|
import { ElMessage, ElLoading} from 'element-plus'
|
|
|
import { getTable, imageToText, Template_ids,txttoimg_moxing,GetHttpUrl,txttoimg_update, getSide,merchantGetab,productList,productDetail,
|
|
|
product_template,GetTxtToTxt,GetProductFind,productAdd,getMerchantId,GetImageStatus } from '@/api/mes/job'
|
|
|
import { useUserStore } from '@/pinia/modules/user'
|
|
|
-import { ZoomIn, Camera,SuccessFilled, WarningFilled, More, ArrowRight, ArrowDown, Loading } from '@element-plus/icons-vue'
|
|
|
+import { ZoomIn, Camera, SuccessFilled, WarningFilled, More, ArrowRight, ArrowDown, Loading, Plus, Upload, Picture } from '@element-plus/icons-vue'
|
|
|
import { Layout, LayoutHeader, LayoutSider, LayoutContent } from '@arco-design/web-vue'
|
|
|
|
|
|
|
|
|
@@ -1394,10 +1399,18 @@ const handleTemplateTreeClick = (data) => {
|
|
|
}
|
|
|
|
|
|
const onRowDblClick = async (row) => {
|
|
|
+ // 打开新产品前先清空上一产品的历史与展示,避免残留
|
|
|
+ showHistoryPanel.value = false
|
|
|
+ newImages.value = []
|
|
|
+ templateTreeData.value = []
|
|
|
+ filteredImages.value = []
|
|
|
+ currentTemplateId.value = null
|
|
|
+ currentTemplateName.value = ''
|
|
|
+
|
|
|
// 设置编辑表单ID
|
|
|
editFormData.id = row.id
|
|
|
editDialogVisible.value = true
|
|
|
-
|
|
|
+
|
|
|
// 1. 获取产品详情并设置原图
|
|
|
const detailResponse = await productDetail({ id: row.id })
|
|
|
if (detailResponse.code === 0 && detailResponse.data) {
|
|
|
@@ -1406,18 +1419,16 @@ const onRowDblClick = async (row) => {
|
|
|
editFormData.original_name = detailResponse.data['产品名称']
|
|
|
editFormData.product_name = detailResponse.data['产品名称']
|
|
|
editFormData.product_code = detailResponse.data['产品编码']
|
|
|
-
|
|
|
+
|
|
|
// 如果有新图片,自动显示到产品信息区域
|
|
|
if (detailResponse.data['产品效果图']) {
|
|
|
showProductImage.value = true
|
|
|
+ } else {
|
|
|
+ showProductImage.value = false
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 获取image数组数据
|
|
|
if (detailResponse.image && Array.isArray(detailResponse.image)) {
|
|
|
- // 清空之前的新图数据
|
|
|
- newImages.value = []
|
|
|
-
|
|
|
- // 遍历image数组,添加到newImages
|
|
|
detailResponse.image.forEach(item => {
|
|
|
if (item.product_new_img) {
|
|
|
newImages.value.push({
|
|
|
@@ -1428,20 +1439,21 @@ const onRowDblClick = async (row) => {
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
+
|
|
|
// 如果有image数据,默认显示第一个的product_content
|
|
|
if (newImages.value.length > 0 && newImages.value[0].product_content) {
|
|
|
editFormData.chinese_description = newImages.value[0].product_content
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 处理模板分类树数据
|
|
|
processTemplateTreeData(detailResponse.image)
|
|
|
+ } else {
|
|
|
+ editFormData.chinese_description = ''
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 2. 获取所有模板数据
|
|
|
await fetchTemplates()
|
|
|
-
|
|
|
}
|
|
|
|
|
|
// 存储所有模板数据
|
|
|
@@ -1462,7 +1474,9 @@ const fetchTemplates = async () => {
|
|
|
// if (templateList.value.length > 0) {
|
|
|
// selectTemplate(templateList.value[0])
|
|
|
// }
|
|
|
- updateProductImage(newImages.value[0].url)
|
|
|
+ if (newImages.value.length > 0) {
|
|
|
+ updateProductImage(newImages.value[0].url)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -2061,6 +2075,8 @@ const handleSearch = async () => {
|
|
|
const AdddialogVisible = ref(false)
|
|
|
const productFormRef = ref()
|
|
|
const submitLoading = ref(false)
|
|
|
+// 新增产品:选中的图片文件(点确定时随 productAdd 一起提交,不单独调 ImgUpload)
|
|
|
+const productImageFile = ref(null)
|
|
|
|
|
|
// 表单数据
|
|
|
const productForm = reactive({
|
|
|
@@ -2068,7 +2084,7 @@ const productForm = reactive({
|
|
|
product_code: '',
|
|
|
create_name: '',
|
|
|
merchant_id: '',
|
|
|
- product_img: ''
|
|
|
+ product_img: '' // 预览用:blob URL 或空
|
|
|
})
|
|
|
|
|
|
|
|
|
@@ -2078,6 +2094,11 @@ const onADD = async() => {
|
|
|
ElMessage.error('请先选择商户')
|
|
|
return
|
|
|
}
|
|
|
+ if (productForm.product_img && productForm.product_img.startsWith('blob:')) {
|
|
|
+ URL.revokeObjectURL(productForm.product_img)
|
|
|
+ }
|
|
|
+ productForm.product_img = ''
|
|
|
+ productImageFile.value = null
|
|
|
AdddialogVisible.value = true
|
|
|
const res = await getMerchantId({
|
|
|
merchant_code: nodeid.value,
|
|
|
@@ -2088,34 +2109,73 @@ const onADD = async() => {
|
|
|
}
|
|
|
|
|
|
|
|
|
+// 将 File 转为 data:image/xxx;base64,... 字符串
|
|
|
+const fileToDataUrl = (file) => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const reader = new FileReader()
|
|
|
+ reader.onload = () => resolve(reader.result)
|
|
|
+ reader.onerror = reject
|
|
|
+ reader.readAsDataURL(file)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
const handleSubmit = async()=>{
|
|
|
- productForm.product_img = '/uploads/merchant/690377511/6903775111138/oldimg/鲜美番茄酱.png'
|
|
|
+ if (!productImageFile.value) {
|
|
|
+ ElMessage.warning('请先选择产品图片')
|
|
|
+ return
|
|
|
+ }
|
|
|
productForm.create_name = userStore.userInfo.nickName
|
|
|
try {
|
|
|
- const response = await productAdd(productForm)
|
|
|
+ submitLoading.value = true
|
|
|
+ const productImgBase64 = await fileToDataUrl(productImageFile.value)
|
|
|
+ const payload = {
|
|
|
+ product_name: productForm.product_name,
|
|
|
+ product_code: productForm.product_code,
|
|
|
+ merchant_id: productForm.merchant_id,
|
|
|
+ create_name: productForm.create_name,
|
|
|
+ product_img: productImgBase64
|
|
|
+ }
|
|
|
+ const response = await productAdd(payload)
|
|
|
if (response.code === 0) {
|
|
|
ElMessage.success('新增成功')
|
|
|
handleClose()
|
|
|
} else {
|
|
|
- ElMessage.error(response.message || '新增失败')
|
|
|
+ ElMessage.error(response.message || response.msg || '新增失败')
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('新增失败:', error)
|
|
|
ElMessage.error('新增失败,请联系管理员')
|
|
|
- }
|
|
|
+ } finally {
|
|
|
+ submitLoading.value = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-// 上传相关配置
|
|
|
-// 环境配置
|
|
|
-const uploadUrl = ref(`${full_url.value}/api/Facility/ImgUpload`)
|
|
|
-
|
|
|
-// 确保上传URL与服务器地址同步
|
|
|
-watch(full_url, (newUrl) => {
|
|
|
- uploadUrl.value = `${newUrl}/api/Facility/ImgUpload`
|
|
|
+// 新增产品弹窗:预览图地址(blob 或后端返回的 URL)
|
|
|
+const addDialogPreviewSrc = computed(() => {
|
|
|
+ const p = productForm.product_img
|
|
|
+ if (!p) return ''
|
|
|
+ if (p.startsWith('blob:')) return p
|
|
|
+ return formatImageUrl(p)
|
|
|
})
|
|
|
-const uploadHeaders = {
|
|
|
- 'Content-Type': 'multipart/form-data',
|
|
|
- 'Authorization': 'Bearer ' + localStorage.getItem('token')
|
|
|
+
|
|
|
+// 新增产品:选择图片仅做本地预览,不调上传接口;点确定时随 productAdd 一起提交
|
|
|
+const handleAddDialogSelectImage = (file) => {
|
|
|
+ const isImage = file.type.startsWith('image/')
|
|
|
+ const isLt5M = file.size / 1024 / 1024 < 5
|
|
|
+ if (!isImage) {
|
|
|
+ ElMessage.error('只能上传图片文件')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if (!isLt5M) {
|
|
|
+ ElMessage.error('图片大小不能超过 5MB')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if (productForm.product_img && productForm.product_img.startsWith('blob:')) {
|
|
|
+ URL.revokeObjectURL(productForm.product_img)
|
|
|
+ }
|
|
|
+ productImageFile.value = file
|
|
|
+ productForm.product_img = URL.createObjectURL(file)
|
|
|
+ return false // 阻止 el-upload 默认上传
|
|
|
}
|
|
|
|
|
|
// 打开弹窗方法(外部调用)
|
|
|
@@ -2131,82 +2191,27 @@ const openDialog = () => {
|
|
|
// productForm.create_name = '默认创建人'
|
|
|
}
|
|
|
|
|
|
-// 关闭弹窗
|
|
|
+// 关闭新增产品弹窗并重置表单
|
|
|
const handleClose = () => {
|
|
|
- productFormRef.value?.resetFields()
|
|
|
- submitLoading.value = false
|
|
|
-}
|
|
|
-
|
|
|
-// 自定义上传逻辑
|
|
|
-const customUpload = async (options) => {
|
|
|
- const { file, onProgress, onSuccess, onError } = options
|
|
|
- try {
|
|
|
- const formData = new FormData()
|
|
|
- formData.append('image', file)
|
|
|
- const response = await axios.post(uploadUrl.value, formData, {
|
|
|
- headers: uploadHeaders,
|
|
|
- onUploadProgress: (progressEvent) => {
|
|
|
- const percent = Math.round(
|
|
|
- (progressEvent.loaded * 100) / progressEvent.total
|
|
|
- )
|
|
|
- onProgress({ percent }, file)
|
|
|
- },
|
|
|
- timeout: 60000 // 60秒超时
|
|
|
- })
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
-
|
|
|
- } finally {
|
|
|
- // 关闭加载中
|
|
|
- if (file.loadingInstance) {
|
|
|
- file.loadingInstance.close()
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 图片上传成功处理
|
|
|
-const handleUploadSuccess = (response, file) => {
|
|
|
- // 根据实际接口返回的数据结构调整
|
|
|
- if (response.code === 0) {
|
|
|
- productForm.product_img = response.data.url
|
|
|
- ElMessage.success('上传成功')
|
|
|
- } else {
|
|
|
- ElMessage.error(response.message || '上传失败')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 上传失败处理
|
|
|
-const handleUploadError = (error, file) => {
|
|
|
- console.error('上传失败:', error)
|
|
|
- ElMessage.error('图片上传失败,请重试')
|
|
|
-}
|
|
|
-
|
|
|
-// 上传前验证
|
|
|
-const beforeUpload = (file) => {
|
|
|
- const isImage = file.type.startsWith('image/')
|
|
|
- const isLt5M = file.size / 1024 / 1024 < 5
|
|
|
-
|
|
|
- if (!isImage) {
|
|
|
- ElMessage.error('只能上传图片文件')
|
|
|
- return false
|
|
|
- }
|
|
|
-
|
|
|
- if (!isLt5M) {
|
|
|
- ElMessage.error('图片大小不能超过 5MB')
|
|
|
- return false
|
|
|
+ if (productForm.product_img && productForm.product_img.startsWith('blob:')) {
|
|
|
+ URL.revokeObjectURL(productForm.product_img)
|
|
|
}
|
|
|
-
|
|
|
- return true
|
|
|
-}
|
|
|
-
|
|
|
-// 查看图片
|
|
|
-const handleViewImage = () => {
|
|
|
- // el-image 的预览功能会自动处理
|
|
|
+ productForm.product_img = ''
|
|
|
+ productForm.product_name = ''
|
|
|
+ productForm.product_code = ''
|
|
|
+ productImageFile.value = null
|
|
|
+ submitLoading.value = false
|
|
|
+ productFormRef.value?.resetFields()
|
|
|
+ AdddialogVisible.value = false
|
|
|
}
|
|
|
|
|
|
-// 删除图片
|
|
|
+// 删除图片(仅清空预览与文件,不调接口)
|
|
|
const handleRemoveImage = () => {
|
|
|
+ if (productForm.product_img && productForm.product_img.startsWith('blob:')) {
|
|
|
+ URL.revokeObjectURL(productForm.product_img)
|
|
|
+ }
|
|
|
productForm.product_img = ''
|
|
|
+ productImageFile.value = null
|
|
|
}
|
|
|
|
|
|
// 暴露方法给父组件
|
|
|
@@ -2232,6 +2237,83 @@ defineExpose({
|
|
|
margin-top: 10px;
|
|
|
text-align: right;
|
|
|
}
|
|
|
+
|
|
|
+ /* 新增产品弹窗 - 上传与预览 */
|
|
|
+ .add-dialog-upload-wrap {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+ .add-dialog-preview-box {
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fafafa;
|
|
|
+ }
|
|
|
+ .add-dialog-preview-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 200px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #f5f7fa;
|
|
|
+ }
|
|
|
+ .add-dialog-preview-img-inner {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 200px;
|
|
|
+ width: auto;
|
|
|
+ height: auto;
|
|
|
+ object-fit: contain;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ .add-dialog-preview-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px;
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-area {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-area :deep(.el-upload) {
|
|
|
+ width: 100%;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-inner {
|
|
|
+ width: 100%;
|
|
|
+ height: 160px;
|
|
|
+ border: 2px dashed #dcdfe6;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 8px;
|
|
|
+ background: #fafafa;
|
|
|
+ color: #606266;
|
|
|
+ transition: border-color 0.2s, background 0.2s;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-inner:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ background: #ecf5ff;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-icon {
|
|
|
+ font-size: 36px;
|
|
|
+ color: #c0c4cc;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-inner:hover .add-dialog-upload-icon {
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-text {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+ .add-dialog-upload-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
:deep(.el-table__body tr.current-row) > td {
|
|
|
background: #ff80ff !important;
|
|
|
}
|
|
|
@@ -2248,26 +2330,104 @@ defineExpose({
|
|
|
border-bottom: 1px solid #e4e7ed;
|
|
|
}
|
|
|
|
|
|
- /* 新布局CSS */
|
|
|
+ /* 编辑弹窗三列布局:自适应,小屏横向滚动防跑偏 */
|
|
|
.image-edit-container {
|
|
|
display: flex;
|
|
|
- gap: 20px;
|
|
|
- height: calc(100vh - 140px);
|
|
|
+ height: 94vh;
|
|
|
+ padding-top: 20px;
|
|
|
+ gap: 0;
|
|
|
+ width: 100%;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: hidden;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
.left-column {
|
|
|
- flex: 2;
|
|
|
+ flex: 0.7;
|
|
|
+ min-width: 280px;
|
|
|
+ max-width: 560px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 20px;
|
|
|
+ gap: 15px;
|
|
|
+ padding: 15px;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ flex-shrink: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .middle-column {
|
|
|
+ width: 430px;
|
|
|
+ min-width: 380px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ border-left: 1px solid #e4e7ed;
|
|
|
+ border-right: 1px solid #e4e7ed;
|
|
|
+ padding: 15px 12px;
|
|
|
+ position: relative;
|
|
|
+ box-sizing: border-box;
|
|
|
+ background: #fff;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .right-column {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 280px;
|
|
|
+ min-height: 0;
|
|
|
+ flex-shrink: 1;
|
|
|
+ padding: 0 12px 0 15px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.image-comparison-section {
|
|
|
- flex: none; /* 改为不伸缩 */
|
|
|
- height: 300px; /* 固定高度,和原来一样 */
|
|
|
+ flex: none;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 16px;
|
|
|
+ min-height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-image-box {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ max-width: 100%;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-placeholder-small {
|
|
|
+ width: 100px;
|
|
|
+ height: 100px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 15px;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ background-color: #f9f9f9;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .case-links-column {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .case-link-btn {
|
|
|
+ padding: 0 !important;
|
|
|
+ font-size: 14px !important;
|
|
|
+ font-weight: normal !important;
|
|
|
+ color: #409eff !important;
|
|
|
}
|
|
|
|
|
|
.image-item {
|
|
|
@@ -2393,14 +2553,15 @@ defineExpose({
|
|
|
.template-item {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- width: calc(33.33% - 7px); /* 一行显示3个,减去gap */
|
|
|
+ width: calc(33.33% - 7px);
|
|
|
+ min-width: 100px; /* 笔记本小屏时不被压得过窄 */
|
|
|
padding: 10px;
|
|
|
border: 2px solid #e4e7ed;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
background: white;
|
|
|
- flex-shrink: 0; /* 防止被压缩 */
|
|
|
+ flex-shrink: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
@@ -2428,7 +2589,7 @@ defineExpose({
|
|
|
.template-thumbnail img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
- object-fit: cover;
|
|
|
+ object-fit: contain;
|
|
|
}
|
|
|
|
|
|
.image-container {
|