|
@@ -0,0 +1,1762 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <layout>
|
|
|
|
|
+ <layout-header>
|
|
|
|
|
+ <!-- 搜索区域 -->
|
|
|
|
|
+ <el-form inline>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-input v-model="searchInfo" placeholder="搜索关键字" clearable style="width: 300px;" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 文件夹筛选下拉菜单 -->
|
|
|
|
|
+ <!-- <el-dropdown @command="handleFolderCommand" style="margin-right: 10px;">
|
|
|
|
|
+ <el-button icon="search" title="文件夹筛选">
|
|
|
|
|
+ {{ selectedFolder || '文件夹筛选' }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <template #dropdown>
|
|
|
|
|
+ <el-dropdown-menu>
|
|
|
|
|
+ <el-dropdown-item command="">全部文件夹</el-dropdown-item>
|
|
|
|
|
+ <el-dropdown-item v-for="(folder, index) in folderList" :key="index" :command="folder">
|
|
|
|
|
+ {{ folder }}
|
|
|
|
|
+ </el-dropdown-item>
|
|
|
|
|
+ </el-dropdown-menu>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dropdown> -->
|
|
|
|
|
+
|
|
|
|
|
+ <el-button type="primary" icon="search" @click="onSubmit" title="搜索">查询</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="onADD" >新增产品</el-button>
|
|
|
|
|
+ <!-- <el-button type="primary" title="选择图片,文生图" @click="texttoimg" :loading="isLoading">文生图</el-button>
|
|
|
|
|
+ <el-button type="primary" title="选择图片,图生图" @click="imgtoimg" :loading="isLoading">图生图</el-button> -->
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </layout-header>
|
|
|
|
|
+
|
|
|
|
|
+ <layout>
|
|
|
|
|
+ <!-- 左侧树形结构 -->
|
|
|
|
|
+ <template v-if="userStore.userInfo.nickName === '超级管理员'">
|
|
|
|
|
+ <layout-sider :resize-directions="['right']" :width="190" style="margin-right: 10px">
|
|
|
|
|
+ <div class="JKWTree-tree" style="height: 200px;width:100vh">
|
|
|
|
|
+ <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="txtimgDialogVisible" title="文生图" width="660px" top="10%">
|
|
|
|
|
+ <el-form label-width="100px">
|
|
|
|
|
+ <el-form-item label="文生图模型">
|
|
|
|
|
+ <div style="display: flex; align-items: center; gap: 20px; flex-wrap: wrap;">
|
|
|
|
|
+ <el-radio-group v-model="txtimgselectedModel" @change="handleModelChange">
|
|
|
|
|
+ <el-radio
|
|
|
|
|
+ v-for="item in txttoimg_modelList"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ :label="item.txttoimg">
|
|
|
|
|
+ {{ item.txttoimg }}
|
|
|
|
|
+ <span v-if="item.id === usedIds.wenshengtu" style="color: #67C23A; margin-left: 5px;">(默认使用)</span>
|
|
|
|
|
+ </el-radio>
|
|
|
|
|
+ </el-radio-group>
|
|
|
|
|
+ <el-button type="success" @click="setActive('wenshengtu')" :disabled="!selectedIds.wenshengtu || selectedIds.wenshengtu === usedIds.wenshengtu">
|
|
|
|
|
+ 设为默认使用模型
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="txtimgDialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="generateTxtImg" :loading="isGenerating">生成文生图</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+<el-dialog
|
|
|
|
|
+ v-model="editDialogVisible"
|
|
|
|
|
+ title="图片对比与编辑"
|
|
|
|
|
+ fullscreen
|
|
|
|
|
+ :modal="true"
|
|
|
|
|
+ @close="handleDialogClose">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="image-edit-container" style="display: flex; gap: 20px; height: calc(100vh - 140px);">
|
|
|
|
|
+ <!-- 左侧:原图新图 + 输入框 -->
|
|
|
|
|
+ <div class="left-column" style="flex: 2; display: flex; flex-direction: column; gap: 20px;">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图片对比区域 -->
|
|
|
|
|
+ <div class="image-comparison-section" style="flex: 1; display: flex; flex-direction: column; gap: 15px;">
|
|
|
|
|
+ <div class="image-item">
|
|
|
|
|
+ <div class="image-title">原图</div>
|
|
|
|
|
+ <div style="display: flex; justify-content: space-between; width: 100%;">
|
|
|
|
|
+ <div style="display: flex; gap: 12px;">
|
|
|
|
|
+ <el-button type="primary" size="large" @click="downloadImage('original')">
|
|
|
|
|
+ <el-icon><Download /></el-icon>下载原图
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button type="primary" size="large" @click="downloadImage('new')">
|
|
|
|
|
+ <el-icon><Download /></el-icon>下载新图
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div style="display: flex; gap: 12px;">
|
|
|
|
|
+ <el-button type="success" size="large">
|
|
|
|
|
+ <el-icon><Download /></el-icon>内容审核
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button type="primary" size="large">
|
|
|
|
|
+ <el-icon><Download /></el-icon>确认广告图
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 原图区域 -->
|
|
|
|
|
+ <div class="image-preview">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ v-if="editFormData.original_image_url"
|
|
|
|
|
+ :src="formatImageUrl(editFormData.original_image_url)"
|
|
|
|
|
+ :preview-src-list="[formatImageUrl(editFormData.original_image_url)]"
|
|
|
|
|
+ style="width: 100%; height: 200px;"
|
|
|
|
|
+ fit="contain"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ />
|
|
|
|
|
+ <div v-else class="image-placeholder">
|
|
|
|
|
+ <el-icon :size="40"><Picture /></el-icon>
|
|
|
|
|
+ <span>暂无原图</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="image-info">
|
|
|
|
|
+ <span>文件名: {{ editFormData.original_name || '未命名' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 新图区域 -->
|
|
|
|
|
+ <div class="image-preview">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ v-if="editFormData.new_image_url"
|
|
|
|
|
+ :src="formatImageUrl(editFormData.new_image_url)"
|
|
|
|
|
+ :preview-src-list="[formatImageUrl(editFormData.new_image_url)]"
|
|
|
|
|
+ style="width: 100%; height: 200px;"
|
|
|
|
|
+ fit="contain"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ />
|
|
|
|
|
+ <div v-else class="image-placeholder">
|
|
|
|
|
+ <el-icon :size="40"><Picture /></el-icon>
|
|
|
|
|
+ <span>暂无新图</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="image-info">
|
|
|
|
|
+ <span>文件名: {{ editFormData.img_name || '未命名' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+<!-- 输入框区域 -->
|
|
|
|
|
+<div class="edit-section" style="flex: 1; border-top: 1px solid #e4e7ed; padding-top: 20px; display: flex; flex-direction: column;">
|
|
|
|
|
+ <el-form :model="editFormData" label-width="80px" style="flex: 1; display: flex; flex-direction: column;">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ v-model="editFormData.chinese_description"
|
|
|
|
|
+ :rows="12"
|
|
|
|
|
+ placeholder="点击右侧模板图片可自动填充描述内容"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ maxlength="500"
|
|
|
|
|
+ style="width: 100%; flex: 1;"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+<!-- 按钮放在表单下方 -->
|
|
|
|
|
+<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px; padding-top: 15px; border-top: 1px solid #e4e7ed;">
|
|
|
|
|
+ <!-- 左侧:尺寸选择区域 -->
|
|
|
|
|
+ <div style="display: flex; align-items: center; gap: 10px;">
|
|
|
|
|
+ <span style="font-size: 14px; color: #606266;">尺寸比例:</span>
|
|
|
|
|
+ <el-select v-model="selectedSize" size="small" style="width: 120px;" @change="handleSizeChange">
|
|
|
|
|
+ <el-option label="1:1" value="1:1" />
|
|
|
|
|
+ <el-option label="4:3" value="4:3" />
|
|
|
|
|
+ <el-option label="3:2" value="3:2" />
|
|
|
|
|
+ <el-option label="2:3" value="2:3" />
|
|
|
|
|
+ <el-option label="9:16" value="9:16" />
|
|
|
|
|
+ <el-option label="16:9" value="16:9" />
|
|
|
|
|
+ <el-option label="自由尺寸" value="custom" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 自由尺寸的像素输入 -->
|
|
|
|
|
+ <div v-if="selectedSize === 'custom'" style="display: flex; align-items: center; gap: 5px;">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="customWidth"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ style="width: 80px;"
|
|
|
|
|
+ placeholder="宽"
|
|
|
|
|
+ @input="validateCustomSize"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #append>px</template>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ <span style="color: #909399;">×</span>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="customHeight"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ style="width: 80px;"
|
|
|
|
|
+ placeholder="高"
|
|
|
|
|
+ @input="validateCustomSize"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #append>px</template>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 显示当前尺寸信息 -->
|
|
|
|
|
+ <div v-if="selectedSize !== 'custom'" style="font-size: 12px; color: #909399;">
|
|
|
|
|
+ {{ getSizeInfo(selectedSize) }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 右侧:操作按钮 -->
|
|
|
|
|
+ <div style="display: flex; gap: 10px;">
|
|
|
|
|
+ <el-button size="small" @click="clearInput">清空</el-button>
|
|
|
|
|
+ <el-button type="primary" size="small" @click="optimizeContent">内容优化</el-button>
|
|
|
|
|
+ <el-button type="success" size="small" @click="generateImage">生成图片</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 右侧:模板列表 -->
|
|
|
|
|
+ <div class="right-column" style="flex: 1; border-left: 1px solid #e4e7ed; padding-left: 20px;">
|
|
|
|
|
+ <div class="right-template" style="height: 100%; display: flex; flex-direction: column;">
|
|
|
|
|
+ <div class="template-header" style="margin-bottom: 15px;">
|
|
|
|
|
+ <h4>参考模板 ({{ templateList.length }})</h4>
|
|
|
|
|
+ <el-tag type="warning" size="small">点击选择</el-tag>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 搜索框 -->
|
|
|
|
|
+ <div class="template-search" style="margin-bottom: 15px;">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="searchKeyword"
|
|
|
|
|
+ placeholder="输入关键词搜索模板..."
|
|
|
|
|
+ clearable
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
|
|
+ @clear="handleClearSearch"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #prefix>
|
|
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template #append>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :icon="Search"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ :loading="searchLoading"
|
|
|
|
|
+ @click="handleSearch"
|
|
|
|
|
+ />搜索
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 模板列表 -->
|
|
|
|
|
+ <div style="flex: 1; min-height: 0;">
|
|
|
|
|
+ <el-scrollbar style="height: 100%;">
|
|
|
|
|
+ <div class="template-list">
|
|
|
|
|
+ <!-- 搜索结果为空提示 -->
|
|
|
|
|
+ <div v-if="searchLoading" class="loading-state">
|
|
|
|
|
+ <el-icon class="loading-icon" :size="30"><Loading /></el-icon>
|
|
|
|
|
+ <p>搜索中...</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 搜索无结果提示 -->
|
|
|
|
|
+ <div v-if="!searchLoading && searchKeyword && templateList.length === 0" class="empty-search">
|
|
|
|
|
+ <el-icon :size="40"><Search /></el-icon>
|
|
|
|
|
+ <p>未找到相关模板</p>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ @click="handleClearSearch"
|
|
|
|
|
+ style="margin-top: 10px;"
|
|
|
|
|
+ >
|
|
|
|
|
+ 查看全部模板
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </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">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ :src="template.template_image_url"
|
|
|
|
|
+ :preview-src-list="[template.template_image_url]"
|
|
|
|
|
+ style="width: 100%; height: 100%;"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ hide-on-click-modal
|
|
|
|
|
+ @click.stop="handleTemplatePreview($event, template)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #error>
|
|
|
|
|
+ <div class="thumbnail-error">
|
|
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-image>
|
|
|
|
|
+ <div class="template-id">模板 {{ template.id }}</div>
|
|
|
|
|
+ </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>
|
|
|
|
|
+ </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="upload-container">
|
|
|
|
|
+ <!-- 图片展示 -->
|
|
|
|
|
+ <div v-if="productForm.product_img" class="image-preview">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ :src="productForm.product_img"
|
|
|
|
|
+ :preview-src-list="[productForm.product_img]"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ class="preview-image"
|
|
|
|
|
+ />
|
|
|
|
|
+ <div class="image-actions">
|
|
|
|
|
+ <el-button type="text" @click="handleViewImage">查看</el-button>
|
|
|
|
|
+ <el-button type="text" @click="handleRemoveImage">删除</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 上传区域 -->
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ v-else
|
|
|
|
|
+ class="image-uploader"
|
|
|
|
|
+ :action="uploadUrl"
|
|
|
|
|
+ :show-file-list="false"
|
|
|
|
|
+ :on-success="handleUploadSuccess"
|
|
|
|
|
+ :on-error="handleUploadError"
|
|
|
|
|
+ :before-upload="beforeUpload"
|
|
|
|
|
+ accept="image/*"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button type="primary" plain>
|
|
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
|
|
+ 上传图片
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <div class="upload-tip">支持 JPG、PNG 格式,大小不超过 5MB</div>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="form-tip">请上传产品图片</div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <span class="dialog-footer">
|
|
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
|
|
|
|
+ 确定
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, reactive, 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 } from '@/api/mes/job'
|
|
|
|
|
+import { useUserStore } from '@/pinia/modules/user'
|
|
|
|
|
+import { ArrowDown } from '@element-plus/icons-vue'
|
|
|
|
|
+import { Layout, LayoutHeader, LayoutSider, LayoutContent } from '@arco-design/web-vue'
|
|
|
|
|
+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: '',
|
|
|
|
|
+ imgtoimg_url: '',
|
|
|
|
|
+ img_name: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 定义文生图表单对象
|
|
|
|
|
+const form = reactive({
|
|
|
|
|
+ chinese_description: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const formatImageUrl = (path) => {
|
|
|
|
|
+ if (!path) return ''
|
|
|
|
|
+ return `${full_url.value}/${path.replace(/^public\//, '')}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const txttoimg_modelList = ref([]); // 存储所有模型数据
|
|
|
|
|
+const txtimgselectedModel = ref(''); // 当前选中的模型名称
|
|
|
|
|
+const usedId = ref(null); // 当前使用的模型ID(兼容旧代码)
|
|
|
|
|
+const selectedId = ref(null); // 用户选择的模型ID(兼容旧代码)
|
|
|
|
|
+const txtimgDialogVisible = ref(false); // 文生图弹窗可见性
|
|
|
|
|
+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 handleFolderCommand = (command) => {
|
|
|
|
|
+// selectedFolder.value = command
|
|
|
|
|
+// onSubmit()
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const onSubmit = () => {
|
|
|
|
|
+// page.value = 1
|
|
|
|
|
+// getTableData()
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const handleCurrentChange = (val) => {
|
|
|
|
|
+// page.value = val
|
|
|
|
|
+// getTableData()
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const handleSizeChange = (val) => {
|
|
|
|
|
+// pageSize.value = val
|
|
|
|
|
+// page.value = 1
|
|
|
|
|
+// getTableData()
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const SelectionChange = (rows) => {
|
|
|
|
|
+// _parh.value = rows.map(item => item.old_image_url)
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// // 文生图生成函数
|
|
|
|
|
+// const generateTxtImg = () => {
|
|
|
|
|
+// txtimgDialogVisible.value = false
|
|
|
|
|
+// processImages(toRaw(_parh.value), '文生图', form.chinese_description)
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const texttoimg = async () => {
|
|
|
|
|
+// if (_parh.value.length === 0) {
|
|
|
|
|
+// ElMessage.warning('请先选择要处理的图片')
|
|
|
|
|
+// return
|
|
|
|
|
+// }
|
|
|
|
|
+// // 获取模型列表
|
|
|
|
|
+// await fetchTxtImgModels()
|
|
|
|
|
+// // 显示弹窗
|
|
|
|
|
+// txtimgDialogVisible.value = true
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const imgtoimg = () => {
|
|
|
|
|
+// processImages(toRaw(_parh.value), '图生图')
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// // 是否执行几何图(默认不执行)
|
|
|
|
|
+// const executeKeywords = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+// const processImages = async (files, type, prompt = '') => {
|
|
|
|
|
+// if (!Array.isArray(files) || files.length === 0) {
|
|
|
|
|
+// ElMessage.warning('请选择要处理的图片')
|
|
|
|
|
+// return
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const pathGroups = {}
|
|
|
|
|
+// _parh.value.forEach(path => {
|
|
|
|
|
+// const dir = path.replace(/\/[^\/]*$/, '')
|
|
|
|
|
+// if (!pathGroups[dir]) pathGroups[dir] = []
|
|
|
|
|
+// pathGroups[dir].push(path)
|
|
|
|
|
+// })
|
|
|
|
|
+
|
|
|
|
|
+// isLoading.value = true
|
|
|
|
|
+// const failList = []
|
|
|
|
|
+
|
|
|
|
|
+// for (const [dir, images] of Object.entries(pathGroups)) {
|
|
|
|
|
+// const payload = {
|
|
|
|
|
+// old_image_file: dir,
|
|
|
|
|
+// type,
|
|
|
|
|
+// selectedOption: selectedOption.value,
|
|
|
|
|
+// txttotxt_selectedOption: txttotxt_selectedOption.value,
|
|
|
|
|
+// batch: images,
|
|
|
|
|
+// num: num.value,
|
|
|
|
|
+// width: width.value,
|
|
|
|
|
+// height: height.value,
|
|
|
|
|
+// executeKeywords:executeKeywords.value,
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// try {
|
|
|
|
|
+// await imageToText(payload)
|
|
|
|
|
+// ElMessage.success(`处理目录:${dir},共 ${images.length} 张 * ${num.value} 次`)
|
|
|
|
|
+// } catch (e) {
|
|
|
|
|
+// console.warn('处理失败:', dir, e)
|
|
|
|
|
+// images.forEach((_, i) => failList.push(`目录 ${dir} 第 ${i + 1} 张`))
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// if (failList.length > 0) {
|
|
|
|
|
+// ElMessage.warning('部分图片处理失败,请查看控制台')
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// await new Promise(resolve => setTimeout(resolve, 3000))
|
|
|
|
|
+// isLoading.value = false
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+// const previewImage = (url) => {
|
|
|
|
|
+// currentImageUrl.value = url
|
|
|
|
|
+// dialogVisible.value = true
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+const onRowDblClick = async (row) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 设置编辑表单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['产品编码']
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 获取所有模板数据
|
|
|
|
|
+ await fetchTemplates()
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('打开编辑弹窗失败:', error)
|
|
|
|
|
+ ElMessage.error('获取数据失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 存储所有模板数据
|
|
|
|
|
+const templateList = ref([])
|
|
|
|
|
+const currentTemplateId = ref(null) // 当前选中的模板ID
|
|
|
|
|
+
|
|
|
|
|
+// 获取所有模板
|
|
|
|
|
+const fetchTemplates = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ 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])
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取模板失败:', error)
|
|
|
|
|
+ templateList.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 选择模板
|
|
|
|
|
+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 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
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置状态
|
|
|
|
|
+ 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'
|
|
|
|
|
+
|
|
|
|
|
+ // 更新内容到输入框
|
|
|
|
|
+ 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'
|
|
|
|
|
+ ElMessage.warning('优化超时,请稍后手动检查结果')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (pollError) {
|
|
|
|
|
+ console.error('优化轮询查询失败:', pollError)
|
|
|
|
|
+ // 轮询失败继续尝试
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 5000) // 每5秒轮询一次
|
|
|
|
|
+
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pollStatus.value = 'error'
|
|
|
|
|
+ ElMessage.error('优化任务启动失败: ' + (generateResponse.msg || '未知错误'))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (generateError) {
|
|
|
|
|
+ pollStatus.value = 'error'
|
|
|
|
|
+ console.error('内容优化失败:', generateError)
|
|
|
|
|
+ ElMessage.error('内容优化失败: ' + (generateError.message || '未知错误'))
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 在script中定义
|
|
|
|
|
+const pollStatus = ref(null) // 轮询状态:'polling', 'success', 'error'
|
|
|
|
|
+const pollCount = ref(0) // 轮询次数
|
|
|
|
|
+const pollInterval = ref(null) // 轮询定时器
|
|
|
|
|
+
|
|
|
|
|
+// 生成图片(简化版,只做轮询)
|
|
|
|
|
+const generateImage = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!editFormData.chinese_description) {
|
|
|
|
|
+ ElMessage.warning('请输入描述内容')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置状态
|
|
|
|
|
+ pollStatus.value = 'polling'
|
|
|
|
|
+ pollCount.value = 0
|
|
|
|
|
+
|
|
|
|
|
+ // 第一步:调用生成接口触发生成任务
|
|
|
|
|
+ const generateResponse = await GetTxtToTxt({
|
|
|
|
|
+ status_val: '文生图',
|
|
|
|
|
+ prompt: editFormData.chinese_description,
|
|
|
|
|
+ model: 'gemini-3-pro-image-preview',
|
|
|
|
|
+ id: editFormData.id,
|
|
|
|
|
+ size: selectedSize.value,
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ 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) {
|
|
|
|
|
+ // 如果获取到数据且有product_new_img字段
|
|
|
|
|
+ if (checkResponse.data.product_new_img) {
|
|
|
|
|
+ // 停止轮询
|
|
|
|
|
+ clearInterval(pollInterval.value)
|
|
|
|
|
+ pollStatus.value = 'success'
|
|
|
|
|
+
|
|
|
|
|
+ // 更新新图URL
|
|
|
|
|
+ editFormData.new_image_url = checkResponse.data.product_new_img
|
|
|
|
|
+ editFormData.img_name = `生成图片_${new Date().getTime()}.jpg`
|
|
|
|
|
+ editFormData.product_name = checkResponse.data.product_name
|
|
|
|
|
+ editFormData.product_code = checkResponse.data.product_code
|
|
|
|
|
+
|
|
|
|
|
+ // 如果有原图字段,也更新一下
|
|
|
|
|
+ if (checkResponse.data.product_img) {
|
|
|
|
|
+ editFormData.original_image_url = checkResponse.data.product_img
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ElMessage.success(`图片生成成功!耗时约${pollCount.value * 10}秒`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果轮询超过30次(5分钟),停止轮询
|
|
|
|
|
+ if (pollCount.value >= 30) {
|
|
|
|
|
+ clearInterval(pollInterval.value)
|
|
|
|
|
+ pollStatus.value = 'error'
|
|
|
|
|
+ ElMessage.warning('生成超时,请稍后手动检查结果')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (pollError) {
|
|
|
|
|
+ console.error('轮询查询失败:', pollError)
|
|
|
|
|
+ // 轮询失败继续尝试
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 5000) // 每10秒轮询一次
|
|
|
|
|
+
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pollStatus.value = 'error'
|
|
|
|
|
+ ElMessage.error('生成任务启动失败: ' + (generateResponse.msg || '未知错误'))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (generateError) {
|
|
|
|
|
+ pollStatus.value = 'error'
|
|
|
|
|
+ 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 }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取尺寸信息
|
|
|
|
|
+const getSizeInfo = (size) => {
|
|
|
|
|
+ if (size === 'custom') return '自定义尺寸'
|
|
|
|
|
+ const preset = sizePresets[size]
|
|
|
|
|
+ return `${preset.width}×${preset.height}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 尺寸变化处理
|
|
|
|
|
+const handleSizeChange = (value) => {
|
|
|
|
|
+ if (value !== 'custom') {
|
|
|
|
|
+ // 如果是预设尺寸,可以在这里执行相关操作
|
|
|
|
|
+ console.log('选择了尺寸:', value, sizePresets[value])
|
|
|
|
|
+ // 可以更新生成图片的参数
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 验证自定义尺寸
|
|
|
|
|
+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
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log(`下载${type}图信息:`, { imagePath, originalFileName })
|
|
|
|
|
+
|
|
|
|
|
+ // 获取完整URL
|
|
|
|
|
+ const fullUrl = formatImageUrl(imagePath)
|
|
|
|
|
+ console.log('完整下载URL:', fullUrl)
|
|
|
|
|
+
|
|
|
|
|
+ // 生成安全的文件名
|
|
|
|
|
+ const safeFileName = generateSafeFilename(originalFileName, imagePath, type)
|
|
|
|
|
+ console.log('安全文件名:', safeFileName)
|
|
|
|
|
+
|
|
|
|
|
+ // 调用下载
|
|
|
|
|
+ await downloadWithBlob(fullUrl, safeFileName)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 生成安全的文件名(处理中文和特殊字符)
|
|
|
|
|
+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'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 使用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('尝试直接打开...')
|
|
|
|
|
+ window.open(url, '_blank')
|
|
|
|
|
+ ElMessage.info('已在新的标签页打开图片')
|
|
|
|
|
+ } catch (openError) {
|
|
|
|
|
+ console.error('所有方法都失败:', openError)
|
|
|
|
|
+ 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)
|
|
|
|
|
+
|
|
|
|
|
+// 表单数据
|
|
|
|
|
+const productForm = reactive({
|
|
|
|
|
+ product_name: '',
|
|
|
|
|
+ product_code: '',
|
|
|
|
|
+ create_name: '',
|
|
|
|
|
+ merchant_id: '',
|
|
|
|
|
+ product_img: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+const onADD = async() => {
|
|
|
|
|
+ if (!nodeid.value) {
|
|
|
|
|
+ ElMessage.error('请先选择商户')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ AdddialogVisible.value = true
|
|
|
|
|
+ const res = await getMerchantId({
|
|
|
|
|
+ merchant_code: nodeid.value,
|
|
|
|
|
+ })
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ productForm.merchant_id = res.data
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+const handleSubmit = async()=>{
|
|
|
|
|
+ productForm.product_img = '/uploads/merchant/690377511/6903775111138/oldimg/鲜美番茄酱.png'
|
|
|
|
|
+ productForm.create_name = userStore.userInfo.nickName
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await productAdd(productForm)
|
|
|
|
|
+ if (response.code === 0) {
|
|
|
|
|
+ ElMessage.success('新增成功')
|
|
|
|
|
+ handleClose()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(response.message || '新增失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('新增失败:', error)
|
|
|
|
|
+ ElMessage.error('新增失败,请联系管理员')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 上传相关配置
|
|
|
|
|
+// 环境配置
|
|
|
|
|
+const uploadUrl = ref(`${full_url.value}/api/Facility/ImgUpload`)
|
|
|
|
|
+
|
|
|
|
|
+// 确保上传URL与服务器地址同步
|
|
|
|
|
+watch(full_url, (newUrl) => {
|
|
|
|
|
+ uploadUrl.value = `${newUrl}/api/Facility/ImgUpload`
|
|
|
|
|
+})
|
|
|
|
|
+const uploadHeaders = {
|
|
|
|
|
+ 'Content-Type': 'multipart/form-data',
|
|
|
|
|
+ 'Authorization': 'Bearer ' + localStorage.getItem('token')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 打开弹窗方法(外部调用)
|
|
|
|
|
+const openDialog = () => {
|
|
|
|
|
+ // 清空表单
|
|
|
|
|
+ Object.keys(productForm).forEach(key => {
|
|
|
|
|
+ productForm[key] = ''
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ dialogVisible.value = true
|
|
|
|
|
+
|
|
|
|
|
+ // 如果有默认值可以在这里设置
|
|
|
|
|
+ // productForm.create_name = '默认创建人'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 关闭弹窗
|
|
|
|
|
+const handleClose = () => {
|
|
|
|
|
+ productFormRef.value?.resetFields()
|
|
|
|
|
+ submitLoading.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 自定义上传逻辑
|
|
|
|
|
+const customUpload = async (options) => {
|
|
|
|
|
+ const { file, onProgress, onSuccess, onError } = options
|
|
|
|
|
+ try {
|
|
|
|
|
+ const formData = new FormData()
|
|
|
|
|
+ formData.append('image', file)
|
|
|
|
|
+ const response = await axios.post(uploadUrl.value, formData, {
|
|
|
|
|
+ headers: uploadHeaders,
|
|
|
|
|
+ onUploadProgress: (progressEvent) => {
|
|
|
|
|
+ const percent = Math.round(
|
|
|
|
|
+ (progressEvent.loaded * 100) / progressEvent.total
|
|
|
|
|
+ )
|
|
|
|
|
+ onProgress({ percent }, file)
|
|
|
|
|
+ },
|
|
|
|
|
+ timeout: 60000 // 60秒超时
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 关闭加载中
|
|
|
|
|
+ if (file.loadingInstance) {
|
|
|
|
|
+ file.loadingInstance.close()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 图片上传成功处理
|
|
|
|
|
+const handleUploadSuccess = (response, file) => {
|
|
|
|
|
+ // 根据实际接口返回的数据结构调整
|
|
|
|
|
+ if (response.code === 0) {
|
|
|
|
|
+ productForm.product_img = response.data.url
|
|
|
|
|
+ ElMessage.success('上传成功')
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(response.message || '上传失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 上传失败处理
|
|
|
|
|
+const handleUploadError = (error, file) => {
|
|
|
|
|
+ console.error('上传失败:', error)
|
|
|
|
|
+ ElMessage.error('图片上传失败,请重试')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 上传前验证
|
|
|
|
|
+const beforeUpload = (file) => {
|
|
|
|
|
+ const isImage = file.type.startsWith('image/')
|
|
|
|
|
+ const isLt5M = file.size / 1024 / 1024 < 5
|
|
|
|
|
+
|
|
|
|
|
+ if (!isImage) {
|
|
|
|
|
+ ElMessage.error('只能上传图片文件')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!isLt5M) {
|
|
|
|
|
+ ElMessage.error('图片大小不能超过 5MB')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 查看图片
|
|
|
|
|
+const handleViewImage = () => {
|
|
|
|
|
+ // el-image 的预览功能会自动处理
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 删除图片
|
|
|
|
|
+const handleRemoveImage = () => {
|
|
|
|
|
+ productForm.product_img = ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+// 暴露方法给父组件
|
|
|
|
|
+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;
|
|
|
|
|
+ }
|
|
|
|
|
+ :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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* 新布局CSS */
|
|
|
|
|
+ .image-edit-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ height: calc(100vh - 140px);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .left-column {
|
|
|
|
|
+ flex: 2;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .image-comparison-section {
|
|
|
|
|
+ flex: none; /* 改为不伸缩 */
|
|
|
|
|
+ height: 400px; /* 固定高度,和原来一样 */
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .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;
|
|
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ 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;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 10px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .edit-section {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ min-height: 200px;
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .right-column {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ 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: column;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ min-height: 0; /* 防止溢出 */
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .template-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ flex-shrink: 0; /* 防止被压缩 */
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .template-item:hover {
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ background: #f5faff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .template-item.active {
|
|
|
|
|
+ border-color: #409eff;
|
|
|
|
|
+ background: #ecf5ff;
|
|
|
|
|
+ border-width: 2px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .template-thumbnail {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 160px;
|
|
|
|
|
+ height: 140px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-right: 12px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .template-thumbnail img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .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;
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|