| @@ -5,5 +5,13 @@ NODE_ENV='test' | |||
| VITE_APP_TITLE = 'Metavatar-PC' | |||
| VITE_APP_PORT = 3000 | |||
| # VITE_APP_BASE_API = 'https://photo.metavatar.cc/C' | |||
| VITE_APP_BASE_API = 'https://phototest.metavatar.cc/C' | |||
| # NODE_ENV='development' | |||
| # VITE_APP_TITLE = 'Metavatar-PC' | |||
| # VITE_APP_PORT = 3000 | |||
| # VITE_APP_BASE_API = 'https://phototest.metavatar.cc/C' | |||
| @@ -77,4 +77,4 @@ | |||
| "repository": "https://gitee.com/youlaiorg/vue3-element-admin.git", | |||
| "author": "有来开源组织", | |||
| "license": "MIT" | |||
| } | |||
| } | |||
| @@ -66,11 +66,18 @@ export function delImgApi(id) { | |||
| * @params id | |||
| * @returns data | |||
| */ | |||
| export function checkPhotoApi(material) { | |||
| export function baiduPhotoApi(data) { | |||
| const formData = new FormData(); | |||
| formData.append('file', data); | |||
| return request({ | |||
| url: `api/personPhoto/checkPhoto?material=${material}`, | |||
| method: 'get', | |||
| }) | |||
| url: 'api/personPhoto/baiduPhoto', | |||
| method: 'post', | |||
| data: formData, | |||
| headers: { | |||
| 'Content-Type': 'application/form-data' | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| @@ -79,11 +86,17 @@ export function checkPhotoApi(material) { | |||
| * @returns data | |||
| */ | |||
| export function checkFaceApi(data) { | |||
| const formData = new FormData(); | |||
| formData.append('file', data); | |||
| return request({ | |||
| url: `suimang/image_qualit`, | |||
| url: 'api/personPhoto/checkPhoto', | |||
| method: 'post', | |||
| data: { img: data } | |||
| }) | |||
| data: formData, | |||
| headers: { | |||
| 'Content-Type': 'application/form-data' | |||
| } | |||
| }); | |||
| } | |||
| /** | |||
| @@ -35,4 +35,16 @@ export function videoHyApi(id) { | |||
| url: `/api/userPhotoVideo/videoHy?id=${id}`, | |||
| method: 'get' | |||
| }) | |||
| } | |||
| /** | |||
| * @description 查询是否有视频更新 | |||
| * @params id | |||
| * @returns data | |||
| */ | |||
| export function checkVideoStatusApi(time) { | |||
| return request({ | |||
| url: `/api/userPhotoVideo/checkVideoStatus?time${time}`, | |||
| method: 'get' | |||
| }) | |||
| } | |||
| @@ -1,7 +1,11 @@ | |||
| import { createI18n } from 'vue-i18n'; | |||
| import { useAppStore } from '@/store/modules/app'; | |||
| const appStore = useAppStore(); | |||
| import { store } from '@/store/index'; // 因为不是在setup环境而是js中,必须重新初始化加载store | |||
| const appStore = useAppStore(store); //pinia | |||
| // const appStore = useAppStore(); | |||
| // 本地语言包 | |||
| import enLocale from './package/en'; | |||
| import zhCnLocale from './package/zh-cn'; | |||
| @@ -72,7 +72,7 @@ export default { | |||
| AipictureTip:'Please customize your picture', | |||
| chooseMusic:'Choose Music', | |||
| searchMusic:'Search Music', | |||
| comingSoon:'coming soon', | |||
| comingSoon:'COMING SOON', | |||
| hotMusic:'Hit song', | |||
| inputRequirement:'Please enter your requirements, for example: Please generate a beverage advertisement of 500 words or less', | |||
| GeneratedText:'Generated Text', | |||
| @@ -81,7 +81,7 @@ export default { | |||
| tooltip1:'Click to listen', | |||
| tooltip2:'Add Pause', | |||
| uploadImgTip1:'Click here to upload pictures', | |||
| uploadImgTip2:'Please ensure that the uploaded image is an open face image', | |||
| uploadImgTip2:'Please upload unobstructed frontal photo', | |||
| entered:'entered' | |||
| }, | |||
| @@ -97,6 +97,7 @@ export default { | |||
| more: "More", | |||
| loading: "Loading , just a moment", | |||
| loading1: "just a moment", | |||
| compress:'Compressed picture', | |||
| sensitiveness: "Sensitive information is being tested", | |||
| chackFace: "Face detection in progress", | |||
| upLoadPictures: "Uploading pictures", | |||
| @@ -108,11 +109,12 @@ export default { | |||
| generationSucceed: "Succeed", | |||
| manuscriptText: "Manuscript", | |||
| generatingText: "The video is generating, Please wait a moment", | |||
| generationFailedText: "Video generation failed, you can click the button below to edit the generation again ", | |||
| generationFailedText: "Video generation failed, click Edit button to retry.", | |||
| edit:'Edit', | |||
| upSharpness:'Upgrade image quality', | |||
| upSharpnessTips:'If you are satisfied with the preview, you can click here to improve the video sharpness ~', | |||
| freeUserTip:'Free user-generated videos are limited to 1 minute', | |||
| hasNewVideo:'You have a new video generated!!', | |||
| }, | |||
| // 个人账户 | |||
| @@ -150,7 +152,7 @@ export default { | |||
| selectPlan: "SELECT PLAN", | |||
| WeChatPay: "WeChatPay", | |||
| Alipay: "Alipay", | |||
| score:'score', | |||
| score:'Credit', | |||
| day:'day', | |||
| second:'second', | |||
| yes:'yes', | |||
| @@ -160,7 +162,7 @@ export default { | |||
| AIGeneratesPhotoPrices:'AI generates photo unit prices', | |||
| UnitChargeDuration:'Unit charge duration', | |||
| Language:'Language', | |||
| sound:'sound', | |||
| sound:'Sound', | |||
| IndividualVideoDuration:'Individual video duration', | |||
| AIGeneratedPortrait:'AI generated portrait', | |||
| PhotoSinging:'Photo singing', | |||
| @@ -97,6 +97,7 @@ export default { | |||
| more: "更多", | |||
| loading: "加载中,请稍等", | |||
| loading1: "请稍等", | |||
| compress:'正在压缩图片', | |||
| sensitiveness: "正在进行敏感信息检测", | |||
| chackFace: "正在进行人脸检测", | |||
| upLoadPictures: "正在上传图片", | |||
| @@ -113,6 +114,7 @@ export default { | |||
| upSharpness:'升级画质', | |||
| upSharpnessTips:'如果对预览效果满意,您可以点击此处来提升视频清晰度~', | |||
| freeUserTip:'免费用户生成视频时长限制为1分钟', | |||
| hasNewVideo:'您有新的视频生成了!', | |||
| }, | |||
| // 个人账户 | |||
| @@ -2,6 +2,7 @@ | |||
| export {} | |||
| declare global { | |||
| const EffectScope: typeof import('vue')['EffectScope'] | |||
| const ElForm: typeof import('element-plus/es')['ElForm'] | |||
| const ElMessage: typeof import('element-plus/es')['ElMessage'] | |||
| const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] | |||
| const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] | |||
| @@ -270,6 +271,7 @@ import { UnwrapRef } from 'vue' | |||
| declare module 'vue' { | |||
| interface ComponentCustomProperties { | |||
| readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']> | |||
| readonly ElForm: UnwrapRef<typeof import('element-plus/es')['ElForm']> | |||
| readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']> | |||
| readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']> | |||
| readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']> | |||
| @@ -1,18 +1,20 @@ | |||
| import axios from 'axios' | |||
| import router from '@/router/index' | |||
| import { ElMessage } from 'element-plus' | |||
| // ElMessage | |||
| import { useAppStore } from '@/store/modules/app'; | |||
| import { userInfoModules } from "@/store/modules/userInfo"; | |||
| // import stores from '@/store/index' | |||
| import { store } from '@/store/index'; // 因为不是在setup环境而是js中,必须重新初始化加载store | |||
| const appStore = useAppStore(store); //pinia | |||
| const userInfoPinia = userInfoModules(store); //pinia | |||
| // const userInfoPinia = userInfoModules(store); //pinia | |||
| // 开发环境需以 "/api" 拼接,其他环境需以 "/C" 拼接 | |||
| const env = process.env.NODE_ENV == 'development' ? "/api" : "/C" | |||
| // const env = process.env.NODE_ENV == 'development' ? "" : "" | |||
| // console.log(process.env.NODE_ENV, 'env') | |||
| console.log(env) | |||
| /** | |||
| * @description:发送Axios请求 | |||
| */ | |||
| @@ -42,7 +44,10 @@ request.interceptors.response.use( | |||
| function (response) { | |||
| if (!response.data) { | |||
| ElMessage.error($t('tips.systemException')); | |||
| // ElMessage.error($t('tips.systemException')); | |||
| ElMessage.error( | |||
| appStore.language == "zh-cn" ? `服务器繁忙,请重试!` : `The server is busy, please try again` | |||
| ); | |||
| } | |||
| let code = response.data.code | |||
| // 当token无效(过期或损坏)时 1053为未登录 | |||
| @@ -50,13 +55,19 @@ request.interceptors.response.use( | |||
| localStorage.removeItem("AccessToken"); | |||
| router.push("/login"); | |||
| // ElMessage.error("登录过期,请重新登录!"); | |||
| ElMessage.error($t('tips.tokenExpired')); | |||
| // ElMessage.error($t('tips.tokenExpired')); | |||
| ElMessage.error( | |||
| appStore.language == "zh-cn" ? `登录过期,请重新登录!` : `Login expired, please login again` | |||
| ); | |||
| return | |||
| // 弹出错误 | |||
| } else if (code != 200) { | |||
| if (code == 500) { | |||
| // ElMessage.error("系统异常,请稍后再试"); | |||
| ElMessage.error($t('tips.systemException')); | |||
| // ElMessage.error($t('tips.systemException')); | |||
| ElMessage.error( | |||
| appStore.language == "zh-cn" ? `服务器繁忙,请重试!` : `The server is busy, please try again` | |||
| ); | |||
| // 其他错误 | |||
| } else { | |||
| return Promise.reject(response.data) | |||
| @@ -694,7 +694,7 @@ import { | |||
| personPhotoUserListList, | |||
| awsImgUpload, | |||
| delImgApi, | |||
| checkPhotoApi, | |||
| baiduPhotoApi, | |||
| checkFaceApi, | |||
| videoUpload, | |||
| saveImgApi, | |||
| @@ -850,38 +850,33 @@ async function lookImg(e) { | |||
| maxSizeMB: 4, // 最大压缩大小为 4MB | |||
| useWebWorker: true, // 使用 Web Worker 进行压缩,提高性能 | |||
| }; | |||
| imgUploadTips.value = "compress"; | |||
| const compressedFile = await imageCompression(e.target.files[0], options); | |||
| imgUploadTips.value = "sensitiveness"; | |||
| try { | |||
| await getImageBase64(compressedFile).then(async (base64) => { | |||
| const res = await checkFaceApi(base64); | |||
| console.log(res); | |||
| // axios({ | |||
| // method: "post", | |||
| // url: "https://phototest.metavatar.cc/suimang/image_qualit", | |||
| // // headers: { | |||
| // // "content-type": "multipart/form-data", | |||
| // // token: AccessToken, | |||
| // // "Access-Control-Allow-Credentials": true, | |||
| // // }, | |||
| // data: { img: base64 }, | |||
| // }).then(async (res) => { | |||
| // // const res2 = await checkPhotoApi(compressedFile); | |||
| // // // const res = await checkFaceApi(compressedFile); | |||
| // // imgUrl.value = URL.createObjectURL(compressedFile); | |||
| // // imgData.value = compressedFile; | |||
| // // showUploadBtn.value = 4; | |||
| // // submitImgBtn.value = true; | |||
| // // compressionImgLoad.value = false; | |||
| // // await changeFile(); | |||
| // }); | |||
| }); | |||
| const res = await baiduPhotoApi(compressedFile); | |||
| if (res.code != 200) { | |||
| throw new Error(); | |||
| } | |||
| // 图片人脸信息检测通过时,继续检测敏感信息 | |||
| imgUploadTips.value = "chackFace"; | |||
| const res2 = await checkFaceApi(compressedFile); | |||
| if (res2.code != 200) { | |||
| throw new Error(); | |||
| } | |||
| imgUrl.value = URL.createObjectURL(compressedFile); | |||
| imgData.value = compressedFile; | |||
| showUploadBtn.value = 4; | |||
| submitImgBtn.value = true; | |||
| compressionImgLoad.value = false; | |||
| await changeFile(); | |||
| } catch (error) { | |||
| ElMessage.error( | |||
| lanChange.value == "zh-cn" | |||
| ? `图片涉及敏感内容,上传失败` | |||
| : `Image related to sensitive content, upload failed` | |||
| ? `图片不符合规范,上传失败` | |||
| : `Image does not meet the specifications, upload failed` | |||
| ); | |||
| showFace.value = 1; | |||
| showUploadBtn.value = 2; | |||
| @@ -925,15 +920,25 @@ async function changeFile() { | |||
| imgUploadTips.value = "upLoadPictures"; | |||
| compressionImgLoad.value = true; | |||
| showUploadBtn.value = 3; | |||
| imgUrl.value = URL.createObjectURL(imgData.value); | |||
| // imgUrl.value = URL.createObjectURL(imgData.value); | |||
| let formData = new FormData(); | |||
| const timestamp = Date.now(); | |||
| formData.append("file", imgData.value, timestamp + ".png"); | |||
| try { | |||
| // 上传图片 | |||
| const res = await awsImgUpload(formData); | |||
| if (res.code != 200) { | |||
| throw new Error(); | |||
| } | |||
| // 保存图片 | |||
| const res2 = await saveImgApi({ material: res.data.url }); | |||
| if (res2.code != 200) { | |||
| throw new Error(); | |||
| } else { | |||
| ElMessage.success( | |||
| lanChange.value == "zh-cn" ? `上传成功` : `Upload successfully` | |||
| ); | |||
| } | |||
| // 重新获取列表 | |||
| compressionImgLoad.value = false; | |||
| showUploadBtn.value = 4; | |||
| @@ -941,23 +946,17 @@ async function changeFile() { | |||
| getAiFaceUserList(1, 10, 3); | |||
| pageNumUser.value = 1; | |||
| overLoadUser.value = false; | |||
| if (res2.code == 200) { | |||
| ElMessage.success( | |||
| lanChange.value == "zh-cn" ? `上传成功` : `Upload successfully` | |||
| ); | |||
| } else { | |||
| ElMessage.error( | |||
| lanChange.value == "zh-cn" ? `上传失败` : `Upload failure` | |||
| ); | |||
| } | |||
| faceItemActive.value = res2.data.id; | |||
| imgUrl.value = res2.data.material; | |||
| submitImgBtn.value = false; | |||
| console.log(123654); | |||
| showUploadBtn.value = 1; | |||
| imgUploadTips.value = null; | |||
| } catch (error) { | |||
| ElMessage.error(lanChange.value == "zh-cn" ? `上传失败` : `Upload failure`); | |||
| imgUrl.value = null; | |||
| showFace.value = 1; | |||
| showUploadBtn.value = 2; | |||
| compressionImgLoad.value = false; | |||
| imgUploadTips.value = null; | |||
| } | |||
| } | |||
| @@ -972,8 +971,14 @@ function chooseMyImg() { | |||
| } | |||
| // 点击AI头像区的+号 | |||
| function clickAddBtn() { | |||
| faceItemActive.value = null; | |||
| // if (!imgUploadTips.value) { | |||
| // showUploadBtn.value = 3; | |||
| // submitImgBtn.value = false; | |||
| // compressionImgLoad.value = true; | |||
| // return; | |||
| // } | |||
| showUploadBtn.value = 2; | |||
| faceItemActive.value = null; | |||
| } | |||
| //#endregion --------------------------- | |||
| @@ -1084,6 +1089,8 @@ async function getPreviewAudio() { | |||
| return; | |||
| } | |||
| previewAudioUrl.value = res.data.data; | |||
| // previewAudioUrl.value = | |||
| // "/suimang/static/tts/2023-07-04/16884538755531874.wav"; | |||
| let audioA = document.getElementById("audio"); | |||
| audioA.addEventListener("loadedmetadata", async () => { | |||
| audioA.muted = false; // 将 muted 属性设置为 false | |||
| @@ -1208,19 +1215,19 @@ const handleFileChange = async () => { | |||
| if (uploadAudioTime.value > 60) { | |||
| ElMessage.error( | |||
| lanChange.value == "zh-cn" | |||
| ? `上传失败,免费用户上传音频时长最多为60秒` | |||
| : `Upload failed, free users can upload audio for up to 60 seconds` | |||
| ? `上传音频大小或者长度超出限制,请重新上传` | |||
| : `The size or length of the uploaded audio exceeds the limit. Please upload it again` | |||
| ); | |||
| audioUrl.value = null; | |||
| audioData.value = null; | |||
| return; | |||
| } | |||
| // 限制3mb | |||
| if (file.size / 1048576 > 3) { | |||
| // 限制5mb | |||
| if (file.size / 1048576 > 5) { | |||
| ElMessage.error( | |||
| lanChange.value == "zh-cn" | |||
| ? `上传失败,免费用户上传音频大小最大为3MB` | |||
| : `Failed to upload. The maximum size of audio uploaded by free users is 3MB` | |||
| ? `上传音频大小或者长度超出限制,请重新上传` | |||
| : `The size or length of the uploaded audio exceeds the limit. Please upload it again` | |||
| ); | |||
| audioUrl.value = null; | |||
| audioData.value = null; | |||
| @@ -12,6 +12,10 @@ | |||
| ><i-ep-search | |||
| /></el-icon> | |||
| </div> | |||
| <!-- 更新视频提示 --> | |||
| <div class="hasNewVideoTips" :class="hasNewCreate ? '' : 'hasNew'"> | |||
| {{ $t("myCreating.hasNewVideo") }} | |||
| </div> | |||
| </div> | |||
| <!-- 作品列表 --> | |||
| <div v-if="!isLoading"> | |||
| @@ -127,7 +131,7 @@ | |||
| > | |||
| <div class="videoBox"> | |||
| <!-- 免费用户1分钟 --> | |||
| <div v-if="false" class="overtimeTips"> | |||
| <div v-if="true" class="overtimeTips"> | |||
| <img src="../../assets/img/tips.png" alt="" /> | |||
| {{ $t("myCreating.freeUserTip") }} | |||
| </div> | |||
| @@ -243,6 +247,7 @@ import { | |||
| userPhotoVideoListApi, | |||
| delUserPhotoVideoByIDApi, | |||
| videoHyApi, | |||
| checkVideoStatusApi, | |||
| } from "@/apis/myCreating"; | |||
| import { timestampToTime } from "@/utils/tools"; | |||
| import { useI18n } from "vue-i18n"; | |||
| @@ -428,23 +433,70 @@ function delPhotoVideo(videoID) { | |||
| ElMessage.error(err.message); | |||
| }); | |||
| } | |||
| //#region 定时刷新 | |||
| const hasNewCreate = ref(false); //提示框显示隐藏 | |||
| const nowTime = Date.now(); | |||
| const checkVideoStatus = async () => { | |||
| console.log(nowTime); | |||
| // const res = await checkVideoStatusApi(); | |||
| // if (res.code == 2115) { | |||
| // hasNewCreate.value = true; | |||
| // const changeHasNewCreate = setTimeout(() => { | |||
| // hasNewCreate.value = false; | |||
| // }, 5000); | |||
| // getPhotoVideoList(1, 10, ""); | |||
| // } | |||
| }; | |||
| const startLogin = () => { | |||
| // 每隔 3 秒执行一次登录请求 | |||
| const intervalId = setInterval(checkVideoStatus, 3000); | |||
| // 在组件销毁前清除定时器 | |||
| onUnmounted(() => { | |||
| clearInterval(intervalId); | |||
| }); | |||
| }; | |||
| //#endregion --------------------------- | |||
| //#region 页面初始化 | |||
| onMounted(() => { | |||
| isLoading.value = true; | |||
| getPhotoVideoList(pageNum.value, pageSize.value, title.value); | |||
| // startLogin(); | |||
| }); | |||
| //#endregion --------------------------- | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .page { | |||
| position: relative; | |||
| min-height: calc(100vh - 80px); | |||
| padding: 15px 130px; | |||
| background-color: #fff; | |||
| border-radius: 25px 0 0; | |||
| width: 100%; | |||
| overflow: hidden; | |||
| .top { | |||
| position: relative; | |||
| margin-bottom: 35px; | |||
| .hasNewVideoTips { | |||
| position: absolute; | |||
| top: 0; | |||
| left: 50%; | |||
| transform: translateX(-50%); | |||
| width: 400px; | |||
| padding: 5px 30px; | |||
| color: #fff; | |||
| background-color: #000; | |||
| text-align: center; | |||
| font-size: 20px; | |||
| transition: all 0.5s; | |||
| opacity: 1; | |||
| border-radius: 15px; | |||
| } | |||
| .hasNew { | |||
| opacity: 0; | |||
| transition: all 0.5s; | |||
| } | |||
| } | |||
| .loadingIcon { | |||
| height: 100px; | |||
| @@ -465,7 +517,6 @@ onMounted(() => { | |||
| // height: 200px; | |||
| margin-top: 5px; | |||
| padding-bottom: 50px; | |||
| cursor: pointer; | |||
| .workItem { | |||
| position: relative; | |||
| width: 300px; | |||
| @@ -58,27 +58,16 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => { | |||
| changeOrigin: true, | |||
| rewrite: (path) => path.replace(/^\/api/, ""), | |||
| }, | |||
| "/api/suimang": { | |||
| // target: https://phototest.metavatar.cc/C, // 线上接口地址 | |||
| target:'https://phototest.metavatar.cc/', // 线上接口地址 | |||
| changeOrigin: true, | |||
| rewrite: (path) => path.replace(/^\/api\/suimang/, "/suimang"), | |||
| // pathRewrite: { | |||
| // "^/api/suimang": "/suimang", | |||
| // } | |||
| }, | |||
| "/suimang": { | |||
| // target: https://phototest.metavatar.cc/C, // 线上接口地址 | |||
| target:'https://phototest.metavatar.cc/', // 线上接口地址 | |||
| changeOrigin: true, | |||
| rewrite: (path) => path.replace(/^\/suimang/, '/suimang/image_qualit') | |||
| // pathRewrite: { | |||
| // "^/api/suimang": "/suimang", | |||
| // } | |||
| }, | |||
| // "/C/api/suimang": { | |||
| // target:'https://phototest.metavatar.cc/', // 线上接口地址 | |||
| // changeOrigin: true, | |||
| // rewrite: (path) => path.replace(/^\/C\/api\/suimang/, '/suimang/image_qualit') | |||
| // }, | |||
| // "/api/suimang": { | |||
| // target:'https://phototest.metavatar.cc/', // 线上接口地址 | |||
| // changeOrigin: true, | |||
| // rewrite: (path) => path.replace(/^\/api\/suimang/, '/suimang/image_qualit') | |||
| // }, | |||
| }, | |||
| }, | |||
| plugins: [ | |||