🚧 developing

This commit is contained in:
2025-02-24 17:10:01 +08:00
parent 5befcddaf5
commit 0e1b7c32b0
10 changed files with 197 additions and 62 deletions

View File

@@ -1,7 +1,6 @@
import {service} from "@/utils/alova/service.ts"; import {service} from "@/utils/alova/service.ts";
import {uploadImageRequest} from "@/types/upscale";
export const uploadImage = (data: uploadImageRequest) => { export const uploadImage = (data: any) => {
return service.Post('/api/auth/phone/upload', { return service.Post('/api/auth/phone/upload', {
image: data.image, image: data.image,
access_token: data.access_token, access_token: data.access_token,
@@ -13,3 +12,17 @@ export const uploadImage = (data: uploadImageRequest) => {
} }
}); });
}; };
/**
* 上传分享图片
* @param data
*/
export const sharePhoneUploadApi = (data: any) => {
return service.Post('/api/auth/phone/share/upload', {
...data,
}, {
meta: {
ignoreToken: false,
signature: false,
}
});
};

View File

@@ -74,3 +74,21 @@ export const queryShareOverviewApi = () => {
}, },
}); });
}; };
/**
* 删除分享记录
* @param id
* @param invite_code
* @param album_id
*/
export const deleteShareRecordApi = (id: number, invite_code: string, album_id: number) => {
return service.Post('/api/auth/share/record/delete', {
id: id,
invite_code: invite_code,
album_id: album_id,
}, {
meta: {
ignoreToken: false,
signature: false,
},
});
};

View File

@@ -1,6 +1,6 @@
export default [ export default [
{ {
path: '/main/photo/phone', path: '/main/photo/upscale',
name: 'upscale', name: 'upscale',
component: () => import('@/views/Upscale/index.vue'), component: () => import('@/views/Upscale/index.vue'),
meta: { meta: {

View File

@@ -1,5 +0,0 @@
export interface uploadImageRequest {
image: string;
access_token: string;
user_id: string;
}

View File

@@ -0,0 +1,26 @@
/**
* 将 File 对象转换为包含前缀的 Base64 字符串
* @param file - 要转换的 File 对象
* @returns 返回一个 Promise解析为包含前缀的 Base64 字符串
*/
export function fileToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
// 创建 FileReader 对象
const reader = new FileReader();
// 读取文件内容
reader.readAsDataURL(file);
// 读取成功时触发
reader.onload = () => {
// 获取 Base64 字符串(包含前缀)
const base64String = reader.result as string;
resolve(base64String);
};
// 读取失败时触发
reader.onerror = (error) => {
reject(error);
};
});
}

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="upscale-upload-content"> <div class="share-upload-content">
<AUploadDragger <AUploadDragger
name="file" name="file"
accept="image/*" accept="image/*"
@@ -7,29 +7,30 @@
:directory="false" :directory="false"
:maxCount="1" :maxCount="1"
:beforeUpload="beforeUpload" :beforeUpload="beforeUpload"
:fileList="fileList" :custom-request="customRequest"
v-model:fileList="fileList"
:loading="uploading" :loading="uploading"
:showUploadList="true"> :showUploadList="false">
<div class="upscale-upload-content-main"> <div class="share-upload-content-main">
<ABadge :offset="[-10, 10]"> <ABadge :offset="[-10, 10]">
<template #count> <template #count>
<AAvatar :size="25" :src="sueccess" v-if="fileList.length > 0"/> <AAvatar :size="25" :src="succeed" v-if="fileList.length > 0"/>
<AAvatar :size="25" :src="warn" v-if="fileList.length === 0"/> <AAvatar :size="25" :src="warn" v-if="fileList.length === 0"/>
</template> </template>
<AAvatar shape="square" :size="70" :src="file"/> <AAvatar shape="square" :size="70" :src="file"/>
</ABadge> </ABadge>
<span class="upscale-upload-text"> <span class="share-upload-text">
点击上传图片 点击上传图片
</span> </span>
</div> </div>
</AUploadDragger> </AUploadDragger>
<ADivider orientation="center" :plain="true"> <ADivider orientation="center" :plain="true">
<span class="upscale-divider-title">图片预览</span> <span class="share-divider-title">图片预览</span>
</ADivider> </ADivider>
<div class="upscale-upload-image" v-if="fileList.length > 0"> <div class="share-upload-image" v-if="fileList.length > 0">
<ABadge v-for="(item, index) in fileList" :key="index"> <ABadge>
<template #count> <template #count>
<AButton type="text" size="small" class="upscale-file-btn"> <AButton type="text" size="small" class="share-file-btn" @click="fileList = []">
<template #icon> <template #icon>
<AAvatar shape="square" :size="25" :src="remove"/> <AAvatar shape="square" :size="25" :src="remove"/>
</template> </template>
@@ -37,7 +38,7 @@
</template> </template>
<AAvatar shape="square" :size="100"> <AAvatar shape="square" :size="100">
<template #icon> <template #icon>
<AImage :src="convertFileToUrl(item.file)" width="100%" height="100%"/> <AImage :src="convertFileToUrl(fileList?.[0].originFileObj)" width="100%" height="100%"/>
</template> </template>
</AAvatar> </AAvatar>
</ABadge> </ABadge>
@@ -46,7 +47,7 @@
<ADivider/> <ADivider/>
<div> <div>
<AButton type="primary" size="large" :disabled="fileList.length === 0" :loading="uploading" <AButton type="primary" size="large" :disabled="fileList.length === 0" :loading="uploading"
class="upscale-upload-btn"> class="share-upload-btn" @click="sendImage()">
发送图片 发送图片
</AButton> </AButton>
</div> </div>
@@ -55,44 +56,49 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import file from "@/assets/svgs/file.svg"; import file from "@/assets/svgs/file.svg";
import useStore from "@/store"; import succeed from '@/assets/svgs/success.svg';
import sueccess from '@/assets/svgs/success.svg';
import warn from '@/assets/svgs/warn.svg'; import warn from '@/assets/svgs/warn.svg';
import remove from '@/assets/svgs/remove.svg'; import remove from '@/assets/svgs/remove.svg';
import empty from '@/assets/svgs/empty.svg'; import empty from '@/assets/svgs/empty.svg';
import {message} from "ant-design-vue"; import {message} from "ant-design-vue";
import {useI18n} from "vue-i18n";
import i18n from "@/locales"; import i18n from "@/locales";
import {initNSFWJs, predictNSFW} from "@/utils/tfjs/nsfw.ts"; import {initNSFWJs, predictNSFW} from "@/utils/tfjs/nsfw.ts";
import {NSFWJS} from "nsfwjs"; import {NSFWJS} from "nsfwjs";
import {sharePhoneUploadApi} from "@/api/phone";
import {useI18n} from "vue-i18n";
import {fileToBase64} from "@/utils/imageUtils/fileToBase64.ts";
const upscale = useStore().upscale;
const route = useRoute(); const route = useRoute();
const {t} = useI18n();
const fileList = ref<any[]>([]); const fileList = ref<any[]>([]);
const {t} = useI18n();
const uploading = ref(false); const uploading = ref(false);
// async function sendImage() { async function sendImage() {
// if (!upscale.imageData) { if (fileList.value.length === 0) {
// return; return;
// } }
// const base64 = await blobToBase64(upscale.imageData); const base64 = await fileToBase64(fileList.value?.[0].originFileObj);
// const data: uploadImageRequest = { const data: any = {
// image: base64, origin_file_obj: base64,
// access_token: route.query.token as string, name: fileList.value?.[0].name,
// user_id: route.query.user_id as string, size: fileList.value?.[0].size,
// }; access_token: route.query.token as string,
// const res: any = await uploadImage(data); user_id: route.query.user_id as string,
// if (res && res && res.code === 200) { type: fileList.value?.[0].type,
// message.success(t('upload.uploadSuccess')); };
// } else { const res: any = await sharePhoneUploadApi(data);
// message.error(res.message); if (res && res && res.code === 200) {
// } message.success(t('upload.uploadSuccess'));
// } fileList.value = [];
} else {
message.error(res.message);
}
}
/** /**
* 上传文件前置 * 上传文件前置
@@ -117,6 +123,12 @@ async function beforeUpload(file: any) {
}; };
return true; return true;
} }
function customRequest() {
return;
}
/** /**
* 转换文件为 URL * 转换文件为 URL
* @param file * @param file
@@ -127,13 +139,13 @@ function convertFileToUrl(file: any) {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.upscale-upload-content { .share-upload-content {
width: 90%; width: 90%;
height: 30vh; height: 40vh;
padding: 15px; padding: 15px;
margin: 0 auto; margin: 0 auto;
.upscale-upload-content-main { .share-upload-content-main {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
@@ -143,33 +155,33 @@ function convertFileToUrl(file: any) {
overflow: scroll; overflow: scroll;
.upscale-upload-text { .share-upload-text {
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
} }
.upscale-upload-btn { .share-upload-btn {
width: 60%; width: 60%;
} }
.upscale-upload-tip { .share-upload-tip {
font-size: 12px; font-size: 12px;
color: rgba(126, 126, 135, 0.99); color: rgba(126, 126, 135, 0.99);
} }
} }
.upscale-divider-title { .share-divider-title {
font-size: 13px; font-size: 13px;
color: rgba(126, 126, 135, 0.99); color: rgba(126, 126, 135, 0.99);
} }
.upscale-upload-image { .share-upload-image {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.upscale-upload-btn { .share-upload-btn {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@@ -63,7 +63,6 @@ import remove from '@/assets/svgs/remove.svg';
import empty from '@/assets/svgs/empty.svg'; import empty from '@/assets/svgs/empty.svg';
import {blobToBase64} from "@/utils/imageUtils/blobToBase64.ts"; import {blobToBase64} from "@/utils/imageUtils/blobToBase64.ts";
import {uploadImage} from "src/api/phone"; import {uploadImage} from "src/api/phone";
import {uploadImageRequest} from "@/types/upscale";
import {message} from "ant-design-vue"; import {message} from "ant-design-vue";
import {useI18n} from "vue-i18n"; import {useI18n} from "vue-i18n";
@@ -78,7 +77,7 @@ async function sendImage() {
return; return;
} }
const base64 = await blobToBase64(upscale.imageData); const base64 = await blobToBase64(upscale.imageData);
const data: uploadImageRequest = { const data: any = {
image: base64, image: base64,
access_token: route.query.token as string, access_token: route.query.token as string,
user_id: route.query.user_id as string, user_id: route.query.user_id as string,

View File

@@ -12,7 +12,7 @@
style="background: linear-gradient(102.74deg, rgb(66, 230, 171) -7.03%, rgb(103, 235, 187) 97.7%);"> style="background: linear-gradient(102.74deg, rgb(66, 230, 171) -7.03%, rgb(103, 235, 187) 97.7%);">
<div class="image-share-left-item-content"> <div class="image-share-left-item-content">
<span style="font-weight: bolder;font-size: 2.3vh">浏览次数</span> <span style="font-weight: bolder;font-size: 2.3vh">浏览次数</span>
<span style="font-weight: bolder;font-size: 5vh">{{ overviewData?overviewData.visit_count:0 }}</span> <span style="font-weight: bolder;font-size: 5vh">{{ overviewData ? overviewData.visit_count : 0 }}</span>
<p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日浏览 <p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日浏览
<span <span
style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{ style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{
@@ -26,11 +26,11 @@
style="background: linear-gradient(101.63deg, rgb(82, 138, 250) -12.83%, rgb(122, 167, 255) 100%);"> style="background: linear-gradient(101.63deg, rgb(82, 138, 250) -12.83%, rgb(122, 167, 255) 100%);">
<div class="image-share-left-item-content"> <div class="image-share-left-item-content">
<span style="font-weight: bolder;font-size: 2.3vh">浏览人数</span> <span style="font-weight: bolder;font-size: 2.3vh">浏览人数</span>
<span style="font-weight: bolder;font-size: 5vh">{{ overviewData?overviewData.viewer_count:0 }}</span> <span style="font-weight: bolder;font-size: 5vh">{{ overviewData ? overviewData.viewer_count : 0 }}</span>
<p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日浏览人数 <p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日浏览人数
<span <span
style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{ style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{
overviewData?overviewData.viewer_count_today:0 overviewData ? overviewData.viewer_count_today : 0
}}</span> }}</span>
</p> </p>
</div> </div>
@@ -40,7 +40,9 @@
style="background: linear-gradient(102.99deg, rgb(126, 92, 255) 3.18%, rgb(162, 139, 255) 102.52%);"> style="background: linear-gradient(102.99deg, rgb(126, 92, 255) 3.18%, rgb(162, 139, 255) 102.52%);">
<div class="image-share-left-item-content"> <div class="image-share-left-item-content">
<span style="font-weight: bolder;font-size: 2.3vh">发布次数</span> <span style="font-weight: bolder;font-size: 2.3vh">发布次数</span>
<span style="font-weight: bolder;font-size: 5vh">{{ overviewData?overviewData.publish_count:0 }}</span> <span style="font-weight: bolder;font-size: 5vh">{{
overviewData ? overviewData.publish_count : 0
}}</span>
<p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日发布 <p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日发布
<span <span
style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{ style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{
@@ -109,6 +111,8 @@
title="确定删除该快传记录?" title="确定删除该快传记录?"
ok-text="确定" ok-text="确定"
cancel-text="取消" cancel-text="取消"
@confirm="async () => await deleteShareRecord(record.id, record.invite_code, record.album_id)"
@cancel="() => {}"
> >
<AButton type="text" size="small" danger> <AButton type="text" size="small" danger>
<DeleteOutlined/> <DeleteOutlined/>
@@ -131,7 +135,7 @@
import {Dayjs} from 'dayjs'; import {Dayjs} from 'dayjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import ShareUpload from "@/views/Share/ImageShare/ShareUpload.vue"; import ShareUpload from "@/views/Share/ImageShare/ShareUpload.vue";
import {queryShareOverviewApi, queryShareRecordListApi} from "@/api/share"; import {deleteShareRecordApi, queryShareOverviewApi, queryShareRecordListApi} from "@/api/share";
import {message} from "ant-design-vue"; import {message} from "ant-design-vue";
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
@@ -278,6 +282,24 @@ async function getShareOverview() {
overviewDataLoading.value = false; overviewDataLoading.value = false;
} }
/**
* 删除分享记录
* @param id
* @param invite_code
* @param album_id
*/
async function deleteShareRecord(id: number, invite_code: string, album_id: number) {
const res: any = await deleteShareRecordApi(id, invite_code, album_id);
if (res && res.code === 200) {
message.success('删除成功');
const endDate = dayjs().format('YYYY-MM-DD'); // 当前日期
const startDate = dayjs().subtract(30, 'day').format('YYYY-MM-DD'); // 30 天前的日期
await getShareRecords([startDate, endDate]);
} else {
message.error('删除失败');
}
}
onMounted(async () => { onMounted(async () => {
const endDate = dayjs().format('YYYY-MM-DD'); // 当前日期 const endDate = dayjs().format('YYYY-MM-DD'); // 当前日期
const startDate = dayjs().subtract(30, 'day').format('YYYY-MM-DD'); // 30 天前的日期 const startDate = dayjs().subtract(30, 'day').format('YYYY-MM-DD'); // 30 天前的日期

View File

@@ -33,10 +33,10 @@
<template #content> <template #content>
<AQrcode :bordered="false" color="rgba(126, 126, 135, 0.48)" <AQrcode :bordered="false" color="rgba(126, 126, 135, 0.48)"
:size="qrcodeSize" :size="qrcodeSize"
:value="`git.landaiqing.cneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjI1MTEyMjE3MzQyMDIxIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTczOTg3ODIyOCwibmJmIjoxNzM5ODcxMDI4LCJpYXQiOjE3Mzk4NzEwMjh9.EUiZsVjhGqHx1V5o90S3W5li6nIqucxy9eEY9LWgqXY`" :value="generateQrCodeUrl()"
:icon="phone" :icon="phone"
:iconSize="iconSize" :iconSize="iconSize"
:status="`active`" :status="qrStatus"
/> />
</template> </template>
<AButton type="default" size="large" shape="round" style="width: 70%" <AButton type="default" size="large" shape="round" style="width: 70%"
@@ -430,6 +430,11 @@ const wsOptions = {
protocols: [user.token.accessToken], protocols: [user.token.accessToken],
}; };
function generateQrCodeUrl(): string {
console.log(import.meta.env.VITE_APP_WEB_URL + "/main/share/phone/app?user_id=" + user.user.uid + "&token=" + user.token.accessToken);
return import.meta.env.VITE_APP_WEB_URL + "/main/share/phone/app?user_id=" + user.user.uid + "&token=" + user.token.accessToken;
}
/** /**
* 初始化 WebSocket * 初始化 WebSocket
*/ */
@@ -438,11 +443,56 @@ function initWebSocket() {
websocket.on("message", async (res: any) => { websocket.on("message", async (res: any) => {
if (res && res.code === 200) { if (res && res.code === 200) {
const {data} = res; const {data} = res;
console.log(data); const file: File = base64ToFile(data.origin_file_obj, data.name, data.type);
fileList.value.push({
originFileObj: file,
name: data.name,
type: data.type,
size: data.size,
});
} }
}); });
} }
/**
* 将 Base64 字符串转换为 File 对象
* @param base64
* @param filename
* @param mimeType
*/
function base64ToFile(base64, filename, mimeType): File {
// 检查并移除Base64前缀如果有
const base64Data = base64.split(',')[1] || base64;
// 将Base64字符串解码为二进制字符串
const binaryString = atob(base64Data);
// 将二进制字符串转换为ArrayBuffer
const arrayBuffer = new ArrayBuffer(binaryString.length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < binaryString.length; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
// 创建Blob对象
const blob = new Blob([arrayBuffer], {type: mimeType});
// 创建File对象
return new File([blob], filename, {type: mimeType});
}
const qrStatus = ref<string>('loading');
watch(
() => websocket.readyState,
(newStatus) => {
if (newStatus === WebSocket.OPEN) {
qrStatus.value = 'active';
} else {
qrStatus.value = 'expired';
}
}
);
onMounted(() => { onMounted(() => {
window.addEventListener("resize", updateQrcodeSize); window.addEventListener("resize", updateQrcodeSize);
}); });

View File

@@ -82,7 +82,7 @@ async function startTask() {
if (upscale.input === null) return; if (upscale.input === null) return;
upscale.isProcessing = true; upscale.isProcessing = true;
const start = Date.now(); const start = Date.now();
const worker = new Worker(new URL("@/workers/phone/phone.worker.ts", import.meta.url), { const worker = new Worker(new URL("@/workers/upscale/upscale.worker.ts", import.meta.url), {
type: "module", type: "module",
}); });
worker.onmessage = (e: MessageEvent<any>) => { worker.onmessage = (e: MessageEvent<any>) => {