|
|
@@ -1,2725 +0,0 @@
|
|
|
-<template>
|
|
|
- <div>
|
|
|
- <layout>
|
|
|
- <layout-header>
|
|
|
- <!-- 搜索区域 -->
|
|
|
- <el-form inline>
|
|
|
- <el-form-item>
|
|
|
- <el-input v-model="searchInfo" placeholder="搜索关键字" clearable style="width: 300px;" />
|
|
|
- <el-button type="primary" icon="search" @click="onSubmit" title="搜索查询">查询</el-button>
|
|
|
- <el-button type="primary" @click="onADD" >新增产品</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </layout-header>
|
|
|
-
|
|
|
- <layout>
|
|
|
- <!-- 左侧树形结构 -->
|
|
|
- <template v-if="userStore.userInfo.nickName === '超级管理员'">
|
|
|
- <layout-sider :resize-directions="['right']" :width="290" style="margin-right: 10px">
|
|
|
- <div class="JKWTree-tree" style="height: 200px;">
|
|
|
- <h3>商户名称</h3>
|
|
|
- <el-tree :data="treeData" class="treecolor" @node-click="handleNodeClick"></el-tree>
|
|
|
- </div>
|
|
|
- </layout-sider>
|
|
|
- </template>
|
|
|
- <!-- 右侧区域 -->
|
|
|
- <layout-content>
|
|
|
- <el-main style="padding: 0;">
|
|
|
- <div class="gva-table-box">
|
|
|
- <!-- 表格展示 -->
|
|
|
- <el-table
|
|
|
- ref="multipleTable"
|
|
|
- style="width: 100%; height: 62vh;"
|
|
|
- :row-style="{ height: '20px' }"
|
|
|
- :header-cell-style="{ padding: '0px' }"
|
|
|
- :cell-style="{ padding: '0px' }"
|
|
|
- :header-row-style="{ height: '20px' }"
|
|
|
- border tooltip-effect="dark"
|
|
|
- :data="tableData1"
|
|
|
- row-key="ID"
|
|
|
- highlight-current-row
|
|
|
- :cell-class-name="tableDataCellClass"
|
|
|
- @selection-change="SelectionChange"
|
|
|
- @row-dblclick="onRowDblClick"
|
|
|
- :show-overflow-tooltip="true">
|
|
|
- <el-table-column type="selection" width="55" />
|
|
|
- <el-table-column label="ID序号" prop="id" width="70" />
|
|
|
- <el-table-column label="产品名称" prop="产品名称" width="300" />
|
|
|
- <el-table-column label="产品编码" prop="产品编码" width="300" />
|
|
|
- <el-table-column label="产品图片" width="120">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-image
|
|
|
- :src="formatImageUrl(row.产品图片)"
|
|
|
- :preview-src-list="[formatImageUrl(row.产品图片)]"
|
|
|
- style="width: 90px; height: 70px;"
|
|
|
- fit="contain"
|
|
|
- preview-teleported
|
|
|
- />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="效果图" width="120">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-image
|
|
|
- :src="formatImageUrl(row.产品效果图)"
|
|
|
- :preview-src-list="[formatImageUrl(row.产品效果图)]"
|
|
|
- style="width: 90px; height: 70px;"
|
|
|
- fit="contain"
|
|
|
- preview-teleported>
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #f5f7fa; color: #909399; font-size: 12px;">
|
|
|
- <el-icon><Picture /></el-icon>
|
|
|
- <span>暂无效果图</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="创建时间" prop="创建时间" width="300" />
|
|
|
- <el-table-column label="创建人" prop="创建人" width="300" />
|
|
|
- </el-table>
|
|
|
-
|
|
|
- <div class="gva-pagination">
|
|
|
- <el-pagination
|
|
|
- @size-change="handleSizeChange"
|
|
|
- @current-change="handleCurrentChange"
|
|
|
- :current-page="page"
|
|
|
- :page-sizes="[10, 30, 50, 100]"
|
|
|
- :page-size="pageSize"
|
|
|
- layout="total, sizes, prev, pager, next, jumper"
|
|
|
- :total="total"/>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-main>
|
|
|
- </layout-content>
|
|
|
- </layout>
|
|
|
- </layout>
|
|
|
-
|
|
|
-<el-dialog v-model="editDialogVisible" title="" fullscreen :modal="true" :show-close="false" @close="handleDialogClose">
|
|
|
- <!-- 关闭按钮 -->
|
|
|
- <div style="position: absolute; top: 10px; right: 20px; z-index: 1000;">
|
|
|
- <el-button type="danger" size="large" @click="editDialogVisible = false">
|
|
|
- <el-icon><Close /></el-icon>关闭
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- <div class="image-edit-container">
|
|
|
- <!-- 左侧:原图新图 + 输入框 -->
|
|
|
- <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">
|
|
|
- <!-- 原图区域 -->
|
|
|
- <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>
|
|
|
- <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)]"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- preview-teleported/>
|
|
|
- </div>
|
|
|
- <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>
|
|
|
-
|
|
|
- <!-- 商品图例、案例:右侧一列,与图片等高上下对齐,不用固定 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'" />
|
|
|
- </el-icon>
|
|
|
- </template>
|
|
|
- 案例
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 输入框区域 -->
|
|
|
- <div class="edit-section" style="border: 1px solid #e4e7ed; border-radius: 4px; display: flex; flex-direction: column; position: relative;">
|
|
|
- <el-form :model="editFormData" label-width="80px" style="flex: 1; display: flex; flex-direction: column; min-height: 0;">
|
|
|
- <el-input
|
|
|
- type="textarea"
|
|
|
- v-model="editFormData.chinese_description"
|
|
|
- :rows="14"
|
|
|
- placeholder="点击右侧模板图片可自动填充描述内容"
|
|
|
- show-word-limit
|
|
|
- maxlength="500"
|
|
|
- style="width: 100%; resize: none;"
|
|
|
- :disabled="loadingStatus"
|
|
|
- />
|
|
|
- </el-form>
|
|
|
- <!-- 加载遮罩 -->
|
|
|
- <div v-if="loadingStatus" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.8); display: flex; align-items: center; justify-content: center; border-radius: 4px; z-index: 10;">
|
|
|
- <el-icon style="font-size: 24px; color: #409eff;"><Loading /></el-icon>
|
|
|
- <span style="margin-left: 10px; color: #409eff;">正在生成中...</span>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 按钮放在表单下方 -->
|
|
|
- <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #e4e7ed;">
|
|
|
- <!-- 尺寸选择区域 -->
|
|
|
- <div style="margin-bottom: 15px;">
|
|
|
- <div style="display: flex; gap: 15px; flex-wrap: wrap; align-items: center;">
|
|
|
- <div v-for="ratio in [ '1:1','4:3','3:2','2:3','16:9']" :key="ratio" style="display: flex; align-items: center; gap: 5px;">
|
|
|
- <el-radio v-model="selectedSize" :label="ratio" @change="handleSizeChange" border style="border-radius: 2px;">
|
|
|
- {{ ratio }}
|
|
|
- </el-radio>
|
|
|
- </div>
|
|
|
- <!-- <div style="display: flex; align-items: center; gap: 5px;">
|
|
|
- <el-radio v-model="selectedSize" label="custom" @change="handleSizeChange" border style="border-radius: 2px;">
|
|
|
- 自由尺寸
|
|
|
- </el-radio>
|
|
|
- </div> -->
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 自由尺寸的像素输入 -->
|
|
|
- <div v-if="selectedSize === 'custom'" style="display: flex; align-items: center; gap: 12px; margin-top: 15px; padding-top: 10px; border-top: 1px dashed #ebeef5;">
|
|
|
- <span style="font-size: 14px; color: #606266; white-space: nowrap;">自定义尺寸:</span>
|
|
|
- <el-input
|
|
|
- v-model="customWidth"
|
|
|
- size="small"
|
|
|
- style="width: 100px;"
|
|
|
- placeholder="宽度"
|
|
|
- @input="validateCustomSize">
|
|
|
- <template #append>px</template>
|
|
|
- </el-input>
|
|
|
- <span style="color: #909399;">×</span>
|
|
|
- <el-input
|
|
|
- v-model="customHeight"
|
|
|
- size="small"
|
|
|
- style="width: 100px;"
|
|
|
- placeholder="高度"
|
|
|
- @input="validateCustomSize">
|
|
|
- <template #append>px</template>
|
|
|
- </el-input>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 显示当前尺寸信息 -->
|
|
|
- <div v-if="selectedSize !== 'custom'" style="font-size: 13px; color: #909399; margin-top: 10px; padding-top: 10px; border-top: 1px dashed #ebeef5;">
|
|
|
- 当前尺寸: {{ getSizeInfo(selectedSize) }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 操作按钮 -->
|
|
|
- <div style="display: flex; justify-content: flex-end; gap: 12px;">
|
|
|
- <!-- <el-button size="medium" @click="clearInput">清空</el-button> -->
|
|
|
- <el-button size="medium" type="success" @click="optimizeContent" :loading="loadingStatus || pollStatus === 'optimizing'">
|
|
|
- {{ loadingStatus || pollStatus === 'optimizing' ? '正在生成中' : '扩写提示词' }} <span style="margin-left: 8px;">⚡ 25 <span style="font-size: 12px;"></span></span>
|
|
|
- </el-button>
|
|
|
- <el-button size="medium" type="primary" @click="generateImage" :loading="loadingStatus || pollStatus === 'polling'">
|
|
|
- {{ loadingStatus || pollStatus === 'polling' ? '正在生成中' : '立即生成' }} <span style="margin-left: 8px;">⚡ 150 <span style="font-size: 12px;"></span></span>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 中间:产品信息和历史记录(保证两侧边框可见,不与其他列重叠) -->
|
|
|
-<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="danger" size="small" @click="toggleHistoryPanel">
|
|
|
- <el-icon><Close /></el-icon>关闭
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 模板分类树 -->
|
|
|
- <el-tree
|
|
|
- :data="templateTreeData"
|
|
|
- node-key="id"
|
|
|
- default-expand-all
|
|
|
- @node-click="handleTemplateTreeClick"
|
|
|
- style="margin-bottom: 20px;"
|
|
|
- >
|
|
|
- <template #default="{ node, data }">
|
|
|
- <span>{{ data.label }}</span>
|
|
|
- <span style="margin-left: 10px; font-size: 12px; color: #909399;">({{ data.count }}张)</span>
|
|
|
- </template>
|
|
|
- </el-tree>
|
|
|
-
|
|
|
- <!-- 选中模板的历史图片 -->
|
|
|
- <div>
|
|
|
- <h5 style="margin: 0 0 10px 0; font-size: 13px; font-weight: bold;">
|
|
|
- {{ currentTemplateName || '历史图片' }}
|
|
|
- </h5>
|
|
|
- <div class="history-images-list" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px;">
|
|
|
- <div v-for="(image, index) in filteredImages" :key="index" class="history-image-item" style="border: 1px solid #e4e7ed; border-radius: 4px; overflow: hidden; padding: 10px;">
|
|
|
- <div style="height: 120px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden;">
|
|
|
- <el-image
|
|
|
- :src="formatImageUrl(image.url)"
|
|
|
- :preview-src-list="[formatImageUrl(image.url)]"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- preview-teleported
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div style="margin-top: 10px; font-size: 12px; color: #606266; line-height: 1.4; height: 50px; overflow: hidden;">
|
|
|
- {{ image.product_content || '' }}
|
|
|
- </div>
|
|
|
- <div style="margin-top: 5px; font-size: 11px; color: #909399; text-align: right;">
|
|
|
- {{ image.createTime || '' }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div v-if="filteredImages.length === 0" style="text-align: center; padding: 40px; color: #909399; font-size: 14px; grid-column: 1 / -1;">
|
|
|
- 暂无历史图片
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 标题 -->
|
|
|
- <div style="font-size: 18px; font-weight: bold; color: white; margin-bottom: 15px; background-color: #404040; padding: 10px; border-radius: 4px; text-align: center; display: flex; justify-content: space-between; align-items: center;">
|
|
|
- <span>③效果图</span>
|
|
|
- <el-button type="primary" size="small" @click="toggleHistoryPanel">
|
|
|
- {{ showHistoryPanel ? '隐藏历史记录' : '显示历史记录' }}
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 主内容区域 -->
|
|
|
- <div style="display: flex; height: calc(100% - 90px);">
|
|
|
- <!-- 左侧:历史记录面板 (已移至覆盖层) -->
|
|
|
-
|
|
|
- <!-- 右侧:原始内容 -->
|
|
|
- <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;">
|
|
|
- <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="toggleProductExample">
|
|
|
- <el-icon><Close /></el-icon>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p style="margin: 0 0 20px 0; color: #606266; line-height: 1.5; font-size: 14px;">
|
|
|
- 建议上传商品单一、完整清晰、占据画面中心的白底图,否则可能会影响商品识别效果和生成的背景图质量。
|
|
|
- </p>
|
|
|
- <!-- 商品图例对比 -->
|
|
|
- <div style="margin-bottom: 20px;">
|
|
|
- <div style="display: flex; gap: 15px;">
|
|
|
- <!-- 正确示例 -->
|
|
|
- <div style="flex: 1; background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); border: 1px solid #e4e7ed;">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
|
- <el-icon :size="14" style="color: #67c23a; margin-right: 8px;"><SuccessFilled /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133; font-size: 13px;">商品单一</span>
|
|
|
- </div>
|
|
|
- <div style="height: 120px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/001.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 错误示例 -->
|
|
|
- <div style="flex: 1; background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); border: 1px solid #e4e7ed;">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
|
- <el-icon :size="14" style="color: #f56c6c; margin-right: 8px;"><Close /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133; font-size: 13px;">画面包含多件商品</span>
|
|
|
- </div>
|
|
|
- <div style="height: 120px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/002.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div style="margin-bottom: 20px;">
|
|
|
- <div style="display: flex; gap: 15px;">
|
|
|
- <!-- 正确示例 -->
|
|
|
- <div style="flex: 1; background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); border: 1px solid #e4e7ed;">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
|
- <el-icon :size="14" style="color: #67c23a; margin-right: 8px;"><SuccessFilled /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133; font-size: 13px;">画面完整清晰</span>
|
|
|
- </div>
|
|
|
- <div style="height: 120px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/003.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 错误示例 -->
|
|
|
- <div style="flex: 1; background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); border: 1px solid #e4e7ed;">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
|
- <el-icon :size="14" style="color: #f56c6c; margin-right: 8px;"><Close /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133; font-size: 13px;">商品残缺/遮挡</span>
|
|
|
- </div>
|
|
|
- <div style="height: 120px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/004.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div style="margin-bottom: 20px;">
|
|
|
- <div style="display: flex; gap: 15px;">
|
|
|
- <!-- 正确示例 -->
|
|
|
- <div style="flex: 1; background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); border: 1px solid #e4e7ed;">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
|
- <el-icon :size="14" style="color: #67c23a; margin-right: 8px;"><SuccessFilled /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133; font-size: 13px;">商品占据画面中心</span>
|
|
|
- </div>
|
|
|
- <div style="height: 120px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/005.jpg`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 错误示例 -->
|
|
|
- <div style="flex: 1; background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); border: 1px solid #e4e7ed;">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
|
- <el-icon :size="14" style="color: #f56c6c; margin-right: 8px;"><Close /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133; font-size: 13px;">商铺主题不突出</span>
|
|
|
- </div>
|
|
|
- <div style="height: 120px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/006.jpg`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 案例覆盖层 -->
|
|
|
- <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;">
|
|
|
- <h3 style="margin: 0; font-size: 16px; font-weight: bold; color: #303133;">合成商品图例</h3>
|
|
|
- <el-button type="text" size="small" @click="toggleCaseExample">
|
|
|
- <el-icon><Close /></el-icon>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div style="display: flex; margin-bottom: 15px; padding: 10px 0; border-bottom: 1px solid #e4e7ed;">
|
|
|
- <div style="flex: 1; font-weight: 600; color: #303133; font-size: 14px; text-align: center;">原图</div>
|
|
|
- <div style="width: 40px;"></div>
|
|
|
- <div style="flex: 1; font-weight: 500; color: #606266; font-size: 14px; text-align: center;">标准模式</div>
|
|
|
- <div style="width: 40px;"></div>
|
|
|
- <div style="flex: 1; font-weight: 500; color: #606266; font-size: 14px; text-align: center;">专业模式</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 案例图片网格 -->
|
|
|
- <div style="display: flex; flex-direction: column; gap: 15px;">
|
|
|
- <!-- 案例1 -->
|
|
|
- <div style="display: flex; align-items: center; gap: 10px;">
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/007.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/008.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/009.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 案例2 -->
|
|
|
- <div style="display: flex; align-items: center; gap: 10px;">
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/010.jpg`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/011.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/012.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 案例3 -->
|
|
|
- <div style="display: flex; align-items: center; gap: 10px;">
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/013.jpg`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/014.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/015.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 案例4 -->
|
|
|
- <div style="display: flex; align-items: center; gap: 10px;">
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/016.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/017.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- <div style="width: 40px; display: flex; justify-content: center;">
|
|
|
- <el-icon style="color: #409eff;"><ArrowRight /></el-icon>
|
|
|
- </div>
|
|
|
- <div style="flex: 1; height: 80px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden; border: 1px solid #e4e7ed;">
|
|
|
- <el-image
|
|
|
- :src="`/src/assets/ai案例图/018.png`"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399; font-size: 12px;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div style="height: 100%; display: flex; flex-direction: column; width: 100%; min-width: 0; align-items: center;">
|
|
|
- <!-- 上:留出的空白区域 -->
|
|
|
- <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; width: 100%;">
|
|
|
- <!-- 产品图片显示(默认) -->
|
|
|
- <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
|
|
|
- :src="formatImageUrl(image.url)"
|
|
|
- :preview-src-list="[formatImageUrl(image.url)]"
|
|
|
- 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
|
|
|
- v-else-if="editFormData.new_image_url && showProductImage"
|
|
|
- :src="formatImageUrl(editFormData.new_image_url)"
|
|
|
- :preview-src-list="[formatImageUrl(editFormData.new_image_url)]"
|
|
|
- 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>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 中:商品信息表单(宽度 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>
|
|
|
- </div>
|
|
|
- <div class="product-info-item">
|
|
|
- <span class="product-info-label">宏伟编码:</span>
|
|
|
- <span class="product-info-value">{{ editFormData.code || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="product-info-item">
|
|
|
- <span class="product-info-label">产品名称:</span>
|
|
|
- <span class="product-info-value">{{ editFormData.product_name || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="product-info-item">
|
|
|
- <span class="product-info-label">品牌名称:</span>
|
|
|
- <span class="product-info-value">{{ editFormData.brand_name || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="product-info-item">
|
|
|
- <span class="product-info-label">产品规格:</span>
|
|
|
- <span class="product-info-value">{{ editFormData.specification || '-' }}</span>
|
|
|
- </div>
|
|
|
- <div class="product-info-item">
|
|
|
- <span class="product-info-label">单 位:</span>
|
|
|
- <span class="product-info-value">{{ editFormData.unit || '-' }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 下:空出来的部分 -->
|
|
|
- <div style="width: 100%; max-width: 430px; height: 61px;"></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</div>
|
|
|
-
|
|
|
- <!-- 右侧:模版列表(与中间列左右留白一致) -->
|
|
|
- <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>
|
|
|
-
|
|
|
- <!-- 搜索框 -->
|
|
|
- <div class="template-search" style="margin-bottom: 15px;">
|
|
|
- <el-input v-model="searchKeyword" placeholder="请描述你想搜索的模版关键字..." clearable @input="handleSearch" @clear="handleClearSearch">
|
|
|
- <template #prefix>
|
|
|
- <el-icon><Search /></el-icon>
|
|
|
- </template>
|
|
|
- <template #append>
|
|
|
- <el-button @click="handleSearch" >搜索</el-button>
|
|
|
- </template>
|
|
|
- </el-input>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 关键词搜索次数最多的 -->
|
|
|
-
|
|
|
-
|
|
|
- <!-- 模板列表 -->
|
|
|
- <div style="flex: 1; min-height: 0;">
|
|
|
- <h4 style="margin-bottom: 10px;margin: 0px 0px 20px 0px;padding: 0px;">模版分类 ({{ templateList.length }})</h4>
|
|
|
- <el-scrollbar style="height: 100%;">
|
|
|
- <div class="template-list">
|
|
|
- <!-- 搜索无结果提示 -->
|
|
|
- <div v-if="!searchLoading && searchKeyword && templateList.length === 0" class="empty-search">
|
|
|
- <el-icon :size="50"><Search /></el-icon>
|
|
|
- <p>未找到相关模板</p>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 模板项 -->
|
|
|
- <div v-for="template in templateList"
|
|
|
- :key="template.id" class="template-item"
|
|
|
- :class="{ 'active': currentTemplateId === template.id }"
|
|
|
- @click="selectTemplate(template)" >
|
|
|
- <div class="template-thumbnail" style="position: relative;">
|
|
|
- <div class="image-container" @click.stop="selectTemplate(template)">
|
|
|
- <el-image
|
|
|
- :src="template.template_image_url"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div class="thumbnail-error">
|
|
|
- <el-icon><Picture /></el-icon>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- <div class="zoom-icon" @click.stop="handleImageZoom(template.template_image_url)">
|
|
|
- <el-icon :size="20"><ZoomIn /></el-icon>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="template-id">模板 {{ template.id }}</div>
|
|
|
- <el-button
|
|
|
- type="primary"
|
|
|
- circle
|
|
|
- size="small"
|
|
|
- style="position: absolute; top: 10px; right: 10px; opacity: 0; transition: opacity 0.3s; z-index: 10;"
|
|
|
- @click.stop="downloadTemplateImage(template.template_image_url)"
|
|
|
- class="image-download-btn" >
|
|
|
- <el-icon><Download /></el-icon>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- <div class="template-desc" :title="template.chinese_description">
|
|
|
- {{ truncateText(template.chinese_description, 25) }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 初始无数据提示 -->
|
|
|
- <div v-if="!searchKeyword && !searchLoading && templateList.length === 0" class="empty-templates">
|
|
|
- <el-icon :size="40"><Picture /></el-icon>
|
|
|
- <p>暂无模板数据</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-scrollbar>
|
|
|
- <!-- 图片预览组件 -->
|
|
|
- <el-image-viewer
|
|
|
- v-if="previewVisible"
|
|
|
- :url-list="previewImageUrl ? [previewImageUrl] : []"
|
|
|
- :hide-on-click-modal="true"
|
|
|
- @close="previewVisible = false"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 商品图例右侧面板 (已移至中间列) -->
|
|
|
- <div v-if="false" class="product-example-panel" style="width: 420px; border-left: 1px solid #e4e7ed; padding: 20px; background: #ffffff; overflow-y: auto; box-shadow: -2px 0 10px rgba(0,0,0,0.05);">
|
|
|
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
|
|
- <h3 style="margin: 0; font-size: 16px; font-weight: bold; color: #303133;">商品图例</h3>
|
|
|
- <el-button type="text" size="small" @click="toggleProductExample">
|
|
|
- <el-icon><Close /></el-icon>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <p style="margin: 0 0 15px 0; color: #606266; line-height: 1.5;">
|
|
|
- 建议上传商品单一、完整清晰、占据画面中心的白底图,否则可能会影响商品识别效果和生成的背景图质量。
|
|
|
- </p>
|
|
|
-
|
|
|
- <!-- 商品单一 -->
|
|
|
- <div style="margin-bottom: 20px;">
|
|
|
- <div style="font-weight: 600; color: #303133; margin-bottom: 10px;">商品单一</div>
|
|
|
- <div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 10px;">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 8px;">
|
|
|
- <el-icon :size="16" style="color: #67c23a; margin-right: 8px;"><SuccessFilled /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133;">商品完整清晰</span>
|
|
|
- </div>
|
|
|
- <div style="height: 180px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden;">
|
|
|
- <el-image
|
|
|
- :src="'@/assets/ai案例图/001.png'"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 8px;">
|
|
|
- <el-icon :size="16" style="color: #67c23a; margin-right: 8px;"><SuccessFilled /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133;">商品占据画面中心</span>
|
|
|
- </div>
|
|
|
- <div style="height: 180px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden;">
|
|
|
- <el-image
|
|
|
- :src="'@/assets/ai案例图/002.png'"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 画面包含多件商品 -->
|
|
|
- <div style="margin-bottom: 20px;">
|
|
|
- <div style="font-weight: 600; color: #303133; margin-bottom: 10px;">画面包含多件商品</div>
|
|
|
- <div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 8px;">
|
|
|
- <el-icon :size="16" style="color: #f56c6c; margin-right: 8px;"><Close /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133;">不推荐</span>
|
|
|
- </div>
|
|
|
- <div style="height: 180px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden;">
|
|
|
- <el-image
|
|
|
- :src="'@/assets/ai案例图/003.png'"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 商品残缺/遮挡 -->
|
|
|
- <div style="margin-bottom: 20px;">
|
|
|
- <div style="font-weight: 600; color: #303133; margin-bottom: 10px;">商品残缺/遮挡</div>
|
|
|
- <div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);">
|
|
|
- <div style="display: flex; align-items: center; margin-bottom: 8px;">
|
|
|
- <el-icon :size="16" style="color: #e6a23c; margin-right: 8px;"><WarningFilled /></el-icon>
|
|
|
- <span style="font-weight: 500; color: #303133;">商品主体不突出</span>
|
|
|
- </div>
|
|
|
- <div style="height: 180px; display: flex; justify-content: center; align-items: center; background: #f9f9f9; border-radius: 4px; overflow: hidden;">
|
|
|
- <el-image
|
|
|
- :src="'@/assets/ai案例图/004.png'"
|
|
|
- style="width: 100%; height: 100%;"
|
|
|
- fit="contain"
|
|
|
- >
|
|
|
- <template #error>
|
|
|
- <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #909399;">
|
|
|
- <span>图片占位</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-image>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</el-dialog>
|
|
|
-
|
|
|
- <!-- 图片预览 -->
|
|
|
- <el-dialog v-model="dialogVisible" title="图片预览" width="70%" top="5vh" destroy-on-close>
|
|
|
- <div style="text-align: center;">
|
|
|
- <el-image :src="currentImageUrl" style="max-width: 100%; max-height: 70vh;" fit="contain" />
|
|
|
- </div>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <el-dialog
|
|
|
- v-model="AdddialogVisible"
|
|
|
- title="新增产品"
|
|
|
- width="600px"
|
|
|
- :close-on-click-modal="false"
|
|
|
- @close="handleClose"
|
|
|
- >
|
|
|
- <el-form
|
|
|
- ref="productFormRef"
|
|
|
- :model="productForm"
|
|
|
- label-width="100px"
|
|
|
- label-position="right"
|
|
|
- size="medium"
|
|
|
- >
|
|
|
- <!-- 产品名称 -->
|
|
|
- <el-form-item label="产品名称" prop="product_name">
|
|
|
- <el-input
|
|
|
- v-model="productForm.product_name"
|
|
|
- placeholder="请输入产品名称"
|
|
|
- clearable
|
|
|
- show-word-limit
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
-
|
|
|
- <!-- 产品编码 -->
|
|
|
- <el-form-item label="产品编码" prop="product_code">
|
|
|
- <el-input
|
|
|
- v-model="productForm.product_code"
|
|
|
- placeholder="请输入产品编码"
|
|
|
- clearable
|
|
|
- show-word-limit
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <!-- 商户 -->
|
|
|
- <el-form-item label="商户" prop="merchant_id">
|
|
|
- <el-input
|
|
|
- v-model="productForm.merchant_id"
|
|
|
- placeholder="请选择商户"
|
|
|
- style="width: 100%"
|
|
|
- clearable
|
|
|
- filterable
|
|
|
- >
|
|
|
- </el-input>
|
|
|
- </el-form-item>
|
|
|
-
|
|
|
- <!-- 产品图片 -->
|
|
|
- <el-form-item label="产品图片" prop="product_img">
|
|
|
- <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="add-dialog-upload-area"
|
|
|
- :show-file-list="false"
|
|
|
- :before-upload="handleAddDialogSelectImage"
|
|
|
- accept="image/jpeg,image/png,image/jpg,image/webp"
|
|
|
- >
|
|
|
- <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>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
-
|
|
|
- <template #footer>
|
|
|
- <span class="dialog-footer">
|
|
|
- <el-button @click="handleClose">取消</el-button>
|
|
|
- <el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
|
|
- 确定
|
|
|
- </el-button>
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script setup>
|
|
|
-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, Plus, Upload, Picture } from '@element-plus/icons-vue'
|
|
|
-import { Layout, LayoutHeader, LayoutSider, LayoutContent } from '@arco-design/web-vue'
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// 商品图例显示状态
|
|
|
-const productExampleVisible = ref(false)
|
|
|
-const caseExampleVisible = ref(false)
|
|
|
-
|
|
|
-// 图片历史记录相关状态
|
|
|
-const templateTreeData = ref([])
|
|
|
-const currentTemplateId = ref(null)
|
|
|
-const currentTemplateName = ref('')
|
|
|
-const filteredImages = ref([])
|
|
|
-const showHistoryPanel = ref(false)
|
|
|
-
|
|
|
-// 切换历史记录面板显示
|
|
|
-const toggleHistoryPanel = () => {
|
|
|
- showHistoryPanel.value = !showHistoryPanel.value
|
|
|
-}
|
|
|
-
|
|
|
-// 切换商品图例显示
|
|
|
-const toggleProductExample = () => {
|
|
|
- productExampleVisible.value = !productExampleVisible.value
|
|
|
- if (productExampleVisible.value) {
|
|
|
- caseExampleVisible.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 切换案例显示
|
|
|
-const toggleCaseExample = () => {
|
|
|
- caseExampleVisible.value = !caseExampleVisible.value
|
|
|
- if (caseExampleVisible.value) {
|
|
|
- productExampleVisible.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-const props = defineProps({
|
|
|
- editFormData: {
|
|
|
- type: Object,
|
|
|
- default: () => ({})
|
|
|
- }
|
|
|
-})
|
|
|
-//获取登录用户信息
|
|
|
-const userStore = useUserStore()
|
|
|
-const _username = ref('')
|
|
|
-_username.value = userStore.userInfo.userName + '/' + userStore.userInfo.nickName
|
|
|
-console.log('获取用户信息',_username.value)
|
|
|
-console.log('获取用户名称',userStore.userInfo.nickName)
|
|
|
-
|
|
|
-//获取服务器地址
|
|
|
-const baseUrl = ref('')
|
|
|
-const port = ref('')
|
|
|
-const full_url = ref('')
|
|
|
-// 从接口获取服务器地址
|
|
|
-const fetchServerUrl = async () => {
|
|
|
- try {
|
|
|
- const res = await GetHttpUrl()
|
|
|
- if (res.code === 0 && res.data && res.data.full_url) {
|
|
|
- full_url.value = res.data.full_url
|
|
|
- baseUrl.value = res.data.baseUrl
|
|
|
- port.value = res.data.port
|
|
|
- console.log('获取服务器地址',full_url.value)
|
|
|
- console.log('IP地址',baseUrl.value)
|
|
|
- console.log('端口',port.value)
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取服务器地址失败:', error)
|
|
|
- }
|
|
|
-}
|
|
|
-fetchServerUrl();
|
|
|
-
|
|
|
-const searchInfo = ref('')
|
|
|
-const tableData1 = ref([])
|
|
|
-const total = ref(0)
|
|
|
-const page = ref(1)
|
|
|
-const pageSize = ref(50)
|
|
|
-const dialogVisible = ref(false)
|
|
|
-const currentImageUrl = ref('')
|
|
|
-const width = ref('')
|
|
|
-const height = ref('')
|
|
|
-const isLoading = ref(false)
|
|
|
-const txttotxt_selectedOption = ref('gemini-2.0-flash')
|
|
|
-const selectedOption = ref('dall-e-3')
|
|
|
-const num = ref(1)
|
|
|
-const _parh = ref([])
|
|
|
-const folderList = ref([])
|
|
|
-const selectedFolder = ref('')
|
|
|
-
|
|
|
-// 左侧树形数据
|
|
|
-const treeData = ref([])
|
|
|
-
|
|
|
-const getTreeData = async () => {
|
|
|
- try {
|
|
|
- const data = await merchantGetab();
|
|
|
- console.log(data)
|
|
|
-
|
|
|
- treeData.value = data.data.map(item => {
|
|
|
- return {
|
|
|
- label: item.tab || item.merchant_name,
|
|
|
- value: item.merchant_code,
|
|
|
- // 如果需要原始数据可以保留
|
|
|
- originalData: item
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error(error)
|
|
|
- treeData.value = [] // 出错时清空数据
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-getTreeData();
|
|
|
-
|
|
|
-const defaultProps = {
|
|
|
- children: 'children',
|
|
|
- label: 'label'
|
|
|
-}
|
|
|
-
|
|
|
-const editDialogVisible = ref(false)
|
|
|
-const editFormData = reactive({
|
|
|
- id: '',
|
|
|
- chinese_description: '',
|
|
|
- english_description: '',
|
|
|
- new_image_url: '',
|
|
|
- new_video_url: '',
|
|
|
- imgtoimg_url: '',
|
|
|
- img_name: '',
|
|
|
- video_name: ''
|
|
|
-})
|
|
|
-
|
|
|
-// 定义文生图表单对象
|
|
|
-const form = reactive({
|
|
|
- chinese_description: ''
|
|
|
-})
|
|
|
-
|
|
|
-// 控制产品信息部分图片是否显示
|
|
|
-const showProductImage = ref(false)
|
|
|
-
|
|
|
-// 存储生成的多个新图
|
|
|
-const newImages = ref([])
|
|
|
-
|
|
|
-// 加载状态控制
|
|
|
-const loadingStatus = ref(false)
|
|
|
-
|
|
|
-const formatImageUrl = (path) => {
|
|
|
- if (!path) return ''
|
|
|
- // 检查path是否已经是完整的URL
|
|
|
- if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
|
- console.log('路径已经是完整的URL:', path)
|
|
|
- return path
|
|
|
- }
|
|
|
- // 确保生成的URL不会有双斜杠
|
|
|
- const baseUrl = full_url.value.replace(/\/$/, '') // 移除末尾的斜杠
|
|
|
- const cleanPath = path.replace(/^public\//, '').replace(/^\//, '') // 移除开头的斜杠
|
|
|
- const formattedUrl = `${baseUrl}/${cleanPath}`
|
|
|
- console.log('生成的完整URL:', { path, full_url: full_url.value, baseUrl, cleanPath, formattedUrl })
|
|
|
- return formattedUrl
|
|
|
-}
|
|
|
-
|
|
|
-// 点击图片时更新产品信息部分的图片和对应的product_content
|
|
|
-const updateProductImage = (imageUrl) => {
|
|
|
- editFormData.new_image_url = imageUrl
|
|
|
- showProductImage.value = true
|
|
|
- // 查找对应的图片对象,更新product_content到输入框
|
|
|
- const selectedImage = newImages.value.find(img => img.url === imageUrl)
|
|
|
- if (selectedImage && selectedImage.product_content) {
|
|
|
- editFormData.chinese_description = selectedImage.product_content
|
|
|
- }
|
|
|
- // ElMessage.success('图片已更新到产品信息区域')
|
|
|
-}
|
|
|
-
|
|
|
-const txttoimg_modelList = ref([]); // 存储所有模型数据
|
|
|
-const txtimgselectedModel = ref(''); // 当前选中的模型名称
|
|
|
-const usedId = ref(null); // 当前使用的模型ID(兼容旧代码)
|
|
|
-const selectedId = ref(null); // 用户选择的模型ID(兼容旧代码)
|
|
|
-const isGenerating = ref(false); // 文生图生成中状态
|
|
|
-const usedIds = ref({}); // 当前使用的模型ID集合
|
|
|
-const selectedIds = ref({}); // 用户选择的模型ID集合
|
|
|
-
|
|
|
-// 获取文生图模型列表
|
|
|
-const fetchTxtImgModels = async () => {
|
|
|
- try {
|
|
|
- const response = await txttoimg_moxing();
|
|
|
- txttoimg_modelList.value = response.data.models.wenshengtu;
|
|
|
- // 获取当前使用的模型ID
|
|
|
- usedIds.value = response.data.used_ids;
|
|
|
- const currentUsedId = usedIds.value.wenshengtu;
|
|
|
-
|
|
|
- // 设置默认选中的模型
|
|
|
- const defaultModel = txttoimg_modelList.value.find(item => item.id === currentUsedId);
|
|
|
- if (defaultModel) {
|
|
|
- txtimgselectedModel.value = defaultModel.txttoimg;
|
|
|
- usedId.value = defaultModel.id;
|
|
|
- selectedId.value = defaultModel.id;
|
|
|
- selectedIds.value.wenshengtu = defaultModel.id;
|
|
|
- selectedOption.value = defaultModel.txttoimg;
|
|
|
- }
|
|
|
-
|
|
|
- console.log('文生图模型列表:', txttoimg_modelList.value);
|
|
|
- console.log('当前使用的模型ID:', currentUsedId);
|
|
|
- console.log('默认选中的模型:', defaultModel);
|
|
|
- } catch (error) {
|
|
|
- console.error('获取文生图模型列表失败:', error);
|
|
|
- ElMessage.error('获取模型列表失败');
|
|
|
- }
|
|
|
-}
|
|
|
-fetchTxtImgModels();
|
|
|
-
|
|
|
-// const getTableData = async () => {
|
|
|
-// const res = await getTable({
|
|
|
-// search: searchInfo.value,
|
|
|
-// limit: pageSize.value,
|
|
|
-// page: page.value,
|
|
|
-// folder: selectedFolder.value
|
|
|
-// })
|
|
|
-// tableData1.value = res.data.list
|
|
|
-// total.value = res.data.total
|
|
|
-
|
|
|
-// // 更新文件夹列表
|
|
|
-// if (res.data.folder) {
|
|
|
-// folderList.value = res.data.folder
|
|
|
-// }
|
|
|
-// }
|
|
|
-
|
|
|
-const nodeid = ref('')
|
|
|
-// 处理树节点点击
|
|
|
-const handleNodeClick = async(node) => {
|
|
|
- console.log('点击的节点:', node.value);
|
|
|
- nodeid.value = node.value;
|
|
|
-
|
|
|
- const res = await productList({
|
|
|
- search: searchInfo.value,
|
|
|
- limit: pageSize.value,
|
|
|
- page: page.value,
|
|
|
- code: node.value
|
|
|
- });
|
|
|
-
|
|
|
- if (res.code === 0) {
|
|
|
- // 按照ID进行排序,保持数据顺序
|
|
|
- const sortedData = res.data.list.sort((b, a) => a.id - b.id);
|
|
|
- tableData1.value = sortedData;
|
|
|
- total.value = res.data.total;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// 当选择模型变化时
|
|
|
-const handleModelChange = (modelName) => {
|
|
|
- const model = txttoimg_modelList.value.find(item => item.txttoimg === modelName);
|
|
|
- if (model) {
|
|
|
- selectedId.value = model.id;
|
|
|
- selectedIds.value.wenshengtu = model.id;
|
|
|
- selectedOption.value = model.txttoimg;
|
|
|
- console.log('选择的模型ID:', model.id);
|
|
|
- }
|
|
|
-}
|
|
|
-// 设置默认模型
|
|
|
-const setActive = async (type) => {
|
|
|
- const id = selectedIds.value[type]
|
|
|
- if (id) {
|
|
|
- try {
|
|
|
- // 调用API设置默认模型
|
|
|
- const res = await txttoimg_update({ id: id,type:"wenshengtu"})
|
|
|
- if (res.code === 0) {
|
|
|
- usedIds.value[type] = id
|
|
|
- ElMessage.success('默认模型设置成功')
|
|
|
- } else {
|
|
|
- ElMessage.error(res.msg || '设置默认模型失败')
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('设置默认模型失败', error)
|
|
|
- ElMessage.error('设置默认模型失败')
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-// 处理模板分类树数据
|
|
|
-const processTemplateTreeData = (images) => {
|
|
|
- // 按template_id分组
|
|
|
- const templateMap = {}
|
|
|
- images.forEach(item => {
|
|
|
- if (item.template_id) {
|
|
|
- if (!templateMap[item.template_id]) {
|
|
|
- templateMap[item.template_id] = []
|
|
|
- }
|
|
|
- templateMap[item.template_id].push(item)
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- // 构建树结构数据
|
|
|
- const treeData = []
|
|
|
- Object.keys(templateMap).forEach(templateId => {
|
|
|
- treeData.push({
|
|
|
- id: templateId,
|
|
|
- label: `模板 ${templateId}`,
|
|
|
- count: templateMap[templateId].length
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- templateTreeData.value = treeData
|
|
|
-
|
|
|
- // 默认选中第一个模板
|
|
|
- if (treeData.length > 0) {
|
|
|
- currentTemplateId.value = treeData[0].id
|
|
|
- currentTemplateName.value = treeData[0].label
|
|
|
- filterImagesByTemplate(treeData[0].id)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 根据模板ID过滤图片
|
|
|
-const filterImagesByTemplate = (templateId) => {
|
|
|
- filteredImages.value = newImages.value.filter(image => image.template_id == templateId)
|
|
|
-}
|
|
|
-
|
|
|
-// 处理模板树点击事件
|
|
|
-const handleTemplateTreeClick = (data) => {
|
|
|
- currentTemplateId.value = data.id
|
|
|
- currentTemplateName.value = data.label
|
|
|
- filterImagesByTemplate(data.id)
|
|
|
-}
|
|
|
-
|
|
|
-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) {
|
|
|
- editFormData.original_image_url = detailResponse.data['产品图片']
|
|
|
- editFormData.new_image_url = detailResponse.data['产品效果图']
|
|
|
- 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)) {
|
|
|
- detailResponse.image.forEach(item => {
|
|
|
- if (item.product_new_img) {
|
|
|
- newImages.value.push({
|
|
|
- url: item.product_new_img,
|
|
|
- product_content: item.product_content || '',
|
|
|
- template_id: item.template_id,
|
|
|
- createTime: item.createTime
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- // 如果有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()
|
|
|
-}
|
|
|
-
|
|
|
-// 存储所有模板数据
|
|
|
-const templateList = ref([])
|
|
|
-
|
|
|
-// 获取所有模板
|
|
|
-const fetchTemplates = async () => {
|
|
|
- const response = await product_template()
|
|
|
- if (response.code === 0 && response.data) {
|
|
|
- templateList.value = response.data.map(item => ({
|
|
|
- id: item.id,
|
|
|
- template_image_url: item.template_image_url,
|
|
|
- chinese_description: item.chinese_description || '',
|
|
|
- english_description: item.english_description || ''
|
|
|
- }))
|
|
|
-
|
|
|
- // 默认选中第一个模板
|
|
|
- // if (templateList.value.length > 0) {
|
|
|
- // selectTemplate(templateList.value[0])
|
|
|
- // }
|
|
|
- if (newImages.value.length > 0) {
|
|
|
- updateProductImage(newImages.value[0].url)
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 选择模板
|
|
|
-const selectTemplate = (template) => {
|
|
|
- currentTemplateId.value = template.id
|
|
|
- editFormData.template_image_url = template.template_image_url
|
|
|
- editFormData.template_id = template.id
|
|
|
- editFormData.chinese_description = template.chinese_description
|
|
|
-}
|
|
|
-
|
|
|
-// 处理图片加载错误
|
|
|
-const handleTemplateImageError = (event) => {
|
|
|
- event.target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2YwZjBmMCIvPjx0ZXh0IHg9IjUwIiB5PSI1MCIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjEyIiBmaWxsPSIjY2NjIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+SW1hZ2UgRXJyb3I8L3RleHQ+PC9zdmc+'
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// 修改后的 selectTemplate 函数(如果需要保留按钮)
|
|
|
-const handleTemplateButton = () => {
|
|
|
- // 可以留空或给提示
|
|
|
- ElMessage.info('请直接点击上方的模板图片')
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// 图片预览相关
|
|
|
-const previewVisible = ref(false)
|
|
|
-const previewImageUrl = ref('')
|
|
|
-
|
|
|
-// 处理图片放大
|
|
|
-const handleImageZoom = (url) => {
|
|
|
- previewImageUrl.value = url
|
|
|
- previewVisible.value = true
|
|
|
-}
|
|
|
-
|
|
|
-// 下载模板图片
|
|
|
-const downloadTemplateImage = async (imageUrl) => {
|
|
|
- try {
|
|
|
- // 创建一个临时的a标签用于下载
|
|
|
- const link = document.createElement('a')
|
|
|
- link.href = imageUrl
|
|
|
- // 设置文件名,从URL中提取或使用默认名
|
|
|
- const fileName = imageUrl.split('/').pop() || `template_${Date.now()}.jpg`
|
|
|
- link.download = fileName
|
|
|
- // 触发下载
|
|
|
- document.body.appendChild(link)
|
|
|
- link.click()
|
|
|
- // 清理
|
|
|
- document.body.removeChild(link)
|
|
|
- ElMessage.success('图片下载成功')
|
|
|
- } catch (error) {
|
|
|
- console.error('图片下载失败:', error)
|
|
|
- ElMessage.error('图片下载失败,请稍后重试')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 截断文本函数
|
|
|
-const truncateText = (text, length) => {
|
|
|
- if (!text) return ''
|
|
|
- if (text.length <= length) return text
|
|
|
- return text.substring(0, length) + '...'
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// 清空输入框内容
|
|
|
-const clearInput = () => {
|
|
|
- editFormData.chinese_description = ''
|
|
|
- ElMessage.success('已清空输入框内容')
|
|
|
-}
|
|
|
-
|
|
|
-// 内容优化(使用轮询机制)
|
|
|
-const optimizeContent = async () => {
|
|
|
- try {
|
|
|
- if (!editFormData.chinese_description) {
|
|
|
- ElMessage.warning('请输入要优化的内容')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // 设置加载状态
|
|
|
- loadingStatus.value = true
|
|
|
-
|
|
|
- // 重置状态
|
|
|
- pollStatus.value = 'optimizing'
|
|
|
- pollCount.value = 0
|
|
|
-
|
|
|
- // 第一步:调用生成接口触发生成任务
|
|
|
- const generateResponse = await GetTxtToTxt({
|
|
|
- status_val: '文生文',
|
|
|
- prompt: editFormData.chinese_description,
|
|
|
- model: 'gemini-3-pro-preview',
|
|
|
- id: editFormData.id,
|
|
|
- path: editFormData.original_image_url,
|
|
|
- })
|
|
|
-
|
|
|
- if (generateResponse.code === 0) {
|
|
|
- ElMessage.success('内容优化任务已开始,请稍候...')
|
|
|
-
|
|
|
- // 清除之前的定时器
|
|
|
- if (pollInterval.value) {
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- }
|
|
|
-
|
|
|
- // 开始轮询查询结果
|
|
|
- pollInterval.value = setInterval(async () => {
|
|
|
- pollCount.value++
|
|
|
-
|
|
|
- try {
|
|
|
- // 调用查询接口
|
|
|
- const checkResponse = await GetProductFind({ id: editFormData.id })
|
|
|
-
|
|
|
- if (checkResponse.code === 0 && checkResponse.data) {
|
|
|
- // 检查是否有content字段
|
|
|
- if (checkResponse.data.content) {
|
|
|
- // 停止轮询
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- pollInterval.value = null
|
|
|
- pollStatus.value = 'success'
|
|
|
- loadingStatus.value = false
|
|
|
-
|
|
|
- // 更新内容到输入框
|
|
|
- editFormData.chinese_description = checkResponse.data.content
|
|
|
- ElMessage.success(`内容优化成功!耗时约${pollCount.value * 10}秒`)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 如果轮询超过30次(5分钟),停止轮询
|
|
|
- if (pollCount.value >= 30) {
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- pollInterval.value = null
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- ElMessage.warning('优化超时,请稍后手动检查结果')
|
|
|
- }
|
|
|
-
|
|
|
- } catch (pollError) {
|
|
|
- console.error('优化轮询查询失败:', pollError)
|
|
|
- // 轮询失败继续尝试
|
|
|
- }
|
|
|
- }, 5000) // 每5秒轮询一次
|
|
|
-
|
|
|
- } else {
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- ElMessage.error('优化任务启动失败: ' + (generateResponse.msg || '未知错误'))
|
|
|
- }
|
|
|
-
|
|
|
- } catch (generateError) {
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- console.error('内容优化失败:', generateError)
|
|
|
- ElMessage.error('内容优化失败: ' + (generateError.message || '未知错误'))
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 在script中定义
|
|
|
-const pollStatus = ref(null) // 轮询状态:'polling', 'success', 'error'
|
|
|
-const pollCount = ref(0) // 轮询次数
|
|
|
-const pollInterval = ref(null) // 轮询定时器
|
|
|
-const task_id = ref('')
|
|
|
-// 生成图片(简化版,只做轮询)
|
|
|
-const generateImage = async () => {
|
|
|
- try {
|
|
|
- if (!editFormData.template_id) {
|
|
|
- ElMessage.warning('请先选择模版')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if (!editFormData.chinese_description) {
|
|
|
- ElMessage.warning('请输入描述内容')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- // 设置加载状态
|
|
|
- loadingStatus.value = true
|
|
|
-
|
|
|
- // 重置状态
|
|
|
- pollStatus.value = 'polling'
|
|
|
- pollCount.value = 0
|
|
|
- console.log(editFormData.template_id)
|
|
|
- // 第一步:调用生成接口触发生成任务
|
|
|
- const generateResponse = await GetTxtToTxt({
|
|
|
- status_val: '文生图',
|
|
|
- prompt: editFormData.chinese_description,
|
|
|
- model: 'gemini-3-pro-image-preview',
|
|
|
- id: editFormData.id,
|
|
|
- size: selectedSize.value,
|
|
|
- template_id:editFormData.template_id,
|
|
|
- })
|
|
|
-
|
|
|
- if (generateResponse.code === 0) {
|
|
|
- ElMessage.success('图片生成任务已开始,请稍候...')
|
|
|
- task_id.value = generateResponse.data.task_id
|
|
|
-
|
|
|
-
|
|
|
- // 稍后在轮询中使用GetImageStatus接口
|
|
|
-
|
|
|
- // 清除之前的定时器
|
|
|
- if (pollInterval.value) {
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- }
|
|
|
-
|
|
|
- // 开始轮询查询结果
|
|
|
- pollInterval.value = setInterval(async () => {
|
|
|
- pollCount.value++
|
|
|
-
|
|
|
- try {
|
|
|
- // 调用GetImageStatus接口查询生成状态
|
|
|
- const statusResponse = await GetImageStatus({ task_id: task_id.value })
|
|
|
-
|
|
|
- if (statusResponse.code === 0 && statusResponse.data) {
|
|
|
- // 如果获取到数据且有图片URL
|
|
|
- if (statusResponse.data.image_url) {
|
|
|
- // 停止轮询
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- pollInterval.value = null
|
|
|
- pollStatus.value = 'success'
|
|
|
- loadingStatus.value = false
|
|
|
-
|
|
|
- // 更新新图URL
|
|
|
- const imageUrl = statusResponse.data.image_url
|
|
|
- editFormData.new_image_url = imageUrl
|
|
|
- editFormData.img_name = `生成图片_${new Date().getTime()}.jpg`
|
|
|
-
|
|
|
- // 更新历史记录
|
|
|
- const newImageItem = {
|
|
|
- url: imageUrl,
|
|
|
- product_content: editFormData.chinese_description || '',
|
|
|
- template_id: editFormData.template_id,
|
|
|
- createTime: statusResponse.data.completed_at || new Date().toISOString()
|
|
|
- }
|
|
|
-
|
|
|
- // 添加到newImages数组(走马灯轮播)
|
|
|
- newImages.value.unshift(newImageItem)
|
|
|
-
|
|
|
- // 处理模板分类树数据
|
|
|
- processTemplateTreeData([newImageItem])
|
|
|
-
|
|
|
- ElMessage.success(`图片生成成功!耗时约${pollCount.value * 5}秒`)
|
|
|
- } else if (statusResponse.data.error) {
|
|
|
- // 如果返回错误
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- pollInterval.value = null
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- ElMessage.error('图片生成失败: ' + statusResponse.data.error)
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 如果接口返回错误
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- pollInterval.value = null
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- ElMessage.error('查询生成状态失败: ' + (statusResponse.msg || '未知错误'))
|
|
|
- }
|
|
|
-
|
|
|
- // 如果轮询超过30次(2.5分钟),停止轮询
|
|
|
- if (pollCount.value >= 30) {
|
|
|
- clearInterval(pollInterval.value)
|
|
|
- pollInterval.value = null
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- ElMessage.warning('生成超时,请稍后手动检查结果')
|
|
|
- }
|
|
|
-
|
|
|
- } catch (pollError) {
|
|
|
- console.error('轮询查询失败:', pollError)
|
|
|
- // 轮询失败继续尝试
|
|
|
- }
|
|
|
- }, 5000) // 每5秒轮询一次
|
|
|
-
|
|
|
- } else {
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- ElMessage.error('生成任务启动失败: ' + (generateResponse.msg || '未知错误'))
|
|
|
- }
|
|
|
-
|
|
|
- } catch (generateError) {
|
|
|
- pollStatus.value = 'error'
|
|
|
- loadingStatus.value = false
|
|
|
- console.error('生成图片失败:', generateError)
|
|
|
- ElMessage.error('生成图片失败: ' + (generateError.message || '未知错误'))
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 尺寸相关的数据
|
|
|
-const selectedSize = ref('1:1') // 默认选择1:1
|
|
|
-const customWidth = ref('')
|
|
|
-const customHeight = ref('')
|
|
|
-const sizePresets = {
|
|
|
- '1:1': { width: 1024, height: 1024 },
|
|
|
- '4:3': { width: 1024, height: 768 },
|
|
|
- '3:2': { width: 1024, height: 683 },
|
|
|
- '2:3': { width: 683, height: 1024 },
|
|
|
- '9:16': { width: 675, height: 1200 },
|
|
|
- '16:9': { width: 1200, height: 675 },
|
|
|
- '21:9': { width: 1200, height: 514 },
|
|
|
- '3:4': { width: 768, height: 1024 }
|
|
|
-}
|
|
|
-
|
|
|
-// 获取尺寸信息
|
|
|
-const getSizeInfo = (size) => {
|
|
|
- if (size === 'custom') return '自定义尺寸'
|
|
|
- const preset = sizePresets[size]
|
|
|
- return `${preset.width}×${preset.height}`
|
|
|
-}
|
|
|
-
|
|
|
-// 尺寸变化处理
|
|
|
-const handleSizeChange = (value) => {
|
|
|
- const selectedValue = value || selectedSize.value
|
|
|
- if (selectedValue !== 'custom') {
|
|
|
- // 如果是预设尺寸,可以在这里执行相关操作
|
|
|
- console.log('选择了尺寸:', selectedValue, sizePresets[selectedValue])
|
|
|
- // 可以更新生成图片的参数
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 验证自定义尺寸
|
|
|
-const validateCustomSize = () => {
|
|
|
- // 确保输入的是数字
|
|
|
- if (customWidth.value && !/^\d+$/.test(customWidth.value)) {
|
|
|
- customWidth.value = customWidth.value.replace(/\D/g, '')
|
|
|
- }
|
|
|
- if (customHeight.value && !/^\d+$/.test(customHeight.value)) {
|
|
|
- customHeight.value = customHeight.value.replace(/\D/g, '')
|
|
|
- }
|
|
|
-
|
|
|
- // 限制最大最小值
|
|
|
- const maxSize = 4096
|
|
|
- const minSize = 64
|
|
|
-
|
|
|
- if (customWidth.value) {
|
|
|
- let width = parseInt(customWidth.value)
|
|
|
- if (width > maxSize) customWidth.value = maxSize.toString()
|
|
|
- if (width < minSize) customWidth.value = minSize.toString()
|
|
|
- }
|
|
|
-
|
|
|
- if (customHeight.value) {
|
|
|
- let height = parseInt(customHeight.value)
|
|
|
- if (height > maxSize) customHeight.value = maxSize.toString()
|
|
|
- if (height < minSize) customHeight.value = minSize.toString()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 获取当前选择的尺寸
|
|
|
-const getCurrentSize = () => {
|
|
|
- if (selectedSize.value === 'custom') {
|
|
|
- return {
|
|
|
- type: 'custom',
|
|
|
- width: customWidth.value ? parseInt(customWidth.value) : 1024,
|
|
|
- height: customHeight.value ? parseInt(customHeight.value) : 1024
|
|
|
- }
|
|
|
- } else {
|
|
|
- return {
|
|
|
- type: selectedSize.value,
|
|
|
- ...sizePresets[selectedSize.value]
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// 可选:添加常用尺寸按钮
|
|
|
-const quickSizes = [
|
|
|
- { label: '512×512', width: 512, height: 512 },
|
|
|
- { label: '768×768', width: 768, height: 768 },
|
|
|
- { label: '1024×1024', width: 1024, height: 1024 },
|
|
|
- { label: '1920×1080', width: 1920, height: 1080 }
|
|
|
-]
|
|
|
-
|
|
|
-const selectQuickSize = (size) => {
|
|
|
- selectedSize.value = 'custom'
|
|
|
- customWidth.value = size.width.toString()
|
|
|
- customHeight.value = size.height.toString()
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-const downloadImage = async (type) => {
|
|
|
- console.log('开始下载...')
|
|
|
-
|
|
|
- let imagePath = ''
|
|
|
- let originalFileName = ''
|
|
|
-
|
|
|
- if (type === 'original') {
|
|
|
- imagePath = editFormData.original_image_url
|
|
|
- originalFileName = editFormData.original_name || ''
|
|
|
-
|
|
|
- if (!imagePath) {
|
|
|
- ElMessage.warning('原图不存在')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- } else if (type === 'new') {
|
|
|
- imagePath = editFormData.new_image_url
|
|
|
- originalFileName = editFormData.img_name || ''
|
|
|
-
|
|
|
- if (!imagePath) {
|
|
|
- ElMessage.warning('新图不存在')
|
|
|
- return
|
|
|
- }
|
|
|
- } else {
|
|
|
- console.error('未知的下载类型:', type)
|
|
|
- ElMessage.error('未知的下载类型')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- console.log(`下载${type}图信息:`, { imagePath, originalFileName })
|
|
|
-
|
|
|
- // 获取完整URL
|
|
|
- const fullUrl = formatImageUrl(imagePath)
|
|
|
- console.log('完整下载URL:', fullUrl)
|
|
|
-
|
|
|
- // 生成安全的文件名
|
|
|
- const safeFileName = generateSafeFilename(originalFileName, imagePath, type)
|
|
|
- console.log('安全文件名:', safeFileName)
|
|
|
-
|
|
|
- try {
|
|
|
- // 调用下载
|
|
|
- await downloadWithBlob(fullUrl, safeFileName)
|
|
|
- console.log('下载函数调用成功')
|
|
|
- } catch (error) {
|
|
|
- console.error('下载函数调用失败:', error)
|
|
|
- ElMessage.error('下载过程中发生错误')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 生成安全的文件名(处理中文和特殊字符)
|
|
|
-const generateSafeFilename = (originalName, imagePath, type) => {
|
|
|
- // 如果有原始文件名,使用它(但需要处理中文)
|
|
|
- if (originalName && originalName.trim()) {
|
|
|
- // 如果文件名包含中文,转换为拼音或使用安全字符
|
|
|
- if (/[\u4e00-\u9fa5]/.test(originalName)) {
|
|
|
- // 方法1:直接使用中文(现代浏览器支持)
|
|
|
- // 方法2:转换为拼音(需要拼音库)或使用时间戳
|
|
|
- return encodeURIComponent(originalName) // 对中文进行编码
|
|
|
- }
|
|
|
- return originalName
|
|
|
- }
|
|
|
-
|
|
|
- // 从路径提取文件名
|
|
|
- const pathParts = imagePath.split('/')
|
|
|
- const lastPart = pathParts[pathParts.length - 1]
|
|
|
-
|
|
|
- if (lastPart && lastPart.includes('.')) {
|
|
|
- return lastPart
|
|
|
- }
|
|
|
-
|
|
|
- // 默认使用时间戳
|
|
|
- const timestamp = Date.now()
|
|
|
- const extension = getFileExtension(imagePath)
|
|
|
- return `${type}_${timestamp}.${extension}`
|
|
|
-}
|
|
|
-
|
|
|
-// 获取文件扩展名
|
|
|
-const getFileExtension = (filepath) => {
|
|
|
- if (!filepath) return 'jpg'
|
|
|
- const match = filepath.match(/\.([a-zA-Z0-9]+)(?:\?.*)?$/)
|
|
|
- return match ? match[1].toLowerCase() : 'jpg'
|
|
|
-}
|
|
|
-
|
|
|
-// 直接下载函数(备用方案)
|
|
|
-const directDownload = (url, filename) => {
|
|
|
- try {
|
|
|
- const link = document.createElement('a')
|
|
|
- link.href = url
|
|
|
- link.download = filename
|
|
|
- link.style.display = 'none'
|
|
|
- document.body.appendChild(link)
|
|
|
- link.click()
|
|
|
- document.body.removeChild(link)
|
|
|
- ElMessage.success('下载成功!')
|
|
|
- } catch (error) {
|
|
|
- console.error('直接下载失败:', error)
|
|
|
- throw error
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 添加时间戳避免缓存
|
|
|
-const addTimestamp = (url) => {
|
|
|
- if (!url) return url
|
|
|
- const separator = url.includes('?') ? '&' : '?'
|
|
|
- return `${url}${separator}t=${Date.now()}`
|
|
|
-}
|
|
|
-
|
|
|
-// 使用Blob下载(推荐,支持中文文件名)
|
|
|
-const downloadWithBlob = async (url, filename) => {
|
|
|
- try {
|
|
|
- console.log('开始Blob下载:', { url, filename })
|
|
|
-
|
|
|
- // 添加时间戳避免缓存
|
|
|
- const urlWithTimestamp = addTimestamp(url)
|
|
|
-
|
|
|
- const response = await fetch(urlWithTimestamp, {
|
|
|
- method: 'GET',
|
|
|
- headers: {
|
|
|
- 'Cache-Control': 'no-cache'
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
|
- }
|
|
|
-
|
|
|
- const blob = await response.blob()
|
|
|
-
|
|
|
- // 检查blob类型
|
|
|
- if (blob.size === 0) {
|
|
|
- throw new Error('文件内容为空')
|
|
|
- }
|
|
|
-
|
|
|
- // 创建blob URL
|
|
|
- const blobUrl = window.URL.createObjectURL(blob)
|
|
|
-
|
|
|
- // 创建下载链接
|
|
|
- const link = document.createElement('a')
|
|
|
- link.href = blobUrl
|
|
|
-
|
|
|
- // 处理文件名:现代浏览器支持中文文件名
|
|
|
- // 如果需要更兼容,可以编码文件名
|
|
|
- link.download = filename
|
|
|
- link.style.display = 'none'
|
|
|
-
|
|
|
- document.body.appendChild(link)
|
|
|
- link.click()
|
|
|
-
|
|
|
- // 清理
|
|
|
- setTimeout(() => {
|
|
|
- window.URL.revokeObjectURL(blobUrl)
|
|
|
- document.body.removeChild(link)
|
|
|
- }, 100)
|
|
|
-
|
|
|
- ElMessage.success('下载成功!')
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error('Blob下载失败:', error)
|
|
|
-
|
|
|
- // 备用方案1:直接下载
|
|
|
- try {
|
|
|
- console.log('尝试直接下载...')
|
|
|
- directDownload(url, filename)
|
|
|
- } catch (directError) {
|
|
|
- console.error('直接下载失败:', directError)
|
|
|
-
|
|
|
- // 备用方案2:使用更简单的下载方法
|
|
|
- try {
|
|
|
- console.log('尝试使用简单下载方法...')
|
|
|
- const link = document.createElement('a')
|
|
|
- link.href = url
|
|
|
- link.download = filename
|
|
|
- link.style.display = 'none'
|
|
|
- document.body.appendChild(link)
|
|
|
- link.click()
|
|
|
- document.body.removeChild(link)
|
|
|
- ElMessage.success('下载成功!')
|
|
|
- } catch (simpleError) {
|
|
|
- console.error('所有方法都失败:', simpleError)
|
|
|
- ElMessage.error('下载失败,请检查网络或联系管理员')
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const searchKeyword = ref('')
|
|
|
-const handleSearch = async () => {
|
|
|
- try {
|
|
|
- const response = await product_template({ search: searchKeyword.value })
|
|
|
- if (response.code === 0 && response.data) {
|
|
|
- templateList.value = response.data.map(item => ({
|
|
|
- id: item.id,
|
|
|
- template_image_url: item.template_image_url,
|
|
|
- chinese_description: item.chinese_description || '',
|
|
|
- english_description: item.english_description || ''
|
|
|
- }))
|
|
|
-
|
|
|
- // 默认选中第一个模板
|
|
|
- if (templateList.value.length > 0) {
|
|
|
- selectTemplate(templateList.value[0])
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('获取模板失败:', error)
|
|
|
- templateList.value = []
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-// 弹窗显示控制
|
|
|
-const AdddialogVisible = ref(false)
|
|
|
-const productFormRef = ref()
|
|
|
-const submitLoading = ref(false)
|
|
|
-// 新增产品:选中的图片文件(点确定时随 productAdd 一起提交,不单独调 ImgUpload)
|
|
|
-const productImageFile = ref(null)
|
|
|
-
|
|
|
-// 表单数据
|
|
|
-const productForm = reactive({
|
|
|
- product_name: '',
|
|
|
- product_code: '',
|
|
|
- create_name: '',
|
|
|
- merchant_id: '',
|
|
|
- product_img: '' // 预览用:blob URL 或空
|
|
|
-})
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-const onADD = async() => {
|
|
|
- if (!nodeid.value) {
|
|
|
- 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,
|
|
|
- })
|
|
|
- if (res.code === 0) {
|
|
|
- productForm.merchant_id = res.data
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-// 将 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()=>{
|
|
|
- if (!productImageFile.value) {
|
|
|
- ElMessage.warning('请先选择产品图片')
|
|
|
- return
|
|
|
- }
|
|
|
- productForm.create_name = userStore.userInfo.nickName
|
|
|
- try {
|
|
|
- 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 || response.msg || '新增失败')
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('新增失败:', error)
|
|
|
- ElMessage.error('新增失败,请联系管理员')
|
|
|
- } finally {
|
|
|
- submitLoading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 新增产品弹窗:预览图地址(blob 或后端返回的 URL)
|
|
|
-const addDialogPreviewSrc = computed(() => {
|
|
|
- const p = productForm.product_img
|
|
|
- if (!p) return ''
|
|
|
- if (p.startsWith('blob:')) return p
|
|
|
- return formatImageUrl(p)
|
|
|
-})
|
|
|
-
|
|
|
-// 新增产品:选择图片仅做本地预览,不调上传接口;点确定时随 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 默认上传
|
|
|
-}
|
|
|
-
|
|
|
-// 打开弹窗方法(外部调用)
|
|
|
-const openDialog = () => {
|
|
|
- // 清空表单
|
|
|
- Object.keys(productForm).forEach(key => {
|
|
|
- productForm[key] = ''
|
|
|
- })
|
|
|
-
|
|
|
- dialogVisible.value = true
|
|
|
-
|
|
|
- // 如果有默认值可以在这里设置
|
|
|
- // productForm.create_name = '默认创建人'
|
|
|
-}
|
|
|
-
|
|
|
-// 关闭新增产品弹窗并重置表单
|
|
|
-const handleClose = () => {
|
|
|
- if (productForm.product_img && productForm.product_img.startsWith('blob:')) {
|
|
|
- URL.revokeObjectURL(productForm.product_img)
|
|
|
- }
|
|
|
- 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
|
|
|
-}
|
|
|
-
|
|
|
-// 暴露方法给父组件
|
|
|
-defineExpose({
|
|
|
- openDialog
|
|
|
-})
|
|
|
-
|
|
|
-
|
|
|
-// onMounted(() => {
|
|
|
-// getTableData()
|
|
|
-// })
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped>
|
|
|
- /* 保持原有通用样式 */
|
|
|
- :deep(.el-table td .cell) {
|
|
|
- line-height: 22px !important;
|
|
|
- }
|
|
|
- /* :deep(.el-dialog__body) {
|
|
|
- padding: 10px 20px;
|
|
|
- } */
|
|
|
- .gva-pagination {
|
|
|
- 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;
|
|
|
- }
|
|
|
- .JKWTree-tree {
|
|
|
- background-color: #fff;
|
|
|
- padding: 10px;
|
|
|
- border-right: 1px solid #e4e7ed;
|
|
|
- }
|
|
|
- .JKWTree-tree h3 {
|
|
|
- font-size: 15px;
|
|
|
- font-weight: 700;
|
|
|
- margin: 10px 0;
|
|
|
- padding-bottom: 10px;
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
- }
|
|
|
-
|
|
|
- /* 编辑弹窗三列布局:自适应,小屏横向滚动防跑偏 */
|
|
|
- .image-edit-container {
|
|
|
- display: flex;
|
|
|
- height: 94vh;
|
|
|
- padding-top: 20px;
|
|
|
- gap: 0;
|
|
|
- width: 100%;
|
|
|
- overflow-x: auto;
|
|
|
- overflow-y: hidden;
|
|
|
- box-sizing: border-box;
|
|
|
- }
|
|
|
-
|
|
|
- .left-column {
|
|
|
- flex: 0.7;
|
|
|
- min-width: 280px;
|
|
|
- max-width: 560px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- 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;
|
|
|
- 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;
|
|
|
- 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 {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- min-height: 0; /* 防止溢出 */
|
|
|
- }
|
|
|
-
|
|
|
- .image-title {
|
|
|
- font-weight: bold;
|
|
|
- margin-bottom: 10px;
|
|
|
- color: #409eff;
|
|
|
- }
|
|
|
-
|
|
|
- .image-preview {
|
|
|
- flex: 1;
|
|
|
- overflow: hidden;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- min-height: 0; /* 防止溢出 */
|
|
|
- }
|
|
|
-
|
|
|
- .image-preview img {
|
|
|
- width: 100%;
|
|
|
- height: 160px; /* 固定高度,确保不占满屏幕 */
|
|
|
- object-fit: contain;
|
|
|
- background: #f5f7fa;
|
|
|
- }
|
|
|
-
|
|
|
- .image-placeholder {
|
|
|
- height: 160px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- background: #f5f7fa;
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
-
|
|
|
- .image-info {
|
|
|
- padding: 8px;
|
|
|
- background: #f5f7fa;
|
|
|
- font-size: 12px;
|
|
|
- color: #606266;
|
|
|
- border-top: 1px solid #dcdfe6;
|
|
|
- }
|
|
|
-
|
|
|
- .image-actions {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- padding: 10px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .edit-section {
|
|
|
- overflow: hidden; /* 防止溢出 */
|
|
|
- }
|
|
|
-
|
|
|
- .edit-form {
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- .edit-form :deep(.el-form) {
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- .edit-form :deep(.el-form-item) {
|
|
|
- height: 100%;
|
|
|
- margin-bottom: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .edit-form :deep(.el-textarea__inner) {
|
|
|
- height: 100% !important;
|
|
|
- resize: none;
|
|
|
- }
|
|
|
-
|
|
|
- :deep(.el-textarea__inner) {
|
|
|
- resize: none !important;
|
|
|
- }
|
|
|
-
|
|
|
- .right-column {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- min-height: 0; /* 防止溢出 */
|
|
|
- }
|
|
|
-
|
|
|
- .right-template {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- min-height: 0; /* 防止溢出 */
|
|
|
- }
|
|
|
-
|
|
|
- .template-header {
|
|
|
- margin-bottom: 15px;
|
|
|
- padding-bottom: 10px;
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
- }
|
|
|
-
|
|
|
- .template-header h4 {
|
|
|
- margin: 0;
|
|
|
- font-size: 16px;
|
|
|
- }
|
|
|
-
|
|
|
- .template-list-container {
|
|
|
- flex: 1;
|
|
|
- min-height: 0;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- }
|
|
|
-
|
|
|
- .template-list {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 10px;
|
|
|
- overflow-y: auto;
|
|
|
- min-height: 0; /* 防止溢出 */
|
|
|
- padding: 5px;
|
|
|
- }
|
|
|
-
|
|
|
- .template-item {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- 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;
|
|
|
- box-sizing: border-box;
|
|
|
- }
|
|
|
-
|
|
|
- .template-item:hover {
|
|
|
- border-color: #409eff;
|
|
|
- background: #f5faff;
|
|
|
- }
|
|
|
-
|
|
|
- .template-item.active {
|
|
|
- border-color: #409eff;
|
|
|
- background: #ecf5ff;
|
|
|
- border-width: 2px;
|
|
|
- }
|
|
|
-
|
|
|
- .template-thumbnail {
|
|
|
- position: relative;
|
|
|
- width: 100%;
|
|
|
- height: 120px;
|
|
|
- margin-bottom: 8px;
|
|
|
- border-radius: 4px;
|
|
|
- overflow: hidden;
|
|
|
- background: #f5f7fa;
|
|
|
- }
|
|
|
-
|
|
|
- .template-thumbnail img {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- object-fit: contain;
|
|
|
- }
|
|
|
-
|
|
|
- .image-container {
|
|
|
- position: relative;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
-
|
|
|
- .zoom-icon {
|
|
|
- position: absolute;
|
|
|
- bottom: 8px;
|
|
|
- right: 8px;
|
|
|
- width: 28px;
|
|
|
- height: 28px;
|
|
|
- background: rgba(0, 0, 0, 0.6);
|
|
|
- color: white;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- cursor: zoom-in;
|
|
|
- opacity: 0;
|
|
|
- transition: opacity 0.3s ease;
|
|
|
- }
|
|
|
-
|
|
|
- .image-container:hover .zoom-icon {
|
|
|
- opacity: 1;
|
|
|
- }
|
|
|
-
|
|
|
- /* 商品信息项样式 */
|
|
|
- .product-info-item {
|
|
|
- margin-bottom: 10px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
-
|
|
|
- .product-info-label {
|
|
|
- font-size: 14px;
|
|
|
- color: #000000;
|
|
|
- font-weight: bold;
|
|
|
- width: 90px;
|
|
|
- text-align: left;
|
|
|
- }
|
|
|
-
|
|
|
- .product-info-value {
|
|
|
- font-size: 14px;
|
|
|
- color: #000000;
|
|
|
- flex: 1;
|
|
|
- text-align: left;
|
|
|
- word-break: break-all;
|
|
|
- }
|
|
|
-
|
|
|
- .template-id {
|
|
|
- position: absolute;
|
|
|
- top: 4px;
|
|
|
- left: 4px;
|
|
|
- background: rgba(0, 0, 0, 0.7);
|
|
|
- color: white;
|
|
|
- padding: 2px 6px;
|
|
|
- border-radius: 3px;
|
|
|
- font-size: 11px;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .image-preview .image-download-btn {
|
|
|
- opacity: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .image-preview:hover .image-download-btn,
|
|
|
- .image-preview > div:hover .image-download-btn,
|
|
|
- .image-preview .image-download-btn:hover {
|
|
|
- opacity: 1 !important;
|
|
|
- }
|
|
|
-
|
|
|
- .image-download-btn:hover {
|
|
|
- background: rgba(0, 0, 0, 0.8) !important;
|
|
|
- }
|
|
|
-
|
|
|
- /* 修改单选框为方形 */
|
|
|
- .el-radio__input.is-border {
|
|
|
- border-radius: 2px !important;
|
|
|
- }
|
|
|
-
|
|
|
- .el-radio__input.is-border .el-radio__inner {
|
|
|
- border-radius: 2px !important;
|
|
|
- }
|
|
|
-
|
|
|
- .template-desc {
|
|
|
- flex: 1;
|
|
|
- font-size: 12px;
|
|
|
- color: #606266;
|
|
|
- line-height: 1.4;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- display: -webkit-box;
|
|
|
- -webkit-line-clamp: 2;
|
|
|
- -webkit-box-orient: vertical;
|
|
|
- }
|
|
|
-
|
|
|
- .empty-templates {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
-
|
|
|
- .empty-templates .el-icon {
|
|
|
- margin-bottom: 10px;
|
|
|
- }
|
|
|
-
|
|
|
- .empty-search {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- padding: 50px 20px;
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
-
|
|
|
- .empty-search .el-icon {
|
|
|
- margin-bottom: 15px;
|
|
|
- color: #c0c4cc;
|
|
|
- }
|
|
|
-
|
|
|
- .empty-search p {
|
|
|
- margin: 0;
|
|
|
- font-size: 14px;
|
|
|
- }
|
|
|
- </style>
|