🚧 developing

This commit is contained in:
2025-02-24 00:36:54 +08:00
parent c7288b2cb4
commit 5befcddaf5
37 changed files with 783 additions and 127 deletions

4
components.d.ts vendored
View File

@@ -31,6 +31,7 @@ declare module 'vue' {
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AlbumShareModal: typeof import('./src/views/Album/Phoalbum/AlbumShareModal.vue')['default']
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']
@@ -42,6 +43,7 @@ declare module 'vue' {
AProgress: typeof import('ant-design-vue/es')['Progress']
AQrcode: typeof import('ant-design-vue/es')['QRCode']
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
ArrowDownOutlined: typeof import('@ant-design/icons-vue')['ArrowDownOutlined']
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASpin: typeof import('ant-design-vue/es')['Spin']
@@ -70,6 +72,7 @@ declare module 'vue' {
DownloadOutlined: typeof import('@ant-design/icons-vue')['DownloadOutlined']
DownOutlined: typeof import('@ant-design/icons-vue')['DownOutlined']
DynamicTitle: typeof import('./src/components/DynamicTitle/DynamicTitle.vue')['default']
EditOutlined: typeof import('@ant-design/icons-vue')['EditOutlined']
EyeInvisibleOutlined: typeof import('@ant-design/icons-vue')['EyeInvisibleOutlined']
FileImageOutlined: typeof import('@ant-design/icons-vue')['FileImageOutlined']
Folder: typeof import('./src/components/Folder/Folder.vue')['default']
@@ -78,6 +81,7 @@ declare module 'vue' {
ImageShare: typeof import('./src/views/Share/ImageShare/ImageShare.vue')['default']
ImageToolbar: typeof import('./src/views/Photograph/ImageToolbar/ImageToolbar.vue')['default']
ImageUpload: typeof import('./src/views/Photograph/ImageUpload/ImageUpload.vue')['default']
ItalicOutlined: typeof import('@ant-design/icons-vue')['ItalicOutlined']
LandingPage: typeof import('./src/views/Landing/LandingPage.vue')['default']
LeftOutlined: typeof import('@ant-design/icons-vue')['LeftOutlined']
LinkOutlined: typeof import('@ant-design/icons-vue')['LinkOutlined']

View File

@@ -0,0 +1 @@
<svg t="1740245840658" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2464" width="200" height="200"><path d="M512 64a448 448 0 1 1 0 896A448 448 0 0 1 512 64z" fill="#FF6A00" p-id="2465"></path><path d="M324.8 602.624a26.752 26.752 0 0 1-21.312-25.92v-142.72a27.712 27.712 0 0 1 21.376-25.984l132.416-28.672 13.952-56.896H317.312a97.6 97.6 0 0 0-98.24 96.96v169.344c0.384 54.08 44.16 97.856 98.24 98.176h153.92l-13.888-56.512-132.544-27.776zM710.4 322.432c54.016 0.128 97.92 43.584 98.56 97.6v170.176a98.368 98.368 0 0 1-98.56 98.048H555.328l14.08-56.832 132.608-28.736a27.84 27.84 0 0 0 21.376-25.92v-142.72a26.88 26.88 0 0 0-21.376-25.984l-132.544-28.8-14.08-56.832zM570.368 497.92v13.952H457.28v-13.952h113.088z" fill="#FFFFFF" p-id="2466"></path></svg>

After

Width:  |  Height:  |  Size: 803 B

View File

@@ -0,0 +1 @@
<svg t="1740245977035" class="icon" viewBox="0 0 1027 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10116" width="200" height="200"><path d="M402.3125 208.25c16.875-5.62500027 33.75-8.4375 50.625-11.24999973 19.68749973 25.3125 25.3125 59.0625 33.75 89.99999946 5.62500027 33.75 11.24999973 67.5 11.24999973 101.25 5.62500027 22.50000027 2.81249973 47.81250027 2.81250054 70.31250054s-2.81249973 42.1875 0 64.68749946c0 30.93750027-2.81249973 59.0625-2.81250054 90.00000027-5.62500027 19.68749973 0 39.37500027-5.62499946 59.0625-8.4375-2.81249973-11.24999973-11.24999973-14.06250027-16.875-33.75-50.625-64.68750027-101.25-92.8125-154.68749973-28.12499973-56.25000027-56.25000027-112.49999973-59.0625-177.1875-5.62500027-50.625 28.12499973-98.43750027 75.9375-115.31250027z m174.37500027-8.4375c5.62500027-2.81249973 8.4375 0 14.06249946 0 22.50000027 5.62500027 47.81250027 8.4375 67.5 22.50000027s36.56249973 33.75 42.1875 56.24999946c8.4375 28.12499973 5.62500027 59.0625 0 87.18750054-8.4375 36.56249973-25.3125 70.31249973-42.1875 104.06249973-5.62500027 11.24999973-11.24999973 19.68749973-16.875 30.93750027-8.4375 19.68749973-22.50000027 39.37500027-33.75 59.0625-19.68749973 33.75-39.37500027 61.87499973-59.0625 95.62499973-2.81249973 2.81249973-5.62500027 11.24999973-11.24999973 5.62500027-2.81249973-33.75-5.62500027-67.5-8.4375-104.06250054-5.62500027-44.99999973-2.81249973-87.18749973-2.81249973-132.18749946 2.81249973-28.12499973 2.81249973-59.0625 5.62499946-87.18750054 5.62500027-33.75 11.24999973-70.31249973 22.50000027-104.06249973 11.24999973-8.4375 11.24999973-25.3125 22.50000027-33.75zM222.31250027 309.5c2.81249973 0 5.62500027 5.62500027 8.4375 8.4375 81.56250027 106.87500027 154.68749973 219.375 213.74999973 340.31249973 5.62500027 8.4375 11.24999973 19.68749973 11.24999973 30.93750027-11.24999973-2.81249973-19.68749973-8.4375-28.12499973-14.06250027-53.43749973-28.12499973-106.87500027-59.0625-160.3125-89.99999946-19.68749973-14.06250027-39.37500027-28.12499973-56.25000027-42.1875-33.75-22.50000027-56.25000027-64.68750027-53.43749973-106.87500027 2.81249973-50.625 30.93750027-92.8125 64.68750027-126.5625z m582.1875 0h5.62499946c14.06250027 19.68749973 33.75 39.37500027 45.00000054 61.87499973 11.24999973 19.68749973 16.875 44.99999973 19.68749973 67.5 0 25.3125-8.4375 53.43749973-28.12499973 73.12500027-11.24999973 11.24999973-19.68749973 22.50000027-33.75 30.93750027-64.68750027 50.625-135 90.00000027-208.12500027 126.5625-11.24999973 5.62500027-19.68749973 14.06250027-33.75 14.06249946 2.81249973-14.06250027 11.24999973-28.12499973 16.875-39.37499946 47.81250027-98.43750027 106.87500027-191.25000027 171.56249973-281.25000054 14.06250027-14.06250027 30.93750027-33.75 45.00000054-53.43749973z m-705.93750027 225.00000027c2.81249973-2.81249973 0-8.4375 5.62500027-11.25000054 8.4375 2.81249973 16.875 8.4375 22.49999946 11.25000054 101.25 56.25000027 202.5 112.49999973 300.93750027 174.37499946 2.81249973 2.81249973 5.62500027 5.62500027 5.62500027 8.4375H239.18750027c-39.37500027 0-75.9375-16.875-104.06250054-44.99999973-22.50000027-25.3125-42.1875-59.0625-44.99999973-92.8125 5.62500027-14.06250027 2.81249973-28.12499973 8.4375-44.99999973z m810-2.81250054c5.62500027-2.81249973 14.06250027-8.4375 19.68749973-5.62499946 0 14.06250027 5.62500027 30.93750027 5.62500027 44.99999973-2.81249973 19.68749973-2.81249973 36.56249973-11.24999973 53.43749973-5.62500027 14.06250027-14.06250027 30.93750027-25.3125 42.1875-14.06250027 11.24999973-22.50000027 25.3125-39.37500027 33.75-16.875 14.06250027-42.1875 16.875-61.87499973 19.68750054h-202.5c2.81249973-2.81249973 2.81249973-5.62500027 5.62499946-8.4375 101.25-64.68750027 205.31249973-123.75000027 309.37500027-180.00000054zM242 748.25c36.56249973-2.81249973 73.12500027 0 109.6875-5.62500027 25.3125 0 53.43749973-2.81249973 78.74999973 0-5.62500027 11.24999973-19.68749973 16.875-28.12499973 22.50000027-28.12499973 19.68749973-56.25000027 36.56249973-87.18749973 50.625s-67.5 8.4375-92.8125-11.24999973c-19.68749973-14.06250027-36.56249973-36.56249973-47.81250027-56.25000027h67.5z m357.18749973-5.62500027c25.3125-2.81249973 50.625 0 78.75000054 0 33.75 2.81249973 70.31249973 0 106.87499946 5.62500027 25.3125 2.81249973 50.625 0 73.12500027 2.81249973-8.4375 16.875-22.50000027 33.75-36.56249973 47.81250027-22.50000027 22.50000027-56.25000027 33.75-87.18750054 28.12499973-28.12499973-8.4375-50.625-25.3125-75.9375-39.37499946-14.06250027-8.4375-25.3125-16.875-39.37499946-25.3125-8.4375-8.4375-14.06250027-11.24999973-19.68750054-19.68750054z" fill="#C71F1E" p-id="10117"></path></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1 @@
<svg t="1740245954753" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9138" width="200" height="200"><path d="M638.855218 56.525807s99.268136 159.838523 132.357514 216.623262a2.523766 2.523766 0 0 1 0 2.944394 2.383557 2.383557 0 0 1-3.50523 0L596.231612 97.326693l42.623606-40.800886z" fill="#dd113c" p-id="9139"></path><path d="M346.518971 639.655999a588.878771 588.878771 0 0 1 116.654081-165.446893 597.291325 597.291325 0 0 1 58.32704-51.176369v126.188308L346.518971 639.655999zM245.568325 756.590498l275.931767-140.209231v321.079139l62.112689 80.760517v-434.648616l37.716283-19.489084a187.179324 187.179324 0 0 0 51.456788-296.121896L530.753901 119.479752a31.547077 31.547077 0 0 1 1.542302-44.446327 31.687286 31.687286 0 0 1 44.586535 1.542302l19.909711 20.750966 42.062769-40.941095c-50.335114-65.337502-112.167385-57.065157-147.64032-24.396407a90.575163 90.575163 0 0 0-3.925859 127.870819l143.574253 149.60325a128.151237 128.151237 0 0 1-28.041846 197.414597l-19.489083 10.095065V314.090164A649.589368 649.589368 0 0 0 245.568325 755.889452v0.701046z" fill="#dd113c" p-id="9140"></path><path d="M583.612781 583.432097v65.617921l-62.112689 31.547077v-65.197293z" fill="#dd113c" p-id="9141"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg t="1740245876441" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6099" width="200" height="200"><path d="M887.36 749.312c-18.176 18.048-50.752 38.528-104.128 40.32-24.704 0.896-53.504 0.96-66.688 0.96H429.696l205.696-199.808c9.472-9.216 30.72-29.568 49.152-46.08 40.32-36.288 76.544-43.584 102.208-43.328 40.192 0.384 76.8 16.768 102.848 43.264 55.936 57.152 54.592 148.48-2.24 204.672m68.736-269.696A236.16 236.16 0 0 0 787.2 408.32c-57.472 0-106.88 19.776-150.08 54.912-18.816 15.36-38.528 33.664-63.36 57.728-12.288 12.032-369.792 358.912-369.792 358.912 18.752 2.624 44.48 3.456 67.456 3.584 21.568 0.128 432.32 0.128 449.472 0.128 34.624 0 57.152-0.064 81.28-1.856 55.552-4.032 107.968-24.384 150.336-65.984a237.504 237.504 0 0 0 3.648-336.192z" fill="#00A3FF" p-id="6100"></path><path d="M379.328 457.28c-42.048-31.424-89.088-49.024-142.4-48.96a237.568 237.568 0 0 0-165.376 407.488c37.76 37.12 83.52 57.28 132.352 64.064l92.032-89.28c-14.848-0.064-36.224-0.256-55.168-0.896-53.376-1.856-85.888-22.336-104.128-40.32A145.024 145.024 0 0 1 134.4 544.64c26.112-26.496 62.72-42.88 102.848-43.264 25.216-0.128 59.392 7.168 98.048 39.232 18.432 15.296 59.392 51.2 77.312 67.456 0.896 0.832 2.048 0.896 3.072 0l63.36-61.824c1.152-1.024 1.088-2.496 0-3.456-30.528-27.52-73.728-66.112-99.712-85.504" fill="#00C8DC" p-id="6101"></path><path d="M811.968 354.688A318.336 318.336 0 0 0 512 142.656a318.72 318.72 0 0 0-314.368 268.992 236.608 236.608 0 0 1 92.992 3.008l0.96 0.192A225.28 225.28 0 0 1 512 235.776c89.984 0 168.896 54.272 204.48 131.136 0.576 1.216 1.536 1.6 2.496 1.344 26.752-7.36 58.88-11.648 89.792-9.344 3.072 0.192 4.224-1.472 3.2-4.224" fill="#006EFF" p-id="6102"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -2,7 +2,7 @@ import {service} from "@/utils/alova/service.ts";
import {uploadImageRequest} from "@/types/upscale";
export const uploadImage = (data: uploadImageRequest) => {
return service.Post('/api/auth/upscale/phone/upload', {
return service.Post('/api/auth/phone/upload', {
image: data.image,
access_token: data.access_token,
user_id: data.user_id,

View File

@@ -53,7 +53,7 @@ export const getFaceSamplesDetailList = (face_id: number, provider: string, buck
ignoreToken: false,
signature: false,
},
hitSource: ["modify-face-sample-name", "modify-face-sample-type"],
hitSource: ["modify-face-sample-name", "modify-face-sample-type", "delete-images"],
});
};
/**
@@ -123,7 +123,7 @@ export const albumListApi = (type: number, sort: boolean) => {
ignoreToken: false,
signature: false,
},
hitSource: ["create-album", "rename-album", "delete-album"],
hitSource: ["create-album", "rename-album", "delete-album", "album-share"],
});
};
/**
@@ -147,7 +147,7 @@ export const queryAlbumDetailListApi = (id: number, provider: string, bucket: st
signature: false,
},
name: "album-detail-list",
hitSource: ["upload-file", "delete-images"],
hitSource: ["upload-file", "delete-images", "album-share"],
});
};
@@ -205,7 +205,7 @@ export const queryAllImagesApi = (type: string, sort: boolean, provider: string,
ignoreToken: false,
signature: false,
},
hitSource: ["upload-file", "delete-images"],
hitSource: ["upload-file", "delete-images", "album-share"],
});
};
@@ -213,8 +213,11 @@ export const queryAllImagesApi = (type: string, sort: boolean, provider: string,
/**
* 获取最近照片列表
*/
export const queryRecentImagesApi = () => {
return service.Post('/api/auth/storage/image/recent/list', {}, {
export const queryRecentImagesApi = (provider: string, bucket: string) => {
return service.Post('/api/auth/storage/image/recent/list', {
provider: provider,
bucket: bucket,
}, {
cacheFor: {
expire: 60 * 60 * 24 * 7,
mode: "restore",
@@ -407,6 +410,31 @@ export const getBucketCapacityApi = (provider: string, bucket: string) => {
ignoreToken: false,
signature: false,
},
name: "delete-images",
name: "get-bucket-capacity",
});
};
/**
* 分享相册
* @param id
* @param expire_date
* @param access_limit
* @param access_password
* @param provider
* @param bucket
*/
export const albumShareApi = (id: number, expire_date: string, access_limit: number, access_password: string, provider: string, bucket: string) => {
return service.Post('/api/auth/storage/album/share', {
id: id,
expire_date: expire_date,
access_limit: access_limit,
access_password: access_password,
provider: provider,
bucket: bucket,
}, {
meta: {
ignoreToken: false,
signature: false,
},
name: "album-share",
});
};

View File

@@ -80,3 +80,13 @@ html {
.ant-table-wrapper .ant-table:not(.ant-table-bordered) .ant-table-tbody > tr:last-child > td {
border-bottom: none !important;
}
// 空白内容样式
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}

1
src/assets/svgs/oss.svg Normal file
View File

@@ -0,0 +1 @@
<svg t="1740246256617" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="19370" width="200" height="200"><path d="M622.08 162.816c-164.352 0-302.08 113.152-339.456 265.728-3.584 0-6.656-0.512-10.24-0.512-119.296 0-216.064 96.768-216.064 216.064 0 57.344 22.528 112.128 63.488 152.576 40.448 40.448 95.744 63.488 153.088 63.488s112.64-23.04 153.088-63.488c33.792-33.792 52.736-79.36 58.368-129.536l-78.336-6.144c-17.92-2.048-23.552-15.872-12.8-30.208l132.608-164.352c10.752-14.336 25.088-12.8 32.256 4.096l75.264 183.808c7.168 16.384-2.048 28.16-19.456 26.112l-58.368-5.12c-6.656 60.928-33.792 114.688-72.192 157.184 44.032 18.944 91.136 28.672 138.752 28.672 193.024 0 349.184-156.672 349.184-349.184s-156.16-349.184-349.184-349.184z" fill="#999999" p-id="19371"></path></svg>

After

Width:  |  Height:  |  Size: 819 B

View File

@@ -0,0 +1 @@
<svg t="1740249125892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="27758" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#EFEFF4" p-id="27759"></path><path d="M512 814.545455a302.545455 302.545455 0 0 1-213.934545-516.48 302.545455 302.545455 0 1 1 427.86909 427.86909A300.555636 300.555636 0 0 1 512 814.545455z m0-186.379637a49.850182 49.850182 0 1 0 49.838545 49.838546 49.896727 49.896727 0 0 0-49.838545-49.838546z m-19.362909-281.530182a46.289455 46.289455 0 0 0-45.975273 51.549091l19.374546 169.157818a46.277818 46.277818 0 0 0 91.927272 0l19.362909-169.169454a46.277818 46.277818 0 0 0-45.975272-51.549091z" fill="#D2D2D8" p-id="27760"></path></svg>

After

Width:  |  Height:  |  Size: 760 B

View File

@@ -0,0 +1,6 @@
export const ProviderIcon = {
"ali": "/provider_icon/ali.svg",
"tencent": "/provider_icon/tencent.svg",
"minio": "/provider_icon/minio.svg",
"huawei": "/provider_icon/huawei.svg",
};

View File

@@ -58,10 +58,10 @@
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/photo/upscale')">
<div class="header-logo-popover-card" @click="router.push('/main/photo/phone')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="small" shape="square" :src="ai"/>
<span class="header-logo-popover-text">{{ t('album.upscale') }}</span>
<span class="header-logo-popover-text">{{ t('album.phone') }}</span>
</AFlex>
</div>

View File

@@ -2,13 +2,23 @@
<AFlex :vertical="false" align="center" justify="flex-end" class="header-menu-container">
<AFlex :vertical="false" align="center" justify="flex-start" class="header-menu-item" gap="large">
<!-- 存储选择 -->
<div class="header-select-container">
<ACascader v-model:value="uploadStore.storageSelected"
:options="configList"
:show-search="{ filter }"
:field-names="{ label: 'name', value: 'value', children: 'children' }"
placeholder="选择存储桶">
</ACascader>
<div class="button-wrapper">
<APopover :arrow="false" trigger="click" placement="right">
<template #content>
<ACascader v-model:value="uploadStore.storageSelected"
:options="configList"
:bordered="false"
style="width: 100%"
:field-names="{ label: 'name', value: 'value', children: 'children' }"
placeholder="选择存储桶">
</ACascader>
</template>
<AButton type="text" shape="circle" size="large" class="header-menu-item-btn">
<template #icon>
<AAvatar size="default" shape="circle" :src="ProviderIcon[uploadStore.storageSelected?.[0]]? ProviderIcon[uploadStore.storageSelected?.[0]] : wenhao"/>
</template>
</AButton>
</APopover>
</div>
<!-- 社区按钮 -->
@@ -141,15 +151,17 @@ import systemMessage from "@/assets/svgs/sys-msg.svg";
import personalCenter from "@/assets/svgs/personal-center.svg";
import accountSetting from "@/assets/svgs/setting.svg";
import logout from "@/assets/svgs/logout.svg";
import wenhao from "@/assets/svgs/wenhao.svg";
import useStore from "@/store";
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
import {getStorageConfigListApi} from "@/api/storage";
import type {ShowSearchType} from 'ant-design-vue/es/cascader';
import {ProviderIcon} from "@/constant/provider_icon.ts";
const uploadStore = useStore().upload;
const user = useStore().user;
const configList = ref<any[]>([]);
async function getUserConfigList() {
@@ -159,10 +171,6 @@ async function getUserConfigList() {
}
}
const filter: ShowSearchType['filter'] = (inputValue, path) => {
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
};
onMounted(() => {
getUserConfigList();

View File

@@ -1,11 +1,20 @@
export default [
{
path: '/upscale/app',
path: '/main/upscale/phone/app',
name: 'upscaleApp',
component: () => import('@/views/Phone/UpscalePhoneUpload/UpscalePhoneUpload.vue'),
meta: {
requiresAuth: false,
title: '手机上传'
}
},
{
path: '/main/share/phone/app',
name: 'shareApp',
component: () => import('@/views/Phone/SharePhoneUpload/SharePhoneUpload.vue'),
meta: {
requiresAuth: false,
title: '手机上传'
}
}
];

View File

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

View File

@@ -18,6 +18,13 @@ export const useImageStore = defineStore(
"gif": "动图",
"screenshot": "截图",
});
// 清算模式切换
const switchValue = ref<boolean>(false);
// 相册分享弹窗相关
const openAlbumShareDialog = ref<boolean>(false);
const albumShareCoverImage = ref<string>("");
const albumShareId = ref<number>(0);
/**
* 统计图片总数
@@ -31,13 +38,24 @@ export const useImageStore = defineStore(
return imageList.reduce((total, record) => total + record.list.length, 0);
}
function openAlbumShareDialogHandler(value: boolean, coverImage: string, albumId: number) {
openAlbumShareDialog.value = value;
albumShareCoverImage.value = coverImage;
albumShareId.value = albumId;
}
return {
selected,
tabActiveKey,
tabMap,
homeTabMap,
switchValue,
homeTabActiveKey,
countTotalImages
albumShareCoverImage,
albumShareId,
countTotalImages,
openAlbumShareDialog,
openAlbumShareDialogHandler,
};
},
{
@@ -46,7 +64,7 @@ export const useImageStore = defineStore(
persist: true,
storage: localStorage,
key: 'image',
includePaths: ["tabActiveKey", "tabMap", "homeTabActiveKey", "homeTabMap"],
includePaths: ["tabActiveKey", "tabMap", "homeTabActiveKey", "homeTabMap", "switchValue"],
}
}
);

View File

@@ -15,7 +15,6 @@ interface UploadPredictResult {
thumb_size: number | null;
}
export const useUploadStore = defineStore(
'upload',
() => {
@@ -38,6 +37,8 @@ export const useUploadStore = defineStore(
const storageSelected = ref<any[]>([]);
const albumSelected = ref<number>();
/**
* 打开上传抽屉
*/
@@ -70,6 +71,7 @@ export const useUploadStore = defineStore(
storageSelected,
openUploadDrawerFn,
clearPredictResult,
albumSelected,
};
},
{
@@ -78,7 +80,7 @@ export const useUploadStore = defineStore(
persist: true,
storage: localStorage,
key: 'upload',
includePaths: ["storageSelected"]
includePaths: ["storageSelected", "albumSelected"]
}
}
);

View File

@@ -42,8 +42,8 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧

View File

@@ -4,7 +4,7 @@
<AButton type="link" size="large" class="location-album-button">地点</AButton>
<span class="location-album-count">你一共在{{ locationAlbums ? locationAlbums.length : 0 }}个地点留下足迹</span>
</div>
<div class="location-album-content">
<div class="location-album-content" v-if="locationAlbums && locationAlbums.length>0 ">
<div class="location-album-content-item" v-for="(item, index) in locationAlbums" :key="index">
<span class="location-album-description">{{ item.location }}</span>
<div class="location-album-location-list">
@@ -20,17 +20,27 @@
</div>
</div>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</div>
</template>
<script setup lang="ts">
import {queryLocationAlbumApi} from "@/api/storage";
import useStore from "@/store";
import empty from "@/assets/svgs/empty.svg";
const route = useRoute();
const router = useRouter();
const upload = useStore().upload;
function handleClick(id: number,name: string) {
function handleClick(id: number, name: string) {
router.push({path: route.path + `/${id}`, query: {name: name}});
}

View File

@@ -10,7 +10,7 @@
</AButton>
</div>
<div class="people-album-detail-toolbar">
<AAvatar shape="circle" size="default"></AAvatar>
<AAvatar shape="circle" size="default" :src="route.query.thumb"></AAvatar>
<span style="font-size: 14px;color: #333333">{{ route.query.name }}</span>
</div>
</div>
@@ -19,7 +19,7 @@
<span style="font-size: 14px;color: #999999">{{ imageStore.countTotalImages(images) }}张照片</span>
</div>
<div class="people-album-detail-list">
<div style="width:100%;height:100%;" v-if="images.length !== 0">
<div style="width:100%;height:100%;" v-if="images &&images.length !== 0">
<div v-for="(itemList, index) in images" :key="index">
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
<AImagePreviewGroup>
@@ -49,8 +49,8 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧

View File

@@ -50,12 +50,12 @@
</div>
</transition>
<div class="people-album-container">
<ASpin :spinning="loading" size="large" wrapperClassName="people-album-container">
<div class="people-album-content">
<ASpin :spinning="loading" size="large" wrapperClassName="spin-container">
<div class="people-album-content" v-if="faceList.length !== 0">
<CheckCard
v-for="(item, index) in faceList"
:key="index"
@click="handleClick(item.id, item.face_name)"
@click="handleClick(item.id, item.face_name, item.face_image)"
class="photo-item"
margin="0"
border-radius="0"
@@ -115,12 +115,22 @@
</CheckCard>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</div>
</div>
</template>
<script setup lang="ts">
import {getFaceSamplesList, modifyFaceSampleName, modifyFaceTypeBatch} from "@/api/storage";
import empty from "@/assets/svgs/empty.svg";
const faceList = ref<any[]>([]);
@@ -217,9 +227,10 @@ const router = useRouter();
* 点击人物跳转到详情页
* @param id
* @param name
* @param thumb
*/
function handleClick(id: number, name: string | null) {
router.push({path: route.path + `/${id}`, query: {name: name}});
function handleClick(id: number, name: string | null,thumb: string | null) {
router.push({path: route.path + `/${id}`, query: {name: name, thumb: thumb}});
}
onMounted(() => {
@@ -332,6 +343,13 @@ onMounted(() => {
align-content: flex-start;
flex-wrap: wrap;
.spin-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.people-album-content {
width: 100%;
height: 100%;

View File

@@ -0,0 +1,116 @@
<template>
<AModal v-model:open="imageStore.openAlbumShareDialog" :footer="null" width="25%"
@cancel="imageStore.openAlbumShareDialogHandler(false, '', 0)">
<div class="share-modal">
<div class="share-modal-title">
<h3>分享相册</h3>
</div>
<AAvatar shape="square" :size="200">
<template #icon>
<AImage :preview="false" width="100%" height="100%"
:src="imageStore.albumShareCoverImage?imageStore.albumShareCoverImage:default_cover"/>
</template>
</AAvatar>
<div class="album-share-select">
<div class="album-share-select-item">
<span class="label-text">访问时效</span>
<ASelect style="width: 50%" placeholder="请选择" :defaultValue="1" v-model:value="expire_date">
<ASelectOption value="1">1</ASelectOption>
<ASelectOption value="3">3</ASelectOption>
<ASelectOption value="7">7</ASelectOption>
<ASelectOption value="15">15</ASelectOption>
<ASelectOption value="30">30</ASelectOption>
<ASelectOption value="0">永久</ASelectOption>
</ASelect>
</div>
<div class="album-share-select-item">
<span class="label-text">访问密码</span>
<AInputPassword style="width: 50%" v-model:value="access_password" :maxlength="10"
:showCount="true"></AInputPassword>
</div>
<div class="album-share-select-item">
<span class="label-text">访问限制</span>
<AInputNumber style="width: 50%" :defaultValue="100" :min="1"
v-model:value="access_limit"></AInputNumber>
</div>
</div>
<AButton type="primary" size="large" shape="round" style="width: 100%" @click="createShare">
<template #icon>
<PlusSquareOutlined/>
</template>
创建分享
</AButton>
</div>
</AModal>
</template>
<script setup lang="ts">
import useStore from "@/store";
import default_cover from "@/assets/images/default-cover.png";
import {albumShareApi} from "@/api/storage";
import {message} from "ant-design-vue";
const imageStore = useStore().image;
const upload = useStore().upload;
const expire_date = ref<string>("1");
const access_limit = ref<number>(100);
const access_password = ref<string>("");
/**
* 创建分享
*/
async function createShare() {
const res: any = await albumShareApi(
imageStore.albumShareId,
expire_date.value,
access_limit.value,
access_password.value,
upload.storageSelected?.[0],
upload.storageSelected?.[1]
);
if (res && res.code === 200) {
imageStore.openAlbumShareDialogHandler(false, '', 0);
message.success("分享成功");
} else {
message.error("分享失败");
}
}
</script>
<style scoped lang="scss">
.share-modal {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 20px;
.album-share-select {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
flex-wrap: nowrap;
.album-share-select-item {
width: 100%;
height: 5vh;
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
.label-text {
width: 50%;
color: #999999;
font-size: 2.2vh;
}
}
}
}
</style>

View File

@@ -1,31 +1,45 @@
<template>
<div class="phoalbum-detail">
<div class="phoalbum-detail-header">
<AButton type="primary" shape="round" size="middle">
<AButton type="primary" shape="round" size="middle" @click="openUploadModal()">
<template #icon>
<PlusSquareOutlined/>
<CloudUploadOutlined/>
</template>
上传
</AButton>
<AButton type="default" shape="round" size="middle">
<template #icon>
<PlusSquareOutlined/>
<APopover placement="right" trigger="click">
<AButton type="default" shape="round" size="middle">
<template #icon>
<EditOutlined/>
</template>
重命名
</AButton>
<template #content>
<AInput :placeholder="route.query.name"
v-model:value="albumRenameValue">
<template #suffix>
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
@click="renameAlbum(albumRenameValue)">
确认
</AButton>
</template>
</AInput>
</template>
重命名
</AButton>
<AButton type="default" shape="round" size="middle">
</APopover>
<AButton type="default" shape="round" size="middle" @click="deleteAlbum">
<template #icon>
<PlusSquareOutlined/>
<DeleteOutlined/>
</template>
删除相册
</AButton>
<AButton type="default" shape="round" size="middle">
<template #icon>
<PlusSquareOutlined/>
<DownloadOutlined/>
</template>
下载相册
</AButton>
</div>
<ImageUpload/>
<ImageToolbar :selected="imageStore.selected" :image-list="albumList"/>
<div class="phoalbum-detail-content">
<div class="phoalbum-detail-content-nav">
@@ -45,7 +59,7 @@
<span>相册描述</span>
</div>
<div class="phoalbum-detail-content-list">
<div style="width:100%;height:100%;" v-if="albumList.length !== 0">
<div style="width:100%;height:100%;" v-if="albumList && albumList.length !== 0">
<div v-for="(itemList, index) in albumList" :key="index">
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
<AImagePreviewGroup>
@@ -75,7 +89,7 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<div v-else class="empty-content">
<AEmpty :image="empty">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
@@ -91,10 +105,12 @@
<script setup lang="ts">
import Vue3JustifiedLayout from "vue3-justified-layout";
import 'vue3-justified-layout/dist/style.css';
import {queryAlbumDetailListApi} from "@/api/storage";
import {deleteAlbumApi, queryAlbumDetailListApi, renameAlbumApi} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import useStore from "@/store";
import empty from "@/assets/svgs/empty.svg";
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
import {message} from "ant-design-vue";
const imageStore = useStore().image;
@@ -121,6 +137,47 @@ onMounted(() => {
getAlbumList(parseInt(albumId, 10));
});
function openUploadModal(): void {
upload.openUploadDrawerFn();
const idParam = route.params.id;
const albumId = Array.isArray(idParam) ? idParam[0] : idParam;
upload.albumSelected = parseInt(albumId);
}
const albumRenameValue = ref<string>("");
/**
* 重命名相册
* @param name
*/
async function renameAlbum(name: string) {
if (name.trim() === "") {
message.warning("相册名称不能为空");
return;
}
const idParam = route.params.id;
const albumId = Array.isArray(idParam) ? idParam[0] : idParam;
const res: any = await renameAlbumApi(parseInt(albumId), name);
if (res && res.code === 200) {
albumRenameValue.value = "";
goBack();
}
}
/**
* 删除相册
*/
async function deleteAlbum() {
const idParam = route.params.id;
const id = Array.isArray(idParam) ? idParam[0] : idParam;
const res: any = await deleteAlbumApi(parseInt(id));
if (res && res.code === 200) {
goBack();
} else {
message.error("删除相册失败");
}
}
/**
* 返回上一页
*/

View File

@@ -1,7 +1,7 @@
<template>
<div class="phoalbum">
<div class="phoalbum-header">
<APopover placement="bottom" trigger="click">
<APopover placement="right" trigger="click">
<AButton type="primary" shape="round" size="middle">
<template #icon>
<PlusSquareOutlined/>
@@ -10,16 +10,15 @@
</AButton>
<template #content>
<span style="color: #999; font-size: 15px;">创建相册</span>
<AInputGroup compact style="display: flex;flex-direction: row;margin-top: 15px">
<AInput placeholder="请填写相册名称" class="phoalbum-create-input"
v-model:value="albumNameValue" size="large">
<template #suffix>
<AButton type="text" @click.prevent size="middle" style="color: #0e87cc" @click="createAlbumSubmit">
确认
</AButton>
</template>
</AInput>
</AInputGroup>
<AInput placeholder="请填写相册名称" class="phoalbum-create-input"
style="margin-top: 15px"
v-model:value="albumNameValue" size="large">
<template #suffix>
<AButton type="text" @click.prevent size="middle" style="color: #0e87cc" @click="createAlbumSubmit">
确认
</AButton>
</template>
</AInput>
</template>
</APopover>
@@ -57,8 +56,8 @@
style="color: #999; font-size: 12px;">已全部加载 {{ albumList ? albumList.length : 0 }} 个相册</span>
</template>
<ATabPane key="-1" :tab="imageStore.tabMap[-1]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@@ -95,7 +94,10 @@
</AInput>
</template>
</APopover>
<AMenuItem key="2">分享相册</AMenuItem>
<AMenuItem key="2"
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
分享相册
</AMenuItem>
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
<AMenuItem key="4">下载相册</AMenuItem>
</AMenu>
@@ -104,11 +106,20 @@
</div>
</div>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无相册快去创建一个吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
<ATabPane key="0" :tab="imageStore.tabMap[0]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@@ -145,7 +156,10 @@
</AInput>
</template>
</APopover>
<AMenuItem key="2">分享相册</AMenuItem>
<AMenuItem key="2"
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
分享相册
</AMenuItem>
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
<AMenuItem key="4">下载相册</AMenuItem>
</AMenu>
@@ -154,11 +168,20 @@
</div>
</div>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无相册快去创建一个吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
<ATabPane key="1" :tab="imageStore.tabMap[1]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@@ -195,7 +218,10 @@
</AInput>
</template>
</APopover>
<AMenuItem key="2">分享相册</AMenuItem>
<AMenuItem key="2"
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
分享相册
</AMenuItem>
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
<AMenuItem key="4">下载相册</AMenuItem>
</AMenu>
@@ -204,11 +230,20 @@
</div>
</div>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无相册快去创建一个吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
<ATabPane key="2" :tab="imageStore.tabMap[2]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<ASpin tip="Loading..." :spinning="loading" size="large" wrapperClassName="spin-container">
<div class="phoalbum-item-container" v-if="albumList && albumList.length != 0">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@@ -245,7 +280,10 @@
</AInput>
</template>
</APopover>
<AMenuItem key="2">分享相册</AMenuItem>
<AMenuItem key="2"
@click.prevent="imageStore.openAlbumShareDialogHandler(true,album.cover_image ?`data:image/png;base64,`+album.cover_image: ``,album.id)">
分享相册
</AMenuItem>
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
<AMenuItem key="4">下载相册</AMenuItem>
</AMenu>
@@ -254,10 +292,20 @@
</div>
</div>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无相册快去创建一个吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
</ATabs>
</div>
<AlbumShareModal/>
</div>
</template>
<script setup lang="ts">
@@ -266,6 +314,8 @@ import {albumListApi, createAlbumApi, deleteAlbumApi, renameAlbumApi} from "@/ap
import {message} from "ant-design-vue";
import default_cover from "@/assets/images/default-cover.png";
import useStore from "@/store";
import empty from "@/assets/svgs/empty.svg";
import AlbumShareModal from "@/views/Album/Phoalbum/AlbumShareModal.vue";
const isHovered = ref<number | null>(null);
@@ -413,6 +463,15 @@ onMounted(() => {
width: 100%;
height: calc(100% - 65px);
.spin-container {
position: relative;
transition: opacity 0.3s;
width: 100%;
height: 70vh;
display: flex;
flex-direction: column;
}
.phoalbum-item-container {
display: flex;
flex-direction: row;

View File

@@ -5,7 +5,7 @@
<AButton type="link" size="large" class="thing-album-button">事物</AButton>
</div>
<div class="thing-album-content">
<div class="thing-album-content" v-if="thingAlbumList && thingAlbumList.length>0">
<div class="thing-album-content-item" v-for="(item, index) in thingAlbumList" :key="index">
<span class="thing-album-title">{{ getZhCategoryNameByEnName(item.category) }}</span>
<div class="thing-album-wrapper">
@@ -21,6 +21,15 @@
</div>
</div>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{width: '100%', height: '100%'}">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</div>
</template>
<script setup lang="ts">
@@ -29,6 +38,7 @@
import {queryThingAlbumApi} from "@/api/storage";
import {getZhCategoryNameByEnName, getZhLabelNameByEnName} from "@/constant/coco_ssd_label_category.ts";
import useStore from "@/store";
import empty from "@/assets/svgs/empty.svg";
const thingAlbumList = ref<any[]>([]);

View File

@@ -1,9 +1,180 @@
<template>
<div class="upscale-upload-content">
<AUploadDragger
name="file"
accept="image/*"
:multiple="true"
:directory="false"
:maxCount="1"
:beforeUpload="beforeUpload"
:fileList="fileList"
:loading="uploading"
:showUploadList="true">
<div class="upscale-upload-content-main">
<ABadge :offset="[-10, 10]">
<template #count>
<AAvatar :size="25" :src="sueccess" v-if="fileList.length > 0"/>
<AAvatar :size="25" :src="warn" v-if="fileList.length === 0"/>
</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="fileList.length > 0">
<ABadge v-for="(item, index) in fileList" :key="index">
<template #count>
<AButton type="text" size="small" class="upscale-file-btn">
<template #icon>
<AAvatar shape="square" :size="25" :src="remove"/>
</template>
</AButton>
</template>
<AAvatar shape="square" :size="100">
<template #icon>
<AImage :src="convertFileToUrl(item.file)" width="100%" height="100%"/>
</template>
</AAvatar>
</ABadge>
</div>
<AEmpty v-else :image="empty" :description="null"/>
<ADivider/>
<div>
<AButton type="primary" size="large" :disabled="fileList.length === 0" :loading="uploading"
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 {message} from "ant-design-vue";
import {useI18n} from "vue-i18n";
import i18n from "@/locales";
import {initNSFWJs, predictNSFW} from "@/utils/tfjs/nsfw.ts";
import {NSFWJS} from "nsfwjs";
const upscale = useStore().upscale;
const route = useRoute();
const {t} = useI18n();
const fileList = ref<any[]>([]);
const uploading = ref(false);
// 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 && res.code === 200) {
// message.success(t('upload.uploadSuccess'));
// } else {
// message.error(res.message);
// }
// }
/**
* 上传文件前置
* @param file
*/
async function beforeUpload(file: any) {
if (!window.FileReader) return false; // 判断是否支持FileReader
const reader = new FileReader();
reader.readAsDataURL(file); // 文件转换
reader.onloadend = async function () {
const img: HTMLImageElement = document.createElement('img');
img.src = reader.result as string;
img.onload = async () => {
// 图片 NSFW 检测
const nsfw: NSFWJS = await initNSFWJs();
const isNSFW: boolean = await predictNSFW(nsfw, img);
if (isNSFW) {
message.error(i18n.global.t('comment.illegalImage'));
return false;
}
};
};
return true;
}
/**
* 转换文件为 URL
* @param file
*/
function convertFileToUrl(file: any) {
return URL.createObjectURL(file);
}
</script>
<style scoped lang="scss">
.upscale-upload-content {
width: 90%;
height: 30vh;
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>

View File

@@ -62,7 +62,7 @@ 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 {uploadImage} from "src/api/phone";
import {uploadImageRequest} from "@/types/upscale";
import {message} from "ant-design-vue";
import {useI18n} from "vue-i18n";

View File

@@ -7,12 +7,27 @@
</template>
上传照片
</AButton>
<AButton type="default" shape="round" size="middle">
<template #icon>
<PlusSquareOutlined/>
<APopover placement="right" trigger="click">
<AButton type="default" shape="round" size="middle">
<template #icon>
<PlusSquareOutlined/>
</template>
创建相册
</AButton>
<template #content>
<span style="color: #999; font-size: 15px;">创建相册</span>
<AInput placeholder="请填写相册名称" class="phoalbum-create-input"
style="margin-top: 15px"
v-model:value="albumNameValue" size="large">
<template #suffix>
<AButton type="text" @click.prevent size="middle" style="color: #0e87cc" @click="createAlbumSubmit">
确认
</AButton>
</template>
</AInput>
</template>
创建相册
</AButton>
</APopover>
</div>
<image-toolbar :selected="imageStore.selected" :image-list="images"/>
<div class="photo-list">
@@ -21,10 +36,16 @@
v-model:activeKey="imageStore.homeTabActiveKey"
style="width: 99%;">
<template #rightExtra>
<ASwitch size="small" v-model:checked="switchValue"/>
<AFlex :vertical="false" align="center" gap="10">
<span style="color: #999999;">清爽模式</span>
<ATooltip title="开启后即可隐藏已添加到相册的照片" placement="bottom">
<QuestionCircleOutlined/>
</ATooltip>
<ASwitch size="default" v-model:checked="imageStore.switchValue"/>
</AFlex>
</template>
<ATabPane key="all" :tab="imageStore.homeTabMap['all']">
<ASpin size="large" :spinning="loading" :delay="500">
<ASpin size="large" :spinning="loading" :delay="500" wrapper-class-name="spin-wrapper">
<div style="width:100%;height:100%;" v-if="images">
<div v-for="(itemList, index) in images" :key="index">
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
@@ -59,7 +80,11 @@
</div>
</div>
<div v-else class="empty-content">
<AEmpty :image="empty">
<AEmpty :image="empty"
:image-style="{
height: '100%',
width: '100%',
}">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
还没检测到任何图片快去上传吧
@@ -237,13 +262,14 @@ import Vue3JustifiedLayout from "vue3-justified-layout";
import 'vue3-justified-layout/dist/style.css';
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
import useStore from "@/store";
import {queryAllImagesApi} from "@/api/storage";
import {createAlbumApi, queryAllImagesApi} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import empty from "@/assets/svgs/empty.svg";
import {message} from "ant-design-vue";
const imageStore = useStore().image;
const switchValue = ref<boolean>(false);
const upload = useStore().upload;
const options = reactive({
@@ -252,6 +278,7 @@ const options = reactive({
const images = ref<any[]>([]);
const loading = ref<boolean>(false);
const router = useRouter();
/**
* 获取所有图片
@@ -259,7 +286,7 @@ const loading = ref<boolean>(false);
async function getAllImages(type: string) {
images.value = [];
loading.value = true;
const res: any = await queryAllImagesApi(type, false, upload.storageSelected?.[0], upload.storageSelected?.[1]);
const res: any = await queryAllImagesApi(type, imageStore.switchValue, upload.storageSelected?.[0], upload.storageSelected?.[1]);
if (res && res.code === 200) {
images.value = res.data.records;
}
@@ -271,6 +298,27 @@ async function handleTabChange(activeKey: string) {
await getAllImages(activeKey);
}
const albumNameValue = ref<string>("未命名相册");
/**
* 创建相册
*/
async function createAlbumSubmit() {
if (albumNameValue.value.trim() === "") {
message.error("相册名称不能为空");
return;
}
const res: any = await createAlbumApi(albumNameValue.value);
if (res && res.code === 200) {
router.push({
path: "/main/album/albums/" + `${res.data.id}`,
query: {name: albumNameValue.value}
});
} else {
message.error("创建相册失败");
}
}
onMounted(() => {
getAllImages(imageStore.homeTabActiveKey);
@@ -314,12 +362,7 @@ onMounted(() => {
box-shadow: 0 0 10px 0 rgba(77, 167, 255, 0.89);
}
.empty-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
.spin-wrapper {
}
</style>

View File

@@ -10,7 +10,8 @@
<span style="font-size: 16px;font-weight: bold">
已选择 {{ props.selected.length }} 张照片
</span>
<AButton type="text" shape="default" class="photo-toolbar-btn" size="middle" @click="selectAll">
<AButton type="text" shape="default" class="photo-toolbar-btn" size="middle" @click="selectAll"
:disabled="isAllSelected">
全选
</AButton>
</div>
@@ -76,7 +77,14 @@ const deleteImages = async () => {
}
};
const isAllSelected = computed(() => {
// 确保 props.imageList 是一个数组
const imageList = props.imageList || [];
return props.selected.length === imageList.flatMap((record: ImageRecord) => record.list).length;
});
onBeforeUnmount(() => {
imageStore.selected = [];
});
</script>
<style scoped lang="scss">
.photo-toolbar-header {

View File

@@ -2,8 +2,12 @@
<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" placeholder="选择上传的相册">
<ASelect size="middle" style="width: 150px" placeholder="选择上传的相册"
:options="albumList"
v-model:value="upload.albumSelected"
:allow-clear="true"
:field-names="{label: 'name', value: 'id'}"
>
</ASelect>
</AFlex>
</template>
@@ -61,7 +65,7 @@ import {animePredictImagePro} from "@/utils/tfjs/anime_classifier_pro.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/storage";
import {albumListApi, uploadFile} from "@/api/storage";
import imageCompression from "browser-image-compression";
import exifr from 'exifr';
import isScreenshot from "@/utils/imageUtils/isScreenshot.ts";
@@ -232,6 +236,7 @@ async function customUploadRequest(file: any) {
provider: upload.storageSelected?.[0],
bucket: upload.storageSelected?.[1],
fileType: file.file.type,
albumId: upload.albumSelected ? upload.albumSelected : 0,
...upload.predictResult,
}));
watch(
@@ -311,6 +316,18 @@ async function extractGPSExifData(file) {
return null;
}
const albumList = ref<any[]>([]);
async function getAlbumList(type: number = 0, sort: boolean = true) {
const res: any = await albumListApi(type, sort);
if (res && res.code === 200) {
albumList.value = res.data.albums;
}
}
onMounted(() => {
getAlbumList();
});
</script>
<style lang="less" scoped>

View File

@@ -7,12 +7,27 @@
</template>
上传照片
</AButton>
<AButton type="default" shape="round" size="middle">
<template #icon>
<PlusSquareOutlined/>
<APopover placement="right" trigger="click">
<AButton type="default" shape="round" size="middle">
<template #icon>
<PlusSquareOutlined/>
</template>
创建相册
</AButton>
<template #content>
<span style="color: #999; font-size: 15px;">创建相册</span>
<AInput placeholder="请填写相册名称" class="phoalbum-create-input"
style="margin-top: 15px"
v-model:value="albumNameValue" size="large">
<template #suffix>
<AButton type="text" @click.prevent size="middle" style="color: #0e87cc" @click="createAlbumSubmit">
确认
</AButton>
</template>
</AInput>
</template>
创建相册
</AButton>
</APopover>
</div>
<image-toolbar :selected="imageStore.selected" :image-list="images"/>
<div class="photo-list">
@@ -31,20 +46,29 @@
:iconSize="20"
:showSelectedEffect="true"
:value="item.id">
<AImage :src="item.url"
<AImage :src="item.thumbnail"
:alt="item.file_name"
:key="index"
style="height: 200px"
:previewMask="false"
loading="lazy"/>
:preview="{
src: item.url,
}"
loading="lazy">
<template #previewMask>
</template>
</AImage>
</CheckCard>
</template>
</Vue3JustifiedLayout>
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{
height: '100%',
width: '100%',
}">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
@@ -62,9 +86,11 @@ import Vue3JustifiedLayout from "vue3-justified-layout";
import 'vue3-justified-layout/dist/style.css';
import useStore from "@/store";
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
import {queryRecentImagesApi} from "@/api/storage";
import {createAlbumApi, queryRecentImagesApi} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import empty from "@/assets/svgs/empty.svg";
import {message} from "ant-design-vue";
import router from "@/router/router.ts";
const imageStore = useStore().image;
const upload = useStore().upload;
@@ -74,12 +100,33 @@ const options = reactive({
});
const getRecentImages = async () => {
const res: any = await queryRecentImagesApi();
const res: any = await queryRecentImagesApi(upload.storageSelected?.[0], upload.storageSelected?.[1]);
if (res && res.code === 200) {
images.value = res.data.records;
}
};
const albumNameValue = ref<string>("未命名相册");
/**
* 创建相册
*/
async function createAlbumSubmit() {
if (albumNameValue.value.trim() === "") {
message.error("相册名称不能为空");
return;
}
const res: any = await createAlbumApi(albumNameValue.value);
if (res && res.code === 200) {
router.push({
path: "/main/album/albums/" + `${res.data.id}`,
query: {name: albumNameValue.value}
});
} else {
message.error("创建相册失败");
}
}
onMounted(() => {
getRecentImages();
});

View File

@@ -38,8 +38,8 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<div v-else class="empty-content">
<AEmpty :image="empty" :image-style="{ width: '100%', height: '100%' }">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧

View File

@@ -39,7 +39,8 @@
:status="`active`"
/>
</template>
<AButton @click.stop type="default" size="large" shape="round" style="width: 70%">
<AButton type="default" size="large" shape="round" style="width: 70%"
@click.stop="initWebSocket">
<template #icon>
<QrcodeOutlined/>
</template>
@@ -429,9 +430,10 @@ const wsOptions = {
protocols: [user.token.accessToken],
};
onMounted(() => {
window.addEventListener("resize", updateQrcodeSize);
/**
* 初始化 WebSocket
*/
function initWebSocket() {
websocket.initialize(wsOptions);
websocket.on("message", async (res: any) => {
if (res && res.code === 200) {
@@ -439,6 +441,10 @@ onMounted(() => {
console.log(data);
}
});
}
onMounted(() => {
window.addEventListener("resize", updateQrcodeSize);
});
onBeforeUnmount(() => {
websocket.close(false);

View File

@@ -10,7 +10,9 @@
<div class="share-sidebar-body-top">
<AAvatar :size="coverImageSize" shape="square">
<template #icon>
<AImage width="100%" height="100%" :src="`data:image/png;base64,`+shareInfo.cover_image" :preview="false">
<AImage width="100%" height="100%"
:src="shareInfo.cover_image?`data:image/png;base64,`+shareInfo.cover_image:default_cover"
:preview="false">
</AImage>
</template>
</AAvatar>
@@ -41,6 +43,7 @@
<script setup lang="ts">
import {queryShareInfoApi} from "@/api/share";
import default_cover from "@/assets/images/default-cover.png";
const coverImageSize = ref<number>(130);
const shareInfo = ref<any>();

View File

@@ -10,12 +10,12 @@
<div class="share-content-header">
<AButton type="link" size="large" class="share-content-header-button">图片列表</AButton>
</div>
<div class="share-content-verify" v-if="images.length <= 0">
<div class="share-content-verify" v-if="images && images.length <= 0">
<AInputPassword size="large" placeholder="请输入访问密码" style="width: 20%" @pressEnter="getShareImages"/>
<p style="font-size: 12px;color: #999;">回车后可查看图片列表</p>
</div>
<ASpin :spinning="loading" size="large">
<div v-if="images.length !== 0">
<div v-if="images && images.length !== 0">
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="images" :options="options">
<template #default="{ item }">

View File

@@ -151,7 +151,7 @@ const updateQrcodeSize = () => {
};
function generateQrCodeUrl(): string {
return import.meta.env.VITE_APP_WEB_URL + "/upscale/app?user_id=" + user.user.uid + "&token=" + user.token.accessToken;
return import.meta.env.VITE_APP_WEB_URL + "/main/upscale/phone/app?user_id=" + user.user.uid + "&token=" + user.token.accessToken;
}
/**

View File

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