增加备注

This commit is contained in:
BianLzhaoMin 2025-11-25 17:16:35 +08:00
parent 5ed6e787bc
commit c7ac05a8ac
3 changed files with 947 additions and 871 deletions

View File

@ -1,279 +1,282 @@
<template>
<div class="upload-container">
<div class="upload-area">
<div class="upload-tip">
<!-- 上传提示或图片预览 -->
<div v-if="fileList.length === 0" class="upload-placeholder">
<div class="upload-text">上传图片</div>
</div>
<div class="upload-container">
<div class="upload-area">
<div class="upload-tip">
<!-- 上传提示或图片预览 -->
<div v-if="fileList.length === 0" class="upload-placeholder">
<div class="upload-text">上传图片</div>
</div>
<!-- 文件预览区域 -->
<div v-else class="file-preview-container">
<div
class="file-item"
v-for="(file, index) in fileList"
:key="index"
>
<!-- 图片文件预览 -->
<img
v-if="isImageFile(file.name)"
:src="file.url"
alt=""
class="preview-image"
>
<!-- 文件预览区域 -->
<div v-else class="file-preview-container">
<div
class="file-item"
v-for="(file, index) in fileList"
:key="index"
>
<!-- 图片文件预览 -->
<img
v-if="isImageFile(file.name)"
:src="file.url"
alt=""
class="preview-image"
/>
<!-- 压缩包文件预览 -->
<div
v-else
class="zip-preview"
:title="file.name"
>
<i class="el-icon-document"></i>
<div class="file-name">{{ file.name }}</div>
<!-- 压缩包文件预览 -->
<div v-else class="zip-preview" :title="file.name">
<i class="el-icon-document"></i>
<div class="file-name">{{ file.name }}</div>
</div>
<i
class="el-icon-close delete-icon"
@click="$emit('remove-image', index)"
></i>
</div>
</div>
</div>
<i class="el-icon-close delete-icon" @click="$emit('remove-image', index)"></i>
</div>
<div class="upload-actions">
<el-upload
action=""
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
accept="image/*,.zip,.rar,.7z"
multiple
:limit="uploadLimit"
:on-exceed="handleExceed"
:show-file-list="false"
>
<el-button type="primary">
<i class="el-icon-upload"></i>
</el-button>
</el-upload>
<el-button
type="primary"
@click="$emit('start-upload')"
:disabled="
fileList.length === 0 || selectedTag.length === 0
"
>
开始标注
</el-button>
</div>
</div>
</div>
<div class="upload-actions">
<el-upload
action=""
:auto-upload="false"
:on-change="handleFileChange"
:file-list="fileList"
accept="image/*,.zip,.rar,.7z"
multiple
:limit="uploadLimit"
:on-exceed="handleExceed"
:show-file-list="false"
>
<el-button type="primary">
<i class="el-icon-upload"></i>
</el-button>
</el-upload>
<el-button
type="primary"
@click="$emit('start-upload')"
:disabled="fileList.length === 0 || selectedTag.length === 0"
>
开始标注
</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: "FileUploader",
props: {
fileList: {
type: Array,
default: () => []
name: 'FileUploader',
props: {
fileList: {
type: Array,
default: () => [],
},
selectedTag: {
type: Array,
default: () => [],
},
},
selectedTag: {
type: Array,
default: () => []
}
},
computed: {
uploadLimit() {
const hasZip = this.fileList.some(file => this.isCompressedFile(file.name));
return hasZip ? 1 : 50;
}
},
methods: {
isImageFile(filename) {
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
const extension = filename.split('.').pop().toLowerCase();
return imageExtensions.includes(extension);
computed: {
uploadLimit() {
const hasZip = this.fileList.some((file) =>
this.isCompressedFile(file.name),
)
return hasZip ? 1 : 50
},
},
methods: {
isImageFile(filename) {
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
const extension = filename.split('.').pop().toLowerCase()
return imageExtensions.includes(extension)
},
handleFileChange(file, fileList) {
const isZip = this.isCompressedFile(file.name);
const hasImage = fileList.some(f => this.isImageFile(f.name));
const hasZip = fileList.some(f => this.isCompressedFile(f.name));
handleFileChange(file, fileList) {
const isZip = this.isCompressedFile(file.name)
const hasImage = fileList.some((f) => this.isImageFile(f.name))
const hasZip = fileList.some((f) => this.isCompressedFile(f.name))
//
if (isZip && hasImage) {
this.$message.warning('不能同时上传压缩包');
return false;
}
//
if (isZip && hasImage) {
this.$message.warning('不能同时上传压缩包')
return false
}
if (this.isImageFile(file.name) && hasZip) {
this.$message.warning('不能同时上传图片');
return false;
}
if (this.isImageFile(file.name) && hasZip) {
this.$message.warning('不能同时上传图片')
return false
}
if (file.raw) {
const reader = new FileReader();
reader.onload = (e) => {
file.url = e.target.result;
};
reader.readAsDataURL(file.raw);
}
if (file.raw) {
const reader = new FileReader()
reader.onload = (e) => {
file.url = e.target.result
}
reader.readAsDataURL(file.raw)
}
// fileListfile.url
setTimeout(() => {
this.$emit('file-change', file, fileList);
}, 100);
// fileListfile.url
setTimeout(() => {
this.$emit('file-change', file, fileList)
}, 100)
},
isCompressedFile(filename) {
const compressedExtensions = ['zip', 'rar', '7z']
const extension = filename.split('.').pop().toLowerCase()
return compressedExtensions.includes(extension)
},
handleExceed(files, fileList) {
const hasZip = fileList.some((f) => this.isCompressedFile(f.name))
if (hasZip) {
this.$message.warning('压缩包最多只能上传1个')
} else {
this.$message.warning(`最多只能上传50张图片`)
}
},
},
isCompressedFile(filename) {
const compressedExtensions = ['zip', 'rar', '7z'];
const extension = filename.split('.').pop().toLowerCase();
return compressedExtensions.includes(extension);
},
handleExceed(files, fileList) {
const hasZip = fileList.some(f => this.isCompressedFile(f.name));
if (hasZip) {
this.$message.warning('压缩包最多只能上传1个');
} else {
this.$message.warning(`最多只能上传50张图片`);
}
}
}
}
</script>
<style scoped>
.upload-container {
margin-top: 20px;
text-align: center;
margin-top: 20px;
text-align: center;
}
.upload-area {
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin: 0 auto;
height: 220px;
position: relative;
background-color: #fff;
border: 1px solid #dcdfe6;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin: 0 auto;
height: 220px;
position: relative;
}
.upload-tip {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.upload-text {
font-size: 14px;
color: #999;
margin-bottom: 10px;
font-size: 14px;
color: #999;
margin-bottom: 10px;
}
.upload-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
position: absolute;
bottom: 20px;
right: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
position: absolute;
bottom: 20px;
right: 20px;
}
.upload-placeholder {
text-align: center;
color: #999;
font-size: 14px;
text-align: center;
color: #999;
font-size: 14px;
}
.file-preview-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 10px 0;
width: 100%;
height: 100px;
overflow-x: auto;
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 10px 0;
width: 100%;
height: 100px;
overflow-x: auto;
}
.file-item {
position: relative;
width: 80px;
height: 80px;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f7fa;
transition: transform 0.3s ease;
position: relative;
width: 80px;
height: 80px;
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f7fa;
transition: transform 0.3s ease;
}
.file-item:hover {
transform: scale(1.05);
transform: scale(1.05);
}
.preview-image {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 4px;
transition: filter 0.3s ease;
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 4px;
transition: filter 0.3s ease;
}
.preview-image:hover {
filter: brightness(1.1);
filter: brightness(1.1);
}
.zip-preview {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
font-size: 12px;
text-align: center;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
font-size: 12px;
text-align: center;
}
.zip-preview .el-icon-document {
font-size: 24px;
color: #999;
margin-bottom: 4px;
font-size: 24px;
color: #999;
margin-bottom: 4px;
}
.file-name {
font-size: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60px;
font-size: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60px;
}
.delete-icon {
position: absolute;
top: 0px;
right: 0px;
background-color: rgba(255, 0, 0, 0.8);
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
z-index: 1;
position: absolute;
top: 0px;
right: 0px;
background-color: rgba(255, 0, 0, 0.8);
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
z-index: 1;
}
.delete-icon:hover {
background-color: rgba(255, 0, 0, 1);
transform: scale(1.1);
background-color: rgba(255, 0, 0, 1);
transform: scale(1.1);
}
</style>

View File

@ -1,118 +1,133 @@
<template>
<div class="history-content">
<!-- 可滚动区域 -->
<div class="scrollable-content">
<ImageResults
:upload-info="uploadInfo"
:image-results="imageResults"
:grouped-image-results="groupedImageResults"
:selected-images="selectedImages"
:is-sure="isSure"
@hand-click="handleHandClickFromChild"
@confirm-results="$emit('confirm-results', $event)"
/>
</div>
<div class="history-content">
<!-- 可滚动区域 -->
<div class="scrollable-content">
<ImageResults
:upload-info="uploadInfo"
:image-results="imageResults"
:grouped-image-results="groupedImageResults"
:selected-images="selectedImages"
:is-sure="isSure"
@hand-click="handleHandClickFromChild"
@confirm-results="$emit('confirm-results', $event)"
/>
</div>
<!-- 固定在底部的区域 -->
<div class="fixed-bottom-area">
<!-- 标签选择区域 -->
<TagSelector
:tags="tags"
:visible-tags="visibleTags"
:selected-tag="selectedTag"
@toggle-tag="$emit('toggle-tag', $event)"
@toggle-all-tags="$emit('toggle-all-tags')"
/>
<!-- 固定在底部的区域 -->
<div class="fixed-bottom-area">
<!-- 标签选择区域 -->
<TagSelector
:tags="tags"
:visible-tags="visibleTags"
:selected-tag="selectedTag"
@toggle-tag="$emit('toggle-tag', $event)"
@toggle-all-tags="$emit('toggle-all-tags')"
/>
<!-- 图片上传区域 -->
<FileUploader
:file-list="fileList"
:selected-tag="selectedTag"
@file-change="(file, fileList) => $emit('file-change', file, fileList)"
@remove-image="(index) => $emit('remove-image', index)"
@start-upload="$emit('start-upload')"
/>
<!-- 输入备注 -->
<el-input
:rows="1"
placeholder="请输入备注"
type="textarea"
v-model="remarkInfo"
/>
<!-- 图片上传区域 -->
<FileUploader
:file-list="fileList"
:selected-tag="selectedTag"
@file-change="
(file, fileList) => $emit('file-change', file, fileList)
"
@remove-image="(index) => $emit('remove-image', index)"
@start-upload="$emit('start-upload')"
/>
</div>
</div>
</div>
</template>
<script>
import ImageResults from './ImageResults.vue';
import TagSelector from './TagSelector.vue';
import FileUploader from './FileUploader.vue';
import ImageResults from './ImageResults.vue'
import TagSelector from './TagSelector.vue'
import FileUploader from './FileUploader.vue'
import axios from 'axios'
import {getToken} from '@/utils/auth'
import { getToken } from '@/utils/auth'
export default {
name: "HistoryView",
components: {
ImageResults,
TagSelector,
FileUploader
},
props: {
uploadInfo: {
type: String,
default: ''
name: 'HistoryView',
components: {
ImageResults,
TagSelector,
FileUploader,
},
imageResults: {
type: Array,
default: () => []
data() {
return {
remarkInfo: '', //
}
},
groupedImageResults: {
type: Array,
default: () => []
props: {
uploadInfo: {
type: String,
default: '',
},
imageResults: {
type: Array,
default: () => [],
},
groupedImageResults: {
type: Array,
default: () => [],
},
selectedImages: {
type: Object,
default: () => ({}),
},
tags: {
type: Array,
default: () => [],
},
visibleTags: {
type: Array,
default: () => [],
},
selectedTag: {
type: Array,
default: () => [],
},
fileList: {
type: Array,
default: () => [],
},
isSure: {
type: String,
default: '',
},
},
selectedImages: {
type: Object,
default: () => ({})
methods: {
handleHandClickFromChild(result, index) {
this.$emit('hand-click', result, index)
},
},
tags: {
type: Array,
default: () => []
},
visibleTags: {
type: Array,
default: () => []
},
selectedTag: {
type: Array,
default: () => []
},
fileList: {
type: Array,
default: () => []
},
isSure: {
type: String,
default: ''
}
},
methods: {
handleHandClickFromChild(result, index) {
this.$emit('hand-click', result, index);
}
}
}
</script>
<style scoped>
.history-content {
height: 100%;
width: 60%;
margin-left: 22%;
display: flex;
flex-direction: column;
height: 100%;
width: 60%;
margin-left: 22%;
display: flex;
flex-direction: column;
}
.scrollable-content {
flex: 1;
overflow-y: auto;
flex: 1;
overflow-y: auto;
}
.fixed-bottom-area {
position: sticky;
bottom: 0;
padding-top: 20px;
position: sticky;
bottom: 0;
padding-top: 20px;
}
</style>

File diff suppressed because it is too large Load Diff