Files
schisandra-cloud-album-front/src/views/Photograph/ImageUpload/ImageUpload.vue
2025-01-14 17:34:55 +08:00

213 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<ADrawer v-model:open="upload.openUploadDrawer" placement="right" title="上传照片" width="40%" @close="cancelUpload">
<template #extra>
<AFlex :vertical="false" align="center" gap="large" justify="center">
<ASelect size="middle" style="width: 150px">
</ASelect>
<ASelect size="middle" style="width: 150px">
</ASelect>
</AFlex>
</template>
<div class="upload-container">
<AUploadDragger
v-model:fileList="fileList"
:beforeUpload="beforeUpload"
:customRequest="customUploadRequest"
:directory="false"
:maxCount="10"
:multiple="true"
:disabled="predicting"
:progress="progress"
@remove="removeFile"
accept="image/*"
list-type="picture"
method="post"
name="file"
>
<p class="ant-upload-drag-icon">
<FileImageOutlined/>
</p>
<p v-show="!predicting" class="ant-upload-text">单击或拖动文件到此区域以上传</p>
<p v-show="!predicting" class="ant-upload-hint">
支持单次或批量上传严禁上传非法图片违者将受到法律惩戒
</p>
<p v-show="predicting" class="ant-upload-hint">
AI 正在识别图片请稍候...
</p>
<AProgress :stroke-color="{'0%': '#108ee9','100%': '#87d068',}" :percent="progressPercent" status="active"
:show-info="true" size="small" type="line" v-show="predicting" style="width: 80%"/>
</AUploadDragger>
</div>
</ADrawer>
</template>
<script lang="ts" setup>
import useStore from "@/store";
import type {UploadProps} from 'ant-design-vue';
import {message} from "ant-design-vue";
import {initNSFWJs, predictNSFW} from "@/utils/nsfw/nsfw.ts";
import i18n from "@/locales";
import {NSFWJS} from "nsfwjs";
import {animePredictImage} from "@/utils/tfjs/anime_classifier.ts";
import {animePredictImagePro} from "@/utils/tfjs/anime_classifier_pro.ts";
import {fnDetectFace} from "@/utils/tfjs/face_extraction.ts";
import {cocoSsdPredict} from "@/utils/tfjs/mobilenet.ts";
import {predictLandscape} from "@/utils/tfjs/landscape_recognition.ts";
import {useRequest} from 'alova/client';
import {uploadFile} from "@/api/file";
const predicting = ref<boolean>(false);
const progressPercent = ref<number>(0);
const upload = useStore().upload;
const image: HTMLImageElement = document.createElement('img');
const fileList = ref([]);
const progress: UploadProps['progress'] = {
strokeColor: {
'0%': '#108ee9',
'100%': '#87d068',
},
strokeWidth: 3,
format: (percent: any) => `${parseFloat(percent.toFixed(2))}%`,
class: 'progress-bar',
};
/**
* 图片上传前的校验
* @param file
*/
async function beforeUpload(file: File) {
predicting.value = true;
upload.clearPredictResult();
progressPercent.value = 0; // 初始化进度条
// 创建图片对象
image.src = URL.createObjectURL(file);
image.addEventListener('webglcontextlost', (_event) => {
window.location.reload();
});
// 更新进度条函数,逐步增加
const smoothUpdateProgress = async (targetPercent, duration) => {
const increment = (targetPercent - progressPercent.value) / (duration / 50);
return new Promise((resolve) => {
const interval = setInterval(() => {
if (progressPercent.value >= targetPercent) {
clearInterval(interval);
resolve(false);
} else {
progressPercent.value = Math.min(progressPercent.value + increment, targetPercent);
}
}, 50);
});
};
// 图片 NSFW 检测
const nsfw: NSFWJS = await initNSFWJs();
await smoothUpdateProgress(10, 500); // 平滑更新进度条
const isNSFW: boolean = await predictNSFW(nsfw, image);
await smoothUpdateProgress(20, 500); // 平滑更新进度条
if (isNSFW) {
message.error(i18n.global.t('comment.illegalImage'));
predicting.value = false;
progressPercent.value = 0; // 重置进度条
return false;
}
// Step 1: 动漫预测
const prediction1 = await animePredictImage(image);
await smoothUpdateProgress(40, 500); // 平滑更新进度条
const prediction2 = await animePredictImagePro(image);
await smoothUpdateProgress(60, 500); // 平滑更新进度条
upload.predictResult.isAnime = prediction1 === 'Anime' && (prediction2 === 'Furry' || prediction2 === 'Anime');
// Step 2: 人脸检测
const faceImageData = await fnDetectFace(image);
await smoothUpdateProgress(80, 500); // 平滑更新进度条
upload.predictResult.hasFace = !!faceImageData;
// Step 3: 目标识别
const cocoResults = await cocoSsdPredict(image);
await smoothUpdateProgress(90, 500); // 平滑更新进度条
if (cocoResults.length > 0) {
const classSet = new Set(cocoResults.map(result => result.class));
upload.predictResult.objectArray = Array.from(classSet);
}
// Step 4: 风景识别
upload.predictResult.landscape = await predictLandscape(image);
await smoothUpdateProgress(100, 500); // 平滑更新进度条
// 完成
predicting.value = false;
image.removeEventListener('webglcontextlost', () => void 0);
return true;
}
const {uploading, send: submitFile, abort} = useRequest(uploadFile, {
immediate: false,
debounce: 500,
});
/**
* 自定义上传请求
* @param file
*/
async function customUploadRequest(file: any) {
const formData = new FormData();
formData.append("file", file.file);
formData.append("result", JSON.stringify({
uid: file.file.uid,
fileName: file.file.name, // 添加文件名
fileType: file.file.type, // 添加文件类型
detectionResult: upload.predictResult,
}));
watch(
() => uploading.value,
(upload) => {
if (upload && upload.loaded && upload.total) {
file.onProgress({percent: Number(Math.round((upload.loaded / upload.total) * 100).toFixed(2))}, file);
}
},
);
submitFile(formData).then((response: any) => {
file.onSuccess(response.data, file);
}).catch(file.onError);
}
/**
* 取消上传
*/
function cancelUpload() {
abort();
fileList.value = [];
upload.clearPredictResult();
predicting.value = false;
progressPercent.value = 0; // 重置进度条
}
/**
* 删除文件
* @param file
*/
function removeFile(file: any) {
fileList.value = fileList.value.filter((item: any) => item.uid !== file.uid);
}
</script>
<style lang="less" scoped>
</style>