| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- <template>
- <div class="app-container">
- <div class="filter-container">
- <div class="filter-container-lt">
- <el-form :inline="true" label-position="left" :model="listQuery">
- <el-form-item>
- <el-input clearable v-model="listQuery.sku" placeholder="搜索SKU"
- ></el-input>
- </el-form-item>
- <el-form-item>
- <el-select clearable v-model="listQuery.status" placeholder="请选择状态">
- <el-option
- v-for="item in statusSets"
- :key="item.value"
- :label="item.label"
- :value="item.value">
- </el-option>
- </el-select>
- </el-form-item>
- <el-button type="primary" @click="getList()">搜索</el-button >
- <el-button type="primary" @click="showCropSizeModal()">裁图尺寸</el-button >
- <el-button type="primary" @click="aiCropPreview()" :loading="uploadLoading">AI裁图</el-button>
- </el-form>
- </div>
- </div>
- <div class="table-container">
- <el-table style="width: 100%" v-loading="listLoading" :key="tableKey" :data="list" row-key="id"
- stripe border fit highlight-current-row @selection-change="handleSelectionChange">
- <!-- <el-table-column type="selection" width="55" :selectable="selectable"></el-table-column> -->
- <el-table-column label="SKU名称" align="center">
- <template slot-scope="scope">
- <el-popover
- placement="bottom"
- width="260"
- trigger="manual"
- v-model="scope.row._popoverVisible"
- >
- <div class="input-block">
- <span style="display: block;">SKU:</span>
- <el-input
- v-model="editValue"
- type="textarea"
- :rows="2"
- />
- </div>
- <div class="input-block" style="margin-top: 10px;" v-if="scope.row.status >= 5">
- <span style="display: block;">颜色代码:</span>
- <el-input
- v-model="editValueColor"
- type="textarea"
- :rows="2"
- />
- </div>
- <div class="popover-footer">
- <el-button size="mini" @click="cancelEdit(scope.row)">取消</el-button>
- <el-button size="mini" :loading="skuLoading" type="primary" @click="confirmEdit(scope.row)">确认</el-button>
- </div>
- <span slot="reference" class="click-text" @click="openEdit(scope.row)">
- {{ scope.row.sku }}
- <template v-if="scope.row.colorCode">
- <br>
- {{ scope.row.colorCode }}
- </template>
- </span>
- </el-popover>
- </template>
- </el-table-column>
- <el-table-column label="原图" min-width="100" align="center">
- <template slot-scope="scope">
- <p v-if="scope.row.type == 1 && scope.row.referenceImagesList && scope.row.referenceImagesList.length" @click="openGallery(scope.row, 'default')">
- <img style="max-width: 100px;" :src="scope.row.referenceImagesList[0]" alt=""></img>
- </p>
- <p style="margin: 0;cursor: pointer;" v-if="scope.row.type == 2 && scope.row.aiGeneratedImageVOList && scope.row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).length" @click="openGallery(scope.row, 'default')">
- <img style="max-width: 100px;" :src="scope.row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1)[0].imageUrl" alt="">
- </p>
- </template>
- </el-table-column>
- <el-table-column label="AI生成图" min-width="100" align="center">
- <template slot-scope="scope">
- <p style="margin: 0;cursor: pointer;" v-if="scope.row.aiGeneratedImageVOList && scope.row.aiGeneratedImageVOList.length" @click="openGallery(scope.row)">
- <img style="max-width: 100px;" :src="scope.row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1)[0].imageUrl" alt="">
- </p>
- </template>
- </el-table-column>
- <el-table-column label="预计完成时间" min-width="100" align="center" prop="expectedCompletionTime" />
- <el-table-column label="状态" min-width="100" align="center">
- <template slot-scope="scope">
- <el-tag v-if="scope.row.status == 0">提交中</el-tag>
- <el-tag v-if="scope.row.status == 1">AI生成中</el-tag>
- <el-tag type="warning" v-if="scope.row.status == 2">待审核</el-tag>
- <el-tag type="warning" v-if="scope.row.status == 3">待复核</el-tag>
- <el-tag type="success" v-if="scope.row.status == 4">待裁图</el-tag>
- <el-tag type="warning" v-if="scope.row.status == 5">裁图待复核</el-tag>
- <el-tag type="success" v-if="scope.row.status == 6">已完成</el-tag>
- <el-tag type="danger" v-if="scope.row.status == 7">生图失败</el-tag>
- <el-tag v-if="scope.row.status == 8">待推送</el-tag>
- <el-tag v-if="scope.row.status == 9">裁图中</el-tag>
- </template>
- </el-table-column>
- <el-table-column label="处理人" min-width="100" align="center" prop="creator" />
- <el-table-column label="处理时间" min-width="100" align="center" prop="updateTime" />
- <el-table-column label="操作" align="center" width="250" fixed="right">
- <template slot-scope="scope">
- <!-- scope.row.status == 2 || scope.row.status == 3 -->
- <el-button size="mini" v-if="scope.row.status == 2 || scope.row.status == 3" @click="showImagesGroupModal(scope.row, 'review')">审核</el-button>
- <el-button type="success" size="mini" v-if="scope.row.status * 1 >= 4 && scope.row.status != 7 && scope.row.status != 9 && scope.row.status != 6" @click="showImagesGroupModal(scope.row, 'crop')">裁图</el-button>
- <el-button type="primary" v-if="scope.row.status * 1 > 1 && scope.row.status != 7" size="mini" @click="handleDownload(scope.row)">下载</el-button>
- <!-- 自动生成 1 手动生成 2 -->
- <el-button type="info" size="mini" v-if="scope.row.type != 2" @click="handleRegenerate(scope.row)">重生成</el-button>
- <el-dropdown v-if="scope.row.status == 8" @command="uploadGalleryList($event, scope.row)">
- <el-button type="warning" size="mini">
- 上传图库<i class="el-icon-arrow-down el-icon--right"></i>
- </el-button>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item command="1">覆盖模式</el-dropdown-item>
- <el-dropdown-item command="2">追加模式</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- <el-popover
- placement="left"
- width="320"
- trigger="click"
- >
- <!-- 日志内容 -->
- <div v-if="scope.row.operationLogs && scope.row.operationLogs.length" style="max-height: 500px;overflow-y: auto;">
- <div
- v-for="(log, index) in scope.row.operationLogs"
- :key="index"
- class="log-item"
- >
- <div class="log-user">{{ log.userName }}</div>
- <div class="log-action">{{ log.msg }}</div>
- <div class="log-time">{{ log.updateTime }}</div>
- </div>
- </div>
- <div v-else class="empty-log">
- 暂无操作日志
- </div>
- <!-- 触发按钮 -->
- <el-button
- slot="reference"
- size="mini"
- type="danger"
- >
- 操作日志
- </el-button>
- </el-popover>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <!-- 分页 -->
- <swPage v-if="total > 0" key="2" :listQuery="listQuery" :total="total" pos="btmRight" @retPage="retPage" />
- <crop-size-modal ref="CropSizeModal" :list="sizeList" @success="getSizeTemplate()"></crop-size-modal>
- <images-group-modal ref="ImagesGroupModal" :sizeList="sizeList" @update-status="updateStatus" @update-images="updateImages" @update-success="handleFilter()"></images-group-modal>
- </div>
- </template>
- <script>
- import waves from "@/directive/waves";
- import swPage from "@/views/common/swPage";
- import CropSizeModal from "./components/CropSizeModal";
- import ImagesGroupModal from "./components/ImagesGroupModal";
- import { auditStatus } from "@/constants/index";
- import { getValueByKey } from "@/utils/index";
- import permission from "@/directive/permission";
- import { genCidHex16 } from '@/utils/index'
- import { Fancybox } from "@fancyapps/ui";
- import request from '@/utils/request'
- import "@fancyapps/ui/dist/fancybox.css";
- import JSZip from 'jszip'
- import { saveAs } from 'file-saver'
- export default {
- name: "cropList",
- directives: {
- waves,
- permission
- },
- components: {
- swPage,
- CropSizeModal,
- ImagesGroupModal
- },
- computed: {
- },
- data() {
- return {
- auditStatus,
- tableKey: 0,
- list: [],
- total: 0,
- skuLoading: false,
- editValue: '',
- editValueColor: '',
- listLoading: false,
- uploadLoading: false,
- listQuery: {
- currentPage: 1,
- pageSize: 10,
- sku: null,
- status: ''
- },
- sizeList: [],
- multipleSelection: []
- };
- },
- //页面创建的时候执行
- created() {
- this.getSizeTemplate();
- this.getList();
- },
- computed: {
- statusSets() {
- return [
- {label: '提交中', value: '0'},
- {label: 'AI生成中', value: '1'},
- {label: '待审核', value: '2'},
- {label: '待复核', value: '3'},
- {label: '待裁图', value: '4'},
- {label: '裁图待复核', value: '5'},
- {label: '已完成', value: '6'},
- {label: '生图失败', value: '7'},
- {label: '待推送', value: '8'},
- {label: '裁图中', value: '9'}
- ]
- }
- },
- mounted() {
- document.addEventListener('click', this.handleClickOutside)
- },
- beforeDestroy() {
- document.removeEventListener('click', this.handleClickOutside)
- },
- methods: {
- aiCropPreview() {
- this.$refs.ImagesGroupModal.init();
- },
- selectable(row, index) {
- // 返回 false → 该行不可选
- // 返回 true → 该行可选
- return row.status == 6 || row.status == 8;
- },
- handleRegenerate(row) {
- this.listLoading = true;
- request({
- url: '/imageTask/regenerate',
- method: 'post',
- data: {
- sessionId: `${Date.now() + 10}_${genCidHex16()}`,
- applicationId: 5,
- id: row.id,
- }
- }).then(res => {
- if (res.code == 200) {
- this.getList();
- this.$message.success("操作成功");
- }
- }).finally(() => {
- this.listLoading = false;
- })
- },
- getSizeTemplate() {
- request({
- url: '/sizeTemplate/list',
- method: 'get'
- }).then(res => {
- if (res.code == 200) {
- this.sizeList = res.data;
- }
- }).finally(() => {
-
- })
- },
- updateImages(id, imageId){
- this.list.forEach(item => {
- if (item.id == id) {
- item.aiGeneratedImageVOList.map((acc, index) => {
- if (acc.id == imageId) {
- item.aiGeneratedImageVOList.splice(index, 1);
- }
- })
- }
- })
- },
- updateStatus(id, status) {
- this.list.forEach(item => {
- if (item.id == id) {
- item.status = status;
- }
- })
- },
- handleClickOutside(e) {
- // 如果点击发生在 popover 或 reference 内,不处理
- const popovers = document.querySelectorAll('.el-popover')
- const references = document.querySelectorAll('.click-text')
- for (let el of [...popovers, ...references]) {
- if (el.contains(e.target)) return
- }
- // 否则关闭所有 popover
- this.list.forEach(item => {
- item._popoverVisible = false
- })
- },
- openEdit(row) {
- // 先关闭所有 popover
- this.list.forEach(item => {
- item._popoverVisible = false;
- })
- row._popoverVisible = true;
- this.editValue = row.sku;
- this.editValueColor = row.colorCode;
- },
- cancelEdit(row) {
- row._popoverVisible = false
- },
- confirmEdit(row) {
- // 👉 这里可直接调用保存接口
- const params = {
- sessionId: `${Date.now() + 10}_${genCidHex16()}`,
- applicationId: 5,
- id: row.id,
- sku: this.editValue
- }
- if (row.status >= 5) {
- params['colorCode'] = this.editValueColor;
- }
- this.skuLoading = true;
- request({
- url: '/imageTask/update',
- method: 'post',
- data: params
- }).then(res => {
- if (res.code == 200) {
- row._popoverVisible = false;
- row.sku = this.editValue;
- if (row.status >= 5) {
- row.colorCode = this.editValueColor;
- }
- this.$message.success("操作成功");
- }
- }).finally(() => {
- this.skuLoading = false;
- })
- },
- getValueByKey,
- getList() {
- this.listLoading = true;
- const params = {}
- for (const key in this.listQuery) {
- const value = this.listQuery[key]
- if (value !== null && value !== '' && value !== undefined) {
- params[key] = value
- }
- }
- request({
- url: '/imageTask/page',
- method: 'get',
- params: params
- }).then(res => {
- if (res.code == 200) {
- this.total = res.data.total;
- this.list = res.data.rows;
- }
- }).finally(() => {
- this.listLoading = false
- })
- },
- retPage() {
- //分页
- this.getList();
- },
- handleFilter() {
- this.listQuery.page = 1;
- this.getList();
- },
- // 上传图库
- uploadGalleryList(command, row) {
- this.listLoading = true;
- const params = {
- taskId: row.id,
- uploadType: command || '2'
- }
- request({
- url: '/imageTask/uploadGallery',
- method: 'post',
- data: params
- }).then(res => {
- if (res.code == 200) {
- this.$message.success(res.msg || '操作成功!');
- }
- }).finally(() => {
- this.listLoading = false
- })
- },
- openGallery(row, type) {
- let images = [];
- if (type == 'default') {
- if (row.type == 1) {
- images = [...row.referenceImagesList, ...row.originalImagesList].slice(0, 10);
- } else {
- images = row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).slice(0, 10).map(acc => {
- return acc.imageUrl
- });
- }
- } else {
- if (row.type == 1) {
- row.aiGeneratedImageVOList.filter(acc => acc.imageType == 1).slice(0, 10).forEach(acc => {
- images.push(acc.imageUrl)
- })
- } else {
- row.aiGeneratedImageVOList.filter(acc => acc.imageType == 2).slice(0, 10).forEach(acc => {
- images.push(acc.imageUrl)
- })
- }
- }
- Fancybox.show(
- images.map((src) => ({
- src,
- type: "image",
- })),
- {
- startIndex: 0,
- }
- );
- },
- handleSelectionChange(val) {
- this.multipleSelection = val;
- },
- async runWithConcurrencyLimit(tasks, limit = 5) {
- const executing = []
- const results = []
- for (const task of tasks) {
- const p = Promise.resolve().then(task)
- results.push(p)
- executing.push(p)
- p.finally(() => {
- const index = executing.indexOf(p)
- if (index > -1) executing.splice(index, 1)
- })
- if (executing.length >= limit) {
- await Promise.race(executing)
- }
- }
- return Promise.allSettled(results)
- },
- async handleDownload(row) {
- const imagesList = row.aiGeneratedImageVOList
- if (!imagesList || !imagesList.length) return
- this.listLoading = true
- const zip = new JSZip()
- const folder = zip.folder('images')
- const tasks = imagesList.map((item, index) => async () => {
- try {
- const res = await fetch(item.imageUrl)
- const blob = await res.blob()
- // 更安全的文件名解析
- const urlObj = new URL(item.imageUrl)
- const fileName = urlObj.pathname.split('/').pop()
- folder.file(fileName || `image_${index + 1}.jpg`, blob)
- } catch (err) {
- console.error('下载失败:', item.imageUrl, err)
- }
- })
- // 并发限制(5 个)
- await this.runWithConcurrencyLimit(tasks, 5)
- const zipBlob = await zip.generateAsync({ type: 'blob' })
- saveAs(zipBlob, `${row.sku || 'images'}.zip`)
- this.listLoading = false
- },
- showCropSizeModal() {
- this.$refs.CropSizeModal.show();
- },
- showImagesGroupModal(row, type) {
- this.$refs.ImagesGroupModal.show(row, type);
- }
- }
- };
- </script>
- <style rel="stylesheet/scss" lang="scss" scoped>
- @import "@/styles/layout.scss";
- .table-container {
- .el-button+.el-button {
- margin-left: 7px;
- margin-bottom: 10px;
- }
- .el-button+span,
- .el-button+.el-dropdown {
- margin-left: 7px;
- }
- }
- .button {
- .el-button {
- margin-bottom: 10px;
- &:last-child {
- margin-bottom: 0;
- }
- }
- }
- .log-item {
- padding: 6px 0;
- border-bottom: 1px dashed #eee;
- }
- .log-item:last-child {
- border-bottom: none;
- }
- .log-user {
- font-weight: 500;
- color: #ae8877;
- }
- .log-action {
- font-size: 13px;
- margin: 2px 0;
- }
- .log-time {
- font-size: 12px;
- color: #999;
- }
- .empty-log {
- text-align: center;
- color: #aaa;
- padding: 20px 0;
- }
- .click-text {
- cursor: pointer;
- color: #409eff;
- }
- .click-text:hover {
- text-decoration: underline;
- }
- .popover-footer {
- margin-top: 10px;
- text-align: right;
- }
- </style>
|