✨ complete mobile image upload
This commit is contained in:
@@ -3,9 +3,8 @@ VITE_NODE_ENV='development'
|
||||
|
||||
# 开发环境
|
||||
VITE_APP_BASE_API='/sys'
|
||||
|
||||
# 页面 title 前缀
|
||||
VITE_APP_TITLE=开发环境
|
||||
# 网站域名
|
||||
VITE_APP_WEB_URL='http://localhost:5173'
|
||||
|
||||
# 网络请求公用地址
|
||||
VITE_API_BASE_URL='http://127.0.0.1:80'
|
||||
@@ -20,4 +19,6 @@ VITE_QR_SOCKET_URL='ws://127.0.0.1:80/api/ws/qrcode'
|
||||
|
||||
VITE_MESSAGE_SOCKET_URL='ws://127.0.0.1:80/api/ws/message'
|
||||
|
||||
VITE_FILE_SOCKET_URL='ws://127.0.0.1:80/api/ws/file'
|
||||
|
||||
VITE_FINGERPRINT_KEY='idm0jdoau38lwourb4pbjk4dxkat0kcx'
|
||||
|
@@ -3,8 +3,8 @@ VITE_NODE_ENV='production'
|
||||
# 生产环境
|
||||
VITE_APP_BASE_API='/sys'
|
||||
|
||||
# 页面 title 前缀
|
||||
VITE_APP_TITLE=生产环境
|
||||
# 网站域名
|
||||
VITE_APP_WEB_URL='http://localhost:5173'
|
||||
|
||||
# 网络请求公用地址
|
||||
VITE_API_BASE_URL='https://landaiqing.cn'
|
||||
@@ -18,5 +18,8 @@ VITE_APP_TOKEN_KEY='Bearer'
|
||||
VITE_QR_SOCKET_URL='wss://landaiqing.cn/api/ws/qr_ws'
|
||||
|
||||
VITE_MESSAGE_SOCKET_URL='wss://landaiqing.cn/api/ws/message_ws'
|
||||
|
||||
VITE_FILE_SOCKET_URL='ws://127.0.0.1:80/api/ws/file'
|
||||
|
||||
# 签名密钥
|
||||
VITE_FINGERPRINT_KEY='idm0jdoau38lwourb4pbjk4dxkat0kcx'
|
||||
|
15
components.d.ts
vendored
15
components.d.ts
vendored
@@ -27,21 +27,30 @@ declare module 'vue' {
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
Alert: typeof import('./src/components/MyUI/Alert/Alert.vue')['default']
|
||||
AList: typeof import('ant-design-vue/es')['List']
|
||||
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||
AllPhoto: typeof import('./src/views/Photograph/AllPhoto/AllPhoto.vue')['default']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AMenuItemGroup: typeof import('ant-design-vue/es')['MenuItemGroup']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
AnimatedNature: typeof import('./src/components/AnimatedNature/AnimatedNature.vue')['default']
|
||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||
APopover: typeof import('ant-design-vue/es')['Popover']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
AQrcode: typeof import('ant-design-vue/es')['QRCode']
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
AUpload: typeof import('ant-design-vue/es')['Upload']
|
||||
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
|
||||
Avatar: typeof import('./src/components/MyUI/Avatar/Avatar.vue')['default']
|
||||
BackgroundAnimation: typeof import('./src/components/BackgroundAnimation/BackgroundAnimation.vue')['default']
|
||||
@@ -55,6 +64,7 @@ declare module 'vue' {
|
||||
Carousel: typeof import('./src/components/MyUI/Carousel/Carousel.vue')['default']
|
||||
Cascader: typeof import('./src/components/MyUI/Cascader/Cascader.vue')['default']
|
||||
Checkbox: typeof import('./src/components/MyUI/Checkbox/Checkbox.vue')['default']
|
||||
CloseCircleOutlined: typeof import('@ant-design/icons-vue')['CloseCircleOutlined']
|
||||
Clouds: typeof import('./src/components/Clouds/Clouds.vue')['default']
|
||||
Col: typeof import('./src/components/MyUI/Grid/Col.vue')['default']
|
||||
Collapse: typeof import('./src/components/MyUI/Collapse/Collapse.vue')['default']
|
||||
@@ -71,6 +81,7 @@ declare module 'vue' {
|
||||
DynamicTitle: typeof import('./src/components/DynamicTitle/DynamicTitle.vue')['default']
|
||||
Ellipsis: typeof import('./src/components/MyUI/Ellipsis/Ellipsis.vue')['default']
|
||||
Empty: typeof import('./src/components/MyUI/Empty/Empty.vue')['default']
|
||||
EyeOutlined: typeof import('@ant-design/icons-vue')['EyeOutlined']
|
||||
Flex: typeof import('./src/components/MyUI/Flex/Flex.vue')['default']
|
||||
FloatButton: typeof import('./src/components/MyUI/FloatButton/FloatButton.vue')['default']
|
||||
Folder: typeof import('./src/components/Folder/Folder.vue')['default']
|
||||
@@ -100,6 +111,8 @@ declare module 'vue' {
|
||||
ParameterSetting: typeof import('./src/views/Upscale/ParameterSetting.vue')['default']
|
||||
PeopleAlbum: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbum.vue')['default']
|
||||
Phoalbum: typeof import('./src/views/Album/Phoalbum/Phoalbum.vue')['default']
|
||||
PhoneUpload: typeof import('./src/views/PhoneUpload/PhoneUpload.vue')['default']
|
||||
PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined']
|
||||
Popconfirm: typeof import('./src/components/MyUI/Popconfirm/Popconfirm.vue')['default']
|
||||
Popover: typeof import('./src/components/MyUI/Popover/Popover.vue')['default']
|
||||
Progress: typeof import('./src/components/MyUI/Progress/Progress.vue')['default']
|
||||
@@ -121,6 +134,7 @@ declare module 'vue' {
|
||||
Scrollbar: typeof import('./src/components/MyUI/Scrollbar/Scrollbar.vue')['default']
|
||||
Segmented: typeof import('./src/components/MyUI/Segmented/Segmented.vue')['default']
|
||||
Select: typeof import('./src/components/MyUI/Select/Select.vue')['default']
|
||||
SendOutlined: typeof import('@ant-design/icons-vue')['SendOutlined']
|
||||
Skeleton: typeof import('./src/components/MyUI/Skeleton/Skeleton.vue')['default']
|
||||
Slider: typeof import('./src/components/MyUI/Slider/Slider.vue')['default']
|
||||
Space: typeof import('./src/components/MyUI/Space/Space.vue')['default']
|
||||
@@ -147,6 +161,7 @@ declare module 'vue' {
|
||||
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
|
||||
Video: typeof import('./src/components/MyUI/Video/Video.vue')['default']
|
||||
VueCompareImage: typeof import('./src/components/VueCompareImage/VueCompareImage.vue')['default']
|
||||
WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined']
|
||||
Waterfall: typeof import('./src/components/MyUI/Waterfall/Waterfall.vue')['default']
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"docker-build": "docker build -t schisandra/schisandra-cloud-album-front ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/adapter-axios": "^2.0.11",
|
||||
"@alova/adapter-axios": "^2.0.12",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@tensorflow/tfjs": "^4.22.0",
|
||||
"@tensorflow/tfjs-backend-webgl": "^4.22.0",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@vuepic/vue-datepicker": "^10.0.0",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"@vueuse/integrations": "^12.0.0",
|
||||
"alova": "^3.2.6",
|
||||
"alova": "^3.2.7",
|
||||
"animejs": "^3.2.2",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"autofit.js": "^3.2.2",
|
||||
@@ -64,7 +64,7 @@
|
||||
"globals": "^15.13.0",
|
||||
"sass": "^1.83.0",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.18.0",
|
||||
"typescript-eslint": "^8.18.1",
|
||||
"unplugin-vue-components": "^0.28.0",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-bundle-obfuscator": "1.4.0",
|
||||
|
15
src/api/upscale/index.ts
Normal file
15
src/api/upscale/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {service} from "@/utils/alova/service.ts";
|
||||
import {uploadImageRequest} from "@/types/upscale";
|
||||
|
||||
export const uploadImage = (data: uploadImageRequest) => {
|
||||
return service.Post('/api/auth/upscale/upload', {
|
||||
image: data.image,
|
||||
access_token: data.access_token,
|
||||
user_id: data.user_id,
|
||||
}, {
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: true
|
||||
}
|
||||
});
|
||||
};
|
1
src/assets/svgs/deleted.svg
Normal file
1
src/assets/svgs/deleted.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1734406724014" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9426" width="200" height="200"><path d="M714.9568 903.9872H310.8864c-63.2832 0-114.5856-51.3024-114.5856-114.5856V233.0624h633.2416v556.3392c0 63.2832-51.3024 114.5856-114.5856 114.5856z" fill="#FF5F5F" p-id="9427"></path><path d="M905.6768 217.7024H726.528V170.496c0-42.9568-34.9696-77.9264-77.9264-77.9264h-271.36c-42.9568 0-77.9264 34.9696-77.9264 77.9264v47.2064H120.1664c-8.4992 0-15.36 6.8608-15.36 15.36s6.8608 15.36 15.36 15.36h60.7744v540.9792c0 71.6288 58.3168 129.9456 129.9456 129.9456h404.0704c71.6288 0 129.9456-58.3168 129.9456-129.9456V248.4224h60.7744c8.4992 0 15.36-6.8608 15.36-15.36s-6.8608-15.36-15.36-15.36zM330.0352 170.496c0-26.0096 21.1968-47.2064 47.2064-47.2064h271.4112c26.0096 0 47.2064 21.1968 47.2064 47.2064v47.2064H330.0352V170.496z m484.1472 618.9056c0 54.7328-44.4928 99.2256-99.2256 99.2256H310.8864c-54.7328 0-99.2256-44.4928-99.2256-99.2256V248.4224h602.5216v540.9792z" fill="#424242" p-id="9428"></path><path d="M379.3408 734.3104c-8.4992 0-15.36-6.8608-15.36-15.36V402.8928c0-8.4992 6.8608-15.36 15.36-15.36s15.36 6.8608 15.36 15.36v316.0576c0 8.448-6.912 15.36-15.36 15.36zM524.0832 734.3104c-8.4992 0-15.36-6.8608-15.36-15.36V402.8928c0-8.4992 6.8608-15.36 15.36-15.36s15.36 6.8608 15.36 15.36v316.0576c0 8.448-6.912 15.36-15.36 15.36zM670.5152 734.3104c-8.4992 0-15.36-6.8608-15.36-15.36V402.8928c0-8.4992 6.8608-15.36 15.36-15.36s15.36 6.8608 15.36 15.36v316.0576c0 8.448-6.912 15.36-15.36 15.36z" fill="#FFFFFF" p-id="9429"></path></svg>
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,11 +1 @@
|
||||
<svg t="1733761095875" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="50680" width="200" height="200">
|
||||
<path d="M471.87259 871.430008a41.629505 41.629505 0 0 0 41.629506 41.629506 40.98575 40.98575 0 0 0 38.625314-41.629506 40.55658 40.55658 0 0 0-39.912825-40.98575h-1.072925a40.98575 40.98575 0 0 0-39.26907 40.98575z"
|
||||
fill="#B8BDCC" p-id="50681"></path>
|
||||
<path d="M738.601844 55.792121a77.036044 77.036044 0 0 1 77.036044 77.036043v758.343672a77.036044 77.036044 0 0 1-77.036044 77.036043H285.398156a77.036044 77.036044 0 0 1-77.036044-77.036043V132.828164A77.036044 77.036044 0 0 1 285.398156 55.792121h453.203688m0-55.792121H285.398156A132.828164 132.828164 0 0 0 152.569992 132.828164v758.343672a132.828164 132.828164 0 0 0 132.828164 132.828164h453.203688a132.828164 132.828164 0 0 0 132.828164-132.828164V132.828164A132.828164 132.828164 0 0 0 738.601844 0z"
|
||||
fill="#B8BDCC" p-id="50682"></path>
|
||||
<path d="M713.49539 793.964795H310.50461a39.483655 39.483655 0 0 1-41.629505-36.694049v-519.295893a39.26907 39.26907 0 0 1 41.629505-36.479463h402.99078a39.26907 39.26907 0 0 1 41.629505 36.479463v518.437553A39.483655 39.483655 0 0 1 713.49539 793.964795z"
|
||||
fill="#E3E5EB" p-id="50683"></path>
|
||||
<path d="M425.736798 103.430008m25.964795 0l120.596814 0q25.964795 0 25.964795 25.964795l0 0q0 25.964795-25.964795 25.964795l-120.596814 0q-25.964795 0-25.964795-25.964795l0 0q0-25.964795 25.964795-25.964795Z"
|
||||
fill="#B8BDCC" p-id="50684"></path>
|
||||
</svg>
|
||||
<svg t="1734409010371" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16420" width="200" height="200"><path d="M820.409449 797.228346q0 25.19685-10.07874 46.866142t-27.716535 38.299213-41.322835 26.204724-50.897638 9.574803l-357.795276 0q-27.212598 0-50.897638-9.574803t-41.322835-26.204724-27.716535-38.299213-10.07874-46.866142l0-675.275591q0-25.19685 10.07874-47.370079t27.716535-38.80315 41.322835-26.204724 50.897638-9.574803l357.795276 0q27.212598 0 50.897638 9.574803t41.322835 26.204724 27.716535 38.80315 10.07874 47.370079l0 675.275591zM738.771654 170.330709l-455.559055 0 0 577.511811 455.559055 0 0-577.511811zM510.992126 776.062992q-21.165354 0-36.787402 15.11811t-15.622047 37.291339q0 21.165354 15.622047 36.787402t36.787402 15.622047q22.173228 0 37.291339-15.622047t15.11811-36.787402q0-22.173228-15.11811-37.291339t-37.291339-15.11811zM591.622047 84.661417q0-8.062992-5.03937-12.598425t-11.086614-4.535433l-128 0q-5.03937 0-10.582677 4.535433t-5.543307 12.598425 5.03937 12.598425 11.086614 4.535433l128 0q6.047244 0 11.086614-4.535433t5.03937-12.598425z" p-id="16421" fill="#707070"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div class="sidebar">
|
||||
<AMenu
|
||||
:selectedKeys="[router.currentRoute.value.path.split('/').slice(-2).join('/')]"
|
||||
:selectedKeys="[route.path.split('/').slice(-2).join('/')]"
|
||||
:selectable="true"
|
||||
:multiple="false"
|
||||
mode="vertical"
|
||||
@@ -96,8 +96,11 @@ import recyclingbin from '@/assets/svgs/recyclingbin.svg';
|
||||
import Folder from "@/components/Folder/Folder.vue";
|
||||
import ai from '@/assets/svgs/ai.svg';
|
||||
import share from '@/assets/svgs/share.svg';
|
||||
|
||||
const {t} = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -131,5 +131,9 @@ export default {
|
||||
recyclingBin: 'Recycling Bin',
|
||||
upscale: 'Image Inpainting',
|
||||
share: 'Quickly Share'
|
||||
},
|
||||
upload:{
|
||||
uploadSuccess: 'upload success!',
|
||||
uploadError: 'upload failed!',
|
||||
}
|
||||
};
|
||||
|
@@ -118,6 +118,10 @@ export default {
|
||||
recyclingBin: '回收站',
|
||||
upscale: '图像修复',
|
||||
share: '快传'
|
||||
},
|
||||
upload:{
|
||||
uploadSuccess: '上传成功!',
|
||||
uploadError: '上传失败!',
|
||||
}
|
||||
|
||||
};
|
||||
|
@@ -21,16 +21,16 @@ export default [
|
||||
name: 'upscale',
|
||||
component: () => import('@/views/Upscale/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
requiresAuth: true,
|
||||
title: '图像修复'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/photo/share',
|
||||
name: 'share',
|
||||
component: () => import('@/views/ImageShare/ImageShare.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
requiresAuth: true,
|
||||
title: '快传'
|
||||
}
|
||||
}
|
||||
|
11
src/router/modules/phone_upload.ts
Normal file
11
src/router/modules/phone_upload.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export default [
|
||||
{
|
||||
path: '/upscale/app',
|
||||
name: 'upscaleApp',
|
||||
component: () => import('@/views/PhoneUpload/PhoneUpload.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
title: '手机上传'
|
||||
}
|
||||
}
|
||||
];
|
@@ -7,12 +7,14 @@ import notFound from "./modules/not_found.ts";
|
||||
import landing from "./modules/landing.ts";
|
||||
import mainRouter from "./modules/main_router.ts";
|
||||
import i18n from "@/locales";
|
||||
import phone_upload from "@/router/modules/phone_upload.ts";
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
...login,
|
||||
...notFound,
|
||||
...landing,
|
||||
...mainRouter,
|
||||
...phone_upload,
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
redirect: '/404',
|
||||
|
@@ -54,13 +54,7 @@ export const useUpscaleStore = defineStore(
|
||||
fileData.value = urlData;
|
||||
await loadImg(image);
|
||||
uploading.value = false;
|
||||
|
||||
imageData.value = "";
|
||||
processedImg.value = "";
|
||||
isDone.value = false;
|
||||
msg.value = "";
|
||||
progressBar.value = 0;
|
||||
|
||||
await clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -68,10 +62,21 @@ export const useUpscaleStore = defineStore(
|
||||
* 自定义上传图片请求
|
||||
*/
|
||||
async function customUploadRequest(_file: any) {
|
||||
|
||||
imageData.value = fileData.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数据
|
||||
*/
|
||||
async function clear() {
|
||||
imageData.value = "";
|
||||
processedImg.value = "";
|
||||
isDone.value = false;
|
||||
msg.value = "";
|
||||
progressBar.value = 0;
|
||||
isProcessing.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载图片
|
||||
* @param img
|
||||
@@ -117,6 +122,7 @@ export const useUpscaleStore = defineStore(
|
||||
isProcessing,
|
||||
msg,
|
||||
progressBar,
|
||||
loadImg,
|
||||
beforeUpload,
|
||||
customUploadRequest,
|
||||
};
|
||||
|
@@ -10,6 +10,9 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
wsService: null as WebSocketService | null,
|
||||
});
|
||||
|
||||
const readyState = ref<number>(WebSocket.CLOSED);
|
||||
|
||||
|
||||
function initialize(options: {
|
||||
url: string;
|
||||
protocols?: string | string[];
|
||||
@@ -17,6 +20,7 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
}) {
|
||||
state.wsService = new WebSocketService(options);
|
||||
state.wsService?.open();
|
||||
readyState.value = WebSocket.OPEN;
|
||||
}
|
||||
|
||||
function sendMessage(data: any) {
|
||||
@@ -33,12 +37,9 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
|
||||
function close(isActiveClose: boolean) {
|
||||
state.wsService?.close(isActiveClose);
|
||||
readyState.value = WebSocket.CLOSED;
|
||||
}
|
||||
|
||||
// 新增的获取 WebSocket 状态的方法
|
||||
function getReadyState() {
|
||||
return state.wsService ? state.wsService.getReadyState() : WebSocket.CLOSED;
|
||||
}
|
||||
|
||||
return {
|
||||
initialize,
|
||||
@@ -46,7 +47,7 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
on,
|
||||
onEvent,
|
||||
close,
|
||||
getReadyState
|
||||
readyState,
|
||||
};
|
||||
}, {
|
||||
persistedState: {
|
||||
|
15
src/types/upscale.d.ts
vendored
15
src/types/upscale.d.ts
vendored
@@ -1,12 +1,5 @@
|
||||
export interface ImageData {
|
||||
model_type: string;
|
||||
model: string;
|
||||
factor: number;
|
||||
tile_size: number;
|
||||
backend: string;
|
||||
width: number;
|
||||
height: number;
|
||||
input: any;
|
||||
hasAlpha: boolean;
|
||||
min_lap: number;
|
||||
export interface uploadImageRequest {
|
||||
image: string;
|
||||
access_token: string;
|
||||
user_id: string;
|
||||
}
|
||||
|
@@ -6,8 +6,7 @@ export const localforageStorageAdapter = {
|
||||
await localforage.setItem(key, value);
|
||||
},
|
||||
async get(key: string) {
|
||||
const res: any = await localforage.getItem(key);
|
||||
return res ? JSON.parse(res) : null;
|
||||
return await localforage.getItem(key);
|
||||
},
|
||||
async remove(key: any) {
|
||||
await localforage.removeItem(key);
|
||||
|
22
src/utils/imageUtils/blobToBase64.ts
Normal file
22
src/utils/imageUtils/blobToBase64.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export async function blobToBase64(blobUrl: string): Promise<string> {
|
||||
try {
|
||||
const response = await fetch(blobUrl);
|
||||
const blob = await response.blob();
|
||||
const reader = new FileReader();
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
reader.onload = function () {
|
||||
// 直接使用 reader.result,包含 MIME 类型前缀
|
||||
const base64StringWithPrefix = reader.result!.toString();
|
||||
resolve(base64StringWithPrefix);
|
||||
};
|
||||
reader.onerror = function () {
|
||||
reject("File could not be read");
|
||||
};
|
||||
// 读取 Blob 文件到 Data URL 格式
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error("Error fetching blob from URL: " + error);
|
||||
}
|
||||
}
|
146
src/views/PhoneUpload/PhoneUpload.vue
Normal file
146
src/views/PhoneUpload/PhoneUpload.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div class="upscale-upload-content">
|
||||
<AUploadDragger
|
||||
name="image"
|
||||
accept="image/*"
|
||||
:multiple="false"
|
||||
:directory="false"
|
||||
:maxCount="1"
|
||||
:beforeUpload="upscale.beforeUpload"
|
||||
:custom-request="upscale.customUploadRequest"
|
||||
:disabled="upscale.uploading"
|
||||
:showUploadList="false">
|
||||
<div class="upscale-upload-content-main">
|
||||
<ABadge :offset="[-10, 10]">
|
||||
<template #count>
|
||||
<AAvatar :size="25" :src="sueccess" v-if="upscale.imageData"/>
|
||||
<AAvatar :size="25" :src="warn" v-if="!upscale.imageData"/>
|
||||
</template>
|
||||
<AAvatar shape="square" :size="70" :src="file"/>
|
||||
</ABadge>
|
||||
<span class="upscale-upload-text">
|
||||
点击上传图片
|
||||
</span>
|
||||
</div>
|
||||
</AUploadDragger>
|
||||
<ADivider orientation="center" :plain="true">
|
||||
<span class="upscale-divider-title">图片预览</span>
|
||||
</ADivider>
|
||||
<div class="upscale-upload-image" v-if="upscale.imageData">
|
||||
<ABadge>
|
||||
<template #count>
|
||||
<AButton type="text" size="small" class="upscale-file-btn" @click="upscale.imageData = ''">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="25" :src="remove"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</template>
|
||||
<AAvatar shape="square" :size="100">
|
||||
<template #icon>
|
||||
<AImage :src="upscale.imageData" width="100%" height="100%"/>
|
||||
</template>
|
||||
</AAvatar>
|
||||
</ABadge>
|
||||
</div>
|
||||
<AEmpty v-else :image="empty" :description="null"/>
|
||||
<ADivider/>
|
||||
<div>
|
||||
<AButton type="primary" size="large" :disabled="!upscale.imageData" :loading="upscale.uploading"
|
||||
@click="sendImage()"
|
||||
class="upscale-upload-btn">
|
||||
发送图片
|
||||
</AButton>
|
||||
</div>
|
||||
<ADivider/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import file from "@/assets/svgs/file.svg";
|
||||
import useStore from "@/store";
|
||||
import sueccess from '@/assets/svgs/success.svg';
|
||||
import warn from '@/assets/svgs/warn.svg';
|
||||
import remove from '@/assets/svgs/remove.svg';
|
||||
import empty from '@/assets/svgs/empty.svg';
|
||||
import {blobToBase64} from "@/utils/imageUtils/blobToBase64.ts";
|
||||
import {uploadImage} from "@/api/upscale";
|
||||
import {uploadImageRequest} from "@/types/upscale";
|
||||
import {message} from "ant-design-vue";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const upscale = useStore().upscale;
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
async function sendImage() {
|
||||
if (!upscale.imageData) {
|
||||
return;
|
||||
}
|
||||
const base64 = await blobToBase64(upscale.imageData);
|
||||
const data: uploadImageRequest = {
|
||||
image: base64,
|
||||
access_token: route.query.token as string,
|
||||
user_id: route.query.user_id as string,
|
||||
};
|
||||
const res: any = await uploadImage(data);
|
||||
if (res && res.code === 200) {
|
||||
message.success(t('upload.uploadSuccess'));
|
||||
} else {
|
||||
message.error(res.message);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upscale-upload-content {
|
||||
width: 90%;
|
||||
height: 40vh;
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
|
||||
.upscale-upload-content-main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
overflow: scroll;
|
||||
|
||||
|
||||
.upscale-upload-text {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.upscale-upload-btn {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.upscale-upload-tip {
|
||||
font-size: 12px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
}
|
||||
|
||||
.upscale-divider-title {
|
||||
font-size: 13px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
|
||||
.upscale-upload-image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.upscale-upload-btn {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
|
||||
<CommentReply/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
@@ -11,20 +11,24 @@
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
>
|
||||
<div v-if="store.isProcessing" class="canvas-progressbar">
|
||||
<!-- 进度条 -->
|
||||
<div class="canvas-progressbar">
|
||||
<span class="canvas-progressbar-text">
|
||||
{{ store.msg }}
|
||||
</span>
|
||||
<AProgress
|
||||
v-if="store.isProcessing"
|
||||
:stroke-color="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
}"
|
||||
'100%': '#87d068',}"
|
||||
:percent="store.progressBar"
|
||||
:showInfo="false"
|
||||
status="active"
|
||||
/>
|
||||
</div>
|
||||
<canvas ref="canvas" v-if="store.imageData || store.processedImg"></canvas>
|
||||
<!-- 图片 -->
|
||||
<canvas ref="canvas"></canvas>
|
||||
<!-- 拖动条 -->
|
||||
<div
|
||||
class="dragLine"
|
||||
v-if="store.isDone"
|
||||
@@ -40,20 +44,58 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="canvas-qr" v-if="!store.isDone && !store.imageData">
|
||||
<AQrcode :bordered="false" color="rgba(126, 126, 135, 0.48)" :size="200"
|
||||
:value="'https://git.landaiqing.cn'"
|
||||
<!-- 二维码 -->
|
||||
<div class="canvas-qr">
|
||||
<AQrcode :bordered="false" color="rgba(126, 126, 135, 0.48)"
|
||||
:size="qrcodeSize"
|
||||
:value="generateQrCodeUrl()"
|
||||
:icon="phone"
|
||||
:iconSize="40"
|
||||
:iconSize="iconSize"
|
||||
/>
|
||||
<span class="canvas-qr-text">手机扫码上传</span>
|
||||
</div>
|
||||
|
||||
<!-- 菜单 -->
|
||||
<div class="floating-menu" @mousedown.stop v-if="store.isDone && store.processedImg">
|
||||
<AFlex :vertical="false" align="center" justify="space-between" :gap="12">
|
||||
<ATooltip placement="top" title="下载图片">
|
||||
<AButton type="text" size="large" @click="downloadImage" class="menu-btn">
|
||||
<template #icon>
|
||||
<AAvatar :src="download" class="menu-icon"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</ATooltip>
|
||||
<ATooltip placement="top" title="分享图片">
|
||||
<AButton type="text" size="large" class="menu-btn">
|
||||
<template #icon>
|
||||
<AAvatar :src="share" :size="28" class="menu-icon"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</ATooltip>
|
||||
<ATooltip placement="top" title="保存图片">
|
||||
<AButton type="text" size="large" class="menu-btn">
|
||||
<template #icon>
|
||||
<AAvatar :src="save" :size="30" class="menu-icon"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</ATooltip>
|
||||
<ATooltip placement="top" title="删除图片">
|
||||
<AButton type="text" size="large" danger class="menu-btn" @click="deletedImage">
|
||||
<template #icon>
|
||||
<AAvatar :src="deleted" :size="28" class="menu-icon"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</ATooltip>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import useStore from "@/store";
|
||||
import phone from '@/assets/svgs/qr-phone.svg';
|
||||
import download from '@/assets/svgs/download.svg';
|
||||
import share from '@/assets/svgs/share.svg';
|
||||
import save from '@/assets/svgs/save.svg';
|
||||
import deleted from '@/assets/svgs/deleted.svg';
|
||||
|
||||
const canvasContainer = ref<HTMLDivElement | null>(null);
|
||||
const dragging = ref<boolean>(false);
|
||||
@@ -76,9 +118,68 @@ const touchStartDistance = ref(0);
|
||||
const imgScaleStart = ref(1);
|
||||
|
||||
const store = useStore().upscale;
|
||||
const user = useStore().user;
|
||||
const img = ref<HTMLImageElement>(new Image());
|
||||
const processedImg = ref<HTMLImageElement>(new Image());
|
||||
|
||||
const qrcodeSize = ref<number>(250);
|
||||
const iconSize = ref<number>(30);
|
||||
/**
|
||||
* 更新二维码大小
|
||||
*/
|
||||
const updateQrcodeSize = () => {
|
||||
if (canvasContainer.value) {
|
||||
// 设置 QRCode 大小
|
||||
const containerWidth = canvasContainer.value.clientWidth;
|
||||
qrcodeSize.value = containerWidth * 0.3; // 设置 QRCode 为父盒子宽度的80%
|
||||
iconSize.value = Math.min(containerWidth * 0.1, 40); // 设置 icon 大小为父盒子宽度的10%
|
||||
}
|
||||
};
|
||||
|
||||
function generateQrCodeUrl(): string {
|
||||
return import.meta.env.VITE_APP_WEB_URL + "/upscale/app?user_id=" + user.user.uid + "&token=" + user.user.access_token;
|
||||
}
|
||||
|
||||
console.log(generateQrCodeUrl());
|
||||
|
||||
/**
|
||||
* 下载图片
|
||||
*/
|
||||
function downloadImage() {
|
||||
if (!store.processedImg) return;
|
||||
const a = document.createElement("a");
|
||||
a.href = store.processedImg;
|
||||
if (store.hasAlpha) a.download = "output.png";
|
||||
else a.download = "output.jpg";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除图片
|
||||
*/
|
||||
function deletedImage() {
|
||||
if (store.processedImg && store.imageData) {
|
||||
store.imageData = '';
|
||||
store.processedImg = '';
|
||||
}
|
||||
store.isDone = false;
|
||||
store.isProcessing = false;
|
||||
store.progressBar = 0;
|
||||
store.msg = "";
|
||||
draggingLine.value = false;
|
||||
dragging.value = false;
|
||||
imgX.value = 0;
|
||||
imgY.value = 0;
|
||||
imgScale.value = 1;
|
||||
imgInitScale.value = 1;
|
||||
img.value = new Image();
|
||||
processedImg.value = new Image();
|
||||
initCanvasSize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开始拖动
|
||||
* @param event
|
||||
@@ -409,6 +510,7 @@ function updateCanvasSize() {
|
||||
*/
|
||||
function handleResize() {
|
||||
updateCanvasSize();
|
||||
updateQrcodeSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,6 +566,7 @@ onBeforeUnmount(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bg {
|
||||
@@ -536,13 +639,19 @@ onBeforeUnmount(() => {
|
||||
canvas {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.canvas-qr {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.canvas-qr-text {
|
||||
@@ -563,6 +672,7 @@ canvas {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dragLine:hover {
|
||||
@@ -588,17 +698,11 @@ canvas {
|
||||
.canvas-progressbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
//left: 50%;
|
||||
//transform: translate(-50%, -50%);
|
||||
width: 300px;
|
||||
//height: 100px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
//background-color: rgba(255, 255, 255, 0.5);
|
||||
//padding: 10px;
|
||||
}
|
||||
|
||||
.canvas-progressbar-text {
|
||||
@@ -606,4 +710,41 @@ canvas {
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.floating-menu {
|
||||
position: absolute;
|
||||
background-color: rgb(255, 255, 255);
|
||||
opacity: 0.8;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
padding: 10px;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.5s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-btn:hover {
|
||||
transform: scale(1.2);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-icon:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
|
@@ -52,6 +52,7 @@
|
||||
</div>
|
||||
<ADivider></ADivider>
|
||||
<AButton style="width: 100%;" size="large" shape="default" type="default" :loading="upscale.isProcessing"
|
||||
:disabled="!upscale.input"
|
||||
@click="startTask">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="25" :src="run"/>
|
||||
@@ -142,7 +143,6 @@ const imgCanvas = document.createElement("canvas");
|
||||
* WebWorker 处理图片
|
||||
*/
|
||||
async function startTask() {
|
||||
console.log(upscale.input);
|
||||
if (upscale.input === null) return;
|
||||
upscale.isProcessing = true;
|
||||
const start = Date.now();
|
||||
@@ -226,7 +226,7 @@ async function startTask() {
|
||||
outputData.value = null;
|
||||
imgCtx.putImageData(outImg, 0, 0);
|
||||
let type = "image/jpeg";
|
||||
const quality = 0.92;
|
||||
const quality = 1.0;
|
||||
if (upscale.hasAlpha) type = "image/png";
|
||||
|
||||
imgCanvas.toBlob(
|
||||
|
@@ -39,7 +39,7 @@ import warn from '@/assets/svgs/warn.svg';
|
||||
const upscale = useStore().upscale;
|
||||
|
||||
const uploadDraggerRef = ref<HTMLDivElement | null>(null);
|
||||
const containerRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -19,9 +19,34 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import UploadImage from "@/views/Upscale/UploadImage.vue";
|
||||
import CompareImage from "@/views/Upscale/CompareImage.vue";
|
||||
import ParameterSetting from "@/views/Upscale/ParameterSetting.vue";
|
||||
|
||||
import useStore from "@/store";
|
||||
|
||||
const websocket = useStore().websocket;
|
||||
const user = useStore().user;
|
||||
const upscale = useStore().upscale;
|
||||
|
||||
const img = new Image();
|
||||
const wsOptions = {
|
||||
url: import.meta.env.VITE_FILE_SOCKET_URL + "?user_id=" + user.user.uid,
|
||||
protocols: [user.user.access_token],
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
websocket.initialize(wsOptions);
|
||||
websocket.on("message", async (res: any) => {
|
||||
if (res && res.code === 200) {
|
||||
const {data} = res;
|
||||
img.src = data;
|
||||
await upscale.loadImg(img);
|
||||
upscale.imageData = data;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
websocket.close(false);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upscale-container {
|
||||
@@ -30,13 +55,6 @@ import ParameterSetting from "@/views/Upscale/ParameterSetting.vue";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
//.upscale-title {
|
||||
// font-size: 16px;
|
||||
// font-weight: bold;
|
||||
// margin-left: 5px;
|
||||
//}
|
||||
|
||||
.upscale-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
3
src/vite-env.d.ts
vendored
3
src/vite-env.d.ts
vendored
@@ -1,7 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
declare interface ImportMetaEnv {
|
||||
readonly VITE_APP_BASE_API: string;
|
||||
readonly VITE_APP_TITLE: string;
|
||||
readonly VITE_API_BASE_URL: string;
|
||||
readonly VITE_NODE_ENV: string;
|
||||
readonly VITE_TITLE_NAME: string;
|
||||
@@ -9,6 +8,8 @@ declare interface ImportMetaEnv {
|
||||
readonly VITE_QR_SOCKET_URL: string;
|
||||
readonly VITE_MESSAGE_SOCKET_URL: string;
|
||||
readonly VITE_FINGERPRINT_KEY: string;
|
||||
readonly VITE_FILE_SOCKET_URL: string;
|
||||
readonly VITE_APP_WEB_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
@@ -24,7 +24,6 @@ self.onmessage = async function (e: MessageEvent): Promise<void> {
|
||||
let Model: tf.GraphModel;
|
||||
try {
|
||||
Model = await tf.loadGraphModel(`indexeddb://${model_name}`);
|
||||
console.log("Model loaded successfully");
|
||||
self.postMessage({info: "Model loaded from cache successfully"});
|
||||
} catch (_error) {
|
||||
self.postMessage({info: "Downloading model..."});
|
||||
@@ -221,7 +220,6 @@ self.onmessage = async function (e: MessageEvent): Promise<void> {
|
||||
const factor = data?.factor || 4;
|
||||
const tile_size = data?.tile_size || 64;
|
||||
const min_lap = data?.min_lap || 12;
|
||||
const start = Date.now();
|
||||
let output: any;
|
||||
try {
|
||||
output = await enlargeImageWithFixedInput(
|
||||
@@ -237,8 +235,6 @@ self.onmessage = async function (e: MessageEvent): Promise<void> {
|
||||
if (withPadding) {
|
||||
output.cropToOriginalSize(width_ori * factor, height_ori * factor);
|
||||
}
|
||||
const end = Date.now();
|
||||
console.log("Time:", end - start);
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
self.postMessage({
|
||||
progress: 100,
|
||||
|
Reference in New Issue
Block a user