add apis

This commit is contained in:
2025-02-22 23:41:22 +08:00
parent 2063a99c83
commit c7288b2cb4
34 changed files with 1170 additions and 328 deletions

26
components.d.ts vendored
View File

@@ -31,33 +31,25 @@ 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']
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']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
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']
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
ASpace: typeof import('ant-design-vue/es')['Space']
ASpin: typeof import('ant-design-vue/es')['Spin']
ASwitch: typeof import('ant-design-vue/es')['Switch']
ATable: typeof import('ant-design-vue/es')['Table']
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']
@@ -67,28 +59,25 @@ declare module 'vue' {
Card3D: typeof import('./src/components/Card3D/Card3D.vue')['default']
CheckCard: typeof import('./src/components/CheckCard/CheckCard.vue')['default']
CheckCircleOutlined: typeof import('@ant-design/icons-vue')['CheckCircleOutlined']
CloseCircleOutlined: typeof import('@ant-design/icons-vue')['CloseCircleOutlined']
CloseOutlined: typeof import('@ant-design/icons-vue')['CloseOutlined']
Clouds: typeof import('./src/components/Clouds/Clouds.vue')['default']
CloudUploadOutlined: typeof import('@ant-design/icons-vue')['CloudUploadOutlined']
CommentInput: typeof import('./src/components/CommentReply/src/CommentInput/CommentInput.vue')['default']
CommentList: typeof import('./src/components/CommentReply/src/CommentList/CommentList.vue')['default']
CommentReply: typeof import('./src/components/CommentReply/index.vue')['default']
CompareImage: typeof import('./src/views/Upscale/CompareImage.vue')['default']
CopyOutlined: typeof import('@ant-design/icons-vue')['CopyOutlined']
DeleteOutlined: typeof import('@ant-design/icons-vue')['DeleteOutlined']
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']
EyeInvisibleOutlined: typeof import('@ant-design/icons-vue')['EyeInvisibleOutlined']
EyeOutlined: typeof import('@ant-design/icons-vue')['EyeOutlined']
FileImageOutlined: typeof import('@ant-design/icons-vue')['FileImageOutlined']
Folder: typeof import('./src/components/Folder/Folder.vue')['default']
ForgetPage: typeof import('./src/views/Forget/ForgetPage.vue')['default']
GradientText: typeof import('./src/components/MyUI/GradientText/GradientText.vue')['default']
ImageShare: typeof import('./src/views/ImageShare/ImageShare.vue')['default']
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']
InboxOutlined: typeof import('@ant-design/icons-vue')['InboxOutlined']
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']
@@ -110,12 +99,14 @@ declare module 'vue' {
Phoalbum: typeof import('./src/views/Album/Phoalbum/Phoalbum.vue')['default']
PhoalbumDetail: typeof import('./src/views/Album/Phoalbum/PhoalbumDetail.vue')['default']
PhoalbumList: typeof import('./src/views/Album/Phoalbum/PhoalbumList.vue')['default']
PhoneUpload: typeof import('./src/views/Phone/UpscalePhoneUpload/PhoneUpload.vue')['default']
PhoneOutlined: typeof import('@ant-design/icons-vue')['PhoneOutlined']
PhotoStack: typeof import('./src/components/PhotoStack/PhotoStack.vue')['default']
PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined']
PlusSquareOutlined: typeof import('@ant-design/icons-vue')['PlusSquareOutlined']
QrcodeOutlined: typeof import('@ant-design/icons-vue')['QrcodeOutlined']
QRLogin: typeof import('./src/views/QRLogin/QRLogin.vue')['default']
QRLoginFooter: typeof import('./src/views/QRLogin/QRLoginFooter.vue')['default']
QuestionCircleOutlined: typeof import('@ant-design/icons-vue')['QuestionCircleOutlined']
Rate: typeof import('./src/components/MyUI/Rate/Rate.vue')['default']
RecentUpload: typeof import('./src/views/Photograph/RecentUpload/RecentUpload.vue')['default']
RecyclingBin: typeof import('./src/views/RecyclingBin/RecyclingBin.vue')['default']
@@ -126,10 +117,11 @@ declare module 'vue' {
RouterView: typeof import('vue-router')['RouterView']
SafetyOutlined: typeof import('@ant-design/icons-vue')['SafetyOutlined']
SearchOutlined: typeof import('@ant-design/icons-vue')['SearchOutlined']
SendOutlined: typeof import('@ant-design/icons-vue')['SendOutlined']
ShareAltOutlined: typeof import('@ant-design/icons-vue')['ShareAltOutlined']
SharePhoneUpload: typeof import('./src/views/Phone/SharePhoneUpload/SharePhoneUpload.vue')['default']
ShareUpload: typeof import('./src/views/ImageShare/ShareUpload.vue')['default']
ShareSidebar: typeof import('./src/views/Share/ShareViewList/ShareSidebar.vue')['default']
ShareUpload: typeof import('./src/views/Share/ImageShare/ShareUpload.vue')['default']
ShareViewList: typeof import('./src/views/Share/ShareViewList/index.vue')['default']
Spin: typeof import('./src/components/MyUI/Spin/Spin.vue')['default']
StarButton: typeof import('./src/components/StarButton/StarButton.vue')['default']
TabletOutlined: typeof import('@ant-design/icons-vue')['TabletOutlined']
@@ -138,11 +130,11 @@ declare module 'vue' {
ThingAlbumList: typeof import('./src/views/Album/ThingAlbum/ThingAlbumList.vue')['default']
Tooltip: typeof import('./src/components/MyUI/Tooltip/Tooltip.vue')['default']
UploadImage: typeof import('./src/views/Upscale/UploadImage.vue')['default']
UploadOutlined: typeof import('@ant-design/icons-vue')['UploadOutlined']
Upscale: typeof import('./src/views/Upscale/index.vue')['default']
UpscalePhoneUpload: typeof import('./src/views/Phone/UpscalePhoneUpload/UpscalePhoneUpload.vue')['default']
UserInfoCard: typeof import('./src/components/CommentReply/src/UserInfoCard/UserInfoCard.vue')['default']
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
VueCompareImage: typeof import('./src/components/VueCompareImage/VueCompareImage.vue')['default']
WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined']
}
}

View File

@@ -10,7 +10,7 @@
"docker-build": "docker build -t schisandra/schisandra-cloud-album-front ."
},
"dependencies": {
"@alova/adapter-axios": "^2.0.12",
"@alova/adapter-axios": "^2.0.13",
"@ant-design/icons-vue": "^7.0.1",
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
"@mediapipe/face_detection": "^0.4.1646425229",
@@ -27,10 +27,10 @@
"@tensorflow/tfjs-backend-webgpu": "^4.22.0",
"@tensorflow/tfjs-converter": "^4.22.0",
"@tensorflow/tfjs-core": "^4.22.0",
"@types/animejs": "^3.1.12",
"@types/animejs": "^3.1.13",
"@types/crypto-js": "^4.2.2",
"@types/json-stringify-safe": "^5.0.3",
"@types/node": "^22.13.4",
"@types/node": "^22.13.5",
"@types/nprogress": "^0.2.3",
"@vladmandic/face-api": "^1.7.15",
"@vuepic/vue-datepicker": "^11.0.1",
@@ -44,7 +44,7 @@
"buffer": "^6.0.3",
"crypto-js": "^4.2.0",
"echarts": "^5.6.0",
"eslint": "9.20.1",
"eslint": "9.21.0",
"exifr": "^7.1.3",
"go-captcha-vue": "^2.0.6",
"gsap": "^3.12.7",
@@ -70,13 +70,13 @@
"vue-i18n": "^11.1.1",
"vue-router": "^4.5.0",
"vue3-justified-layout": "^0.0.6",
"ws": "^8.18.0"
"ws": "^8.18.1"
},
"devDependencies": {
"@eslint/js": "^9.20.0",
"@eslint/js": "^9.21.0",
"@vitejs/plugin-vue": "^5.2.1",
"eslint-plugin-vue": "^9.32.0",
"globals": "^15.15.0",
"globals": "^16.0.0",
"sass": "^1.85.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.1",

View File

@@ -14,12 +14,12 @@ export const shareImageUploadApi = (formData) => {
};
/**
* 查询分享图片列表
* @param share_code
* @param invite_code
* @param access_password
*/
export const queryShareImageApi = (share_code: string, access_password: string) => {
export const queryShareImageApi = (invite_code: string, access_password: string) => {
return service.Post('/api/auth/share/image/list', {
share_code: share_code,
invite_code: invite_code,
access_password: access_password,
}, {
meta: {
@@ -41,3 +41,36 @@ export const queryShareRecordListApi = (dataRequest: string[]) => {
},
});
};
/**
* 查询分享信息
* @param invite_code
*/
export const queryShareInfoApi = (invite_code: string) => {
return service.Post('/api/auth/share/info', {
invite_code: invite_code,
}, {
cacheFor: {
expire: 60, //60 * 60 * 24 * 7
mode: "restore",
},
meta: {
ignoreToken: false,
signature: false,
},
});
};
/**
* 查询分享概览
*/
export const queryShareOverviewApi = () => {
return service.Post('/api/auth/share/overview', {}, {
cacheFor: {
expire: 60, //60 * 60 * 24 * 7
mode: "restore",
},
meta: {
ignoreToken: false,
signature: false,
},
});
};

View File

@@ -30,7 +30,7 @@ export const getFaceSamplesList = (type: number) => {
ignoreToken: false,
signature: false,
},
hitSource: ["modify-face-sample-name", "modify-face-sample-type"],
hitSource: ["modify-face-sample-name", "modify-face-sample-type", "delete-images"],
});
};
/**
@@ -146,6 +146,8 @@ export const queryAlbumDetailListApi = (id: number, provider: string, bucket: st
ignoreToken: false,
signature: false,
},
name: "album-detail-list",
hitSource: ["upload-file", "delete-images"],
});
};
@@ -203,7 +205,7 @@ export const queryAllImagesApi = (type: string, sort: boolean, provider: string,
ignoreToken: false,
signature: false,
},
hitSource: ["upload-file"],
hitSource: ["upload-file", "delete-images"],
});
};
@@ -221,7 +223,7 @@ export const queryRecentImagesApi = () => {
ignoreToken: false,
signature: false,
},
hitSource: ["upload-file"],
hitSource: ["upload-file", "delete-images"],
});
};
/**
@@ -240,7 +242,7 @@ export const queryLocationAlbumApi = (provider: string, bucket: string) => {
ignoreToken: false,
signature: false,
},
hitSource: ["upload-file"],
hitSource: ["upload-file", "delete-images"],
});
};
/**
@@ -263,7 +265,7 @@ export const queryLocationDetailListApi = (id: number, provider: string, bucket:
ignoreToken: false,
signature: false,
},
hitSource: ["upload-file"],
hitSource: ["upload-file", "delete-images"],
});
};
@@ -283,7 +285,7 @@ export const queryThingAlbumApi = (provider: string, bucket: string) => {
ignoreToken: false,
signature: false,
},
hitSource: ["upload-file"],
hitSource: ["upload-file", "delete-images"],
});
};
@@ -307,7 +309,7 @@ export const queryThingDetailListApi = (tag_name: string, provider: string, buck
ignoreToken: false,
signature: false,
},
hitSource: ["upload-file"],
hitSource: ["upload-file", "delete-images"],
});
};
@@ -327,6 +329,7 @@ export const getSingleImageApi = (id: number) => {
ignoreToken: false,
signature: false,
},
name: "single-image-url",
});
};
/**
@@ -342,7 +345,68 @@ export const getStorageConfigListApi = () => {
ignoreToken: false,
signature: false,
},
name: "storage-config-list",
});
};
/**
* 查询删除记录
* @param provider
* @param bucket
*/
export const getDeletedRecordApi = (provider: string, bucket: string) => {
return service.Post('/api/auth/storage/delete/record', {
provider: provider,
bucket: bucket,
}, {
cacheFor: {
expire: 60 * 60 * 24 * 7,
mode: "restore",
},
meta: {
ignoreToken: false,
signature: false,
},
name: "deleted-record",
hitSource: ["upload-file", "delete-images"],
});
};
/**
* 删除照片
* @param ids
* @param provider
* @param bucket
*/
export const deletedImagesApi = (ids: number[], provider: string, bucket: string) => {
return service.Post('/api/auth/storage/image/delete', {
ids: ids,
provider: provider,
bucket: bucket,
}, {
meta: {
ignoreToken: false,
signature: false,
},
name: "delete-images",
});
};
/**
* 获取存储桶容量
* @param provider
* @param bucket
*/
export const getBucketCapacityApi = (provider: string, bucket: string) => {
return service.Post('/api/auth/storage/bucket/capacity', {
provider: provider,
bucket: bucket,
}, {
cacheFor: {
expire: 60 * 60 * 24,
mode: "restore",
},
meta: {
ignoreToken: false,
signature: false,
},
name: "delete-images",
});
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -74,11 +74,15 @@
<div class="sidebar-bottom">
<ACard :bordered="false" style="box-shadow: none">
<Folder/>
<span class="sidebar-folder-text-title">30% In-used</span>
<AProgress :percent="30" size="small" :showInfo="false" style="width: 150px"/>
<span class="sidebar-folder-text-title">{{ bucketCapacityInfo?.percentage }}% In-used
<ATooltip placement="right" title="每天同步一次" :color="'rgba(191,189,189,0.83)'">
<QuestionCircleOutlined/>
</ATooltip>
</span>
<AProgress :percent="bucketCapacityInfo?.percentage" size="small" :showInfo="false" style="width: 150px"/>
<AFlex :vertical="false" align="center" justify="space-between" style="width: 150px">
<span class="sidebar-folder-info-text1">500G</span>
<span class="sidebar-folder-info-text2">500G</span>
<span class="sidebar-folder-info-text1">{{ bucketCapacityInfo?.used }}</span>
<span class="sidebar-folder-info-text2">{{ bucketCapacityInfo?.capacity }}</span>
</AFlex>
</ACard>
</div>
@@ -97,11 +101,13 @@ import Folder from "@/components/Folder/Folder.vue";
import ai from '@/assets/svgs/ai.svg';
import share from '@/assets/svgs/share.svg';
import useStore from "@/store";
import {getBucketCapacityApi} from "@/api/storage";
const {t} = useI18n();
const router = useRouter();
const route = useRoute();
const menu = useStore().menu;
const upload = useStore().upload;
/**
* handle click event of menu item
@@ -117,6 +123,14 @@ const menuCSSStyle: any = reactive({
alignItems: 'center',
});
const bucketCapacityInfo = ref<any>();
async function getBucketCapacity() {
const res: any = await getBucketCapacityApi(upload.storageSelected?.[0], upload.storageSelected?.[1]);
if (res && res.code === 200) {
bucketCapacityInfo.value = res.data;
}
}
watch(
() => route.path,
@@ -135,9 +149,10 @@ function scrollToSelectedMenuItem() {
});
}
onMounted(() => {
onMounted(async () => {
menu.currentMenu = route.path.replace('/main', '').split('/').slice(0, 3).join('/').substring(1);
scrollToSelectedMenuItem();
await getBucketCapacity();
});
router.afterEach((_to) => {
menu.currentMenu = route.path.replace('/main', '').split('/').slice(0, 3).join('/').substring(1);

View File

@@ -1,12 +1,14 @@
import photo from "@/router/modules/photos.ts";
import albums from "@/router/modules/albums.ts";
import recycling_bin from "@/router/modules/recycling_bin.ts";
import share from "@/router/modules/share.ts";
import upscale from "@/router/modules/upscale.ts";
export default [
{
path: '/main',
name: 'main',
redirect: '/main/photos',
redirect: '/main/photo/all',
component: () => import('@/views/Main/MainPage.vue'),
meta: {
requiresAuth: true,
@@ -16,25 +18,18 @@ export default [
...photo,
...albums,
...recycling_bin,
{
path: '/main/photo/upscale',
name: 'upscale',
component: () => import('@/views/Upscale/index.vue'),
meta: {
requiresAuth: true,
title: '图像修复'
},
},
{
path: '/main/photo/share',
name: 'share',
component: () => import('@/views/ImageShare/ImageShare.vue'),
meta: {
requiresAuth: true,
title: '快传'
}
}
...share,
...upscale,
]
}, {
path: '/main/share/list/:id',
name: 'share-list',
component: () => import('@/views/Share/ShareViewList/index.vue'),
meta: {
requiresAuth: true,
title: '分享列表'
},
}
];

View File

@@ -0,0 +1,11 @@
export default [
{
path: '/main/photo/share',
name: 'share',
component: () => import('@/views/Share/ImageShare/ImageShare.vue'),
meta: {
requiresAuth: true,
title: '快传'
}
}
];

View File

@@ -0,0 +1,11 @@
export default [
{
path: '/main/photo/upscale',
name: 'upscale',
component: () => import('@/views/Upscale/index.vue'),
meta: {
requiresAuth: true,
title: '图像修复'
},
},
];

View File

@@ -6,6 +6,7 @@ import {useWebSocketStore} from "@/store/modules/websocketStore.ts";
import {useUpscaleStore} from "@/store/modules/upscaleStore.ts";
import {useMenuStore} from "@/store/modules/menuStore.ts";
import {useUploadStore} from "@/store/modules/uploadStore.ts";
import {useImageStore} from "@/store/modules/imageStore.ts";
export default function useStore() {
return {
@@ -17,5 +18,6 @@ export default function useStore() {
upscale: useUpscaleStore(),
menu: useMenuStore(),
upload: useUploadStore(),
image: useImageStore(),
};
}

View File

@@ -0,0 +1,52 @@
import {ImageList} from "@/types/image";
export const useImageStore = defineStore(
'image',
() => {
const selected = ref<number[]>([]);
const tabActiveKey = ref<string>("-1");
const tabMap = reactive({
"-1": "全部相册",
"0": "我的相册",
"1": "我的分享",
"2": "我的收藏",
});
const homeTabActiveKey = ref<string>("all");
const homeTabMap = reactive({
"all": "全部",
"video": "视频",
"gif": "动图",
"screenshot": "截图",
});
/**
* 统计图片总数
* @param imageList 图片列表数据
* @returns 图片总数
*/
function countTotalImages(imageList: ImageList): number {
if (!imageList) {
return 0;
}
return imageList.reduce((total, record) => total + record.list.length, 0);
}
return {
selected,
tabActiveKey,
tabMap,
homeTabMap,
homeTabActiveKey,
countTotalImages
};
},
{
// 开启数据持久化
persistedState: {
persist: true,
storage: localStorage,
key: 'image',
includePaths: ["tabActiveKey", "tabMap", "homeTabActiveKey", "homeTabMap"],
}
}
);

View File

@@ -84,7 +84,13 @@ export const useAuthStore = defineStore(
message.success(t('login.loginSuccess'));
window.removeEventListener("message", messageHandler);
setTimeout(() => {
router.push('/main/photo/all');
const currentUrl = new URL(window.location.href);
const redirect = currentUrl.searchParams.get('redirect');
if (redirect) {
router.push(redirect);
} else {
router.push('/main/photo/all');
}
}, 1000);
} else {
message.error(t('login.loginError'));

16
src/types/image.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
export interface Image {
id: number;
file_name: string;
url: string;
width: number;
height: number;
created_at: string;
thumbnail: string;
}
export interface ImageRecord {
date: string;
list: Image[];
}
export type ImageList = ImageRecord[];

View File

@@ -4,14 +4,15 @@
<div class="location-detail-content-nav">
<AButton size="large" type="text" class="location-detail-content-nav-title" @click="goBack">地点</AButton>
<span class="location-detail-content-nav-separator"> > </span>
<span class="location-detail-content-nav-name">乌鲁木齐</span>
<span class="location-detail-content-nav-name">{{ route.query.name }}</span>
</div>
</div>
<ImageToolbar :selected="imageStore.selected" :imageList="albumList"/>
<div class="location-album-detail-info">
<span style="font-size: 14px;color: #999999">12张照片</span>
<span style="font-size: 14px;color: #999999">{{ imageStore.countTotalImages(albumList) }}张照片</span>
</div>
<div class="location-album-detail-list">
<div style="width:100%;height:100%;">
<div style="width:100%;height:100%;" v-if="albumList.length != 0">
<div v-for="(itemList, index) in albumList" :key="index">
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
<AImagePreviewGroup>
@@ -21,7 +22,7 @@
class="photo-item"
margin="0"
border-radius="0"
v-model="selected"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
@@ -41,6 +42,15 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</div>
</div>
@@ -50,8 +60,10 @@ import Vue3JustifiedLayout from "vue3-justified-layout";
import 'vue3-justified-layout/dist/style.css';
import {queryLocationDetailListApi} from "@/api/storage";
import useStore from "@/store";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import empty from "@/assets/svgs/empty.svg";
const selected = ref<(string | number)[]>([]);
const imageStore = useStore().image;
const albumList = ref<any[]>([]);
const route = useRoute();

View File

@@ -8,7 +8,7 @@
<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">
<div class="location-album-container" @click="handleClick(itemList.id)"
<div class="location-album-container" @click="handleClick(itemList.id,itemList.city)"
v-for="(itemList, indexItem) in item.list" :key="indexItem">
<img class="background-image" :src="itemList.cover_image" :alt="itemList.city"/>
<div class="overlay">
@@ -30,8 +30,8 @@ const route = useRoute();
const router = useRouter();
const upload = useStore().upload;
function handleClick(id: number) {
router.push({path: route.path + `/${id}`});
function handleClick(id: number,name: string) {
router.push({path: route.path + `/${id}`, query: {name: name}});
}
const locationAlbums = ref<any[]>([]);

View File

@@ -11,16 +11,16 @@
</div>
<div class="people-album-detail-toolbar">
<AAvatar shape="circle" size="default"></AAvatar>
<span style="font-size: 14px;color: #333333">张皓扬</span>
<span style="font-size: 14px;color: #333333">{{ route.query.name }}</span>
</div>
</div>
<ImageToolbar :selected="selected"/>
<ImageToolbar :selected="imageStore.selected" :imageList="images"/>
<div class="people-album-detail-info">
<span style="font-size: 14px;color: #999999">12张照片</span>
<span style="font-size: 14px;color: #999999">{{ imageStore.countTotalImages(images) }}张照片</span>
</div>
<div class="people-album-detail-list">
<div style="width:100%;height:100%;">
<div v-for="(itemList, index) in albumList" :key="index">
<div style="width:100%;height:100%;" v-if="images.length !== 0">
<div v-for="(itemList, index) in images" :key="index">
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options">
@@ -29,7 +29,7 @@
class="photo-item"
margin="0"
border-radius="0"
v-model="selected"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
@@ -49,6 +49,15 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</div>
</div>
</template>
@@ -58,10 +67,11 @@ import 'vue3-justified-layout/dist/style.css';
import {getFaceSamplesDetailList} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import useStore from "@/store";
import empty from "@/assets/svgs/empty.svg";
const selected = ref<(string | number)[]>([]);
const albumList = ref<any[]>([]);
const imageStore = useStore().image;
const images = ref<any[]>([]);
const route = useRoute();
const router = useRouter();
@@ -74,7 +84,7 @@ const options = reactive({
async function getAlbumList(id: number) {
const res: any = await getFaceSamplesDetailList(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
if (res && res.code === 200) {
albumList.value = res.data.records;
images.value = res.data.records;
}
}

View File

@@ -3,11 +3,11 @@
<div class="people-album-header">
<ADropdown trigger="click">
<AButton type="text" size="large" class="people-album-button">
{{ selecetedKey === '0' ? '人 物' : '已隐藏' }}
{{ selectedKey === '0' ? '人 物' : '已隐藏' }}
<DownOutlined class="people-album-icon"/>
</AButton>
<template #overlay>
<AMenu selectable :selectedKeys="[selecetedKey]" @select="handleSelect">
<AMenu selectable :selectedKeys="[selectedKey]" @select="handleSelect">
<AMenuItem key="0"> </AMenuItem>
<AMenuItem key="1">已隐藏</AMenuItem>
</AMenu>
@@ -33,7 +33,7 @@
<div class="people-album-toolbar-right">
<AButton type="text" shape="default" size="middle" class="people-album-toolbar-btn"
:disabled="selected.length !== 2" v-if="selecetedKey === '0'">
:disabled="selected.length !== 2" v-if="selectedKey === '0'">
<template #icon>
<BlockOutlined class="people-album-toolbar-icon"/>
</template>
@@ -44,7 +44,7 @@
<template #icon>
<EyeInvisibleOutlined class="people-album-toolbar-icon"/>
</template>
{{ selecetedKey === '0' ? '隐藏人物' : '取消隐藏' }}
{{ selectedKey === '0' ? '隐藏人物' : '取消隐藏' }}
</AButton>
</div>
</div>
@@ -55,7 +55,7 @@
<CheckCard
v-for="(item, index) in faceList"
:key="index"
@click="handleClick(item.id)"
@click="handleClick(item.id, item.face_name)"
class="photo-item"
margin="0"
border-radius="0"
@@ -125,7 +125,7 @@ import {getFaceSamplesList, modifyFaceSampleName, modifyFaceTypeBatch} from "@/a
const faceList = ref<any[]>([]);
const addNameInputValue = ref<string>('');
const selecetedKey = ref<string>('0');
const selectedKey = ref<string>('0');
const loading = ref<boolean>(false);
const selected = ref<any[]>([]);
@@ -180,7 +180,7 @@ async function modifyFaceName(id: number, index: number) {
* @param key
*/
function handleSelect({key}) {
selecetedKey.value = key;
selectedKey.value = key;
getFaceList(parseInt(key));
}
@@ -203,7 +203,7 @@ function cancelSelectPeople() {
*/
async function hiddenFace() {
if (selected.value.length === 0) return;
const res: any = await modifyFaceTypeBatch(selected.value, selecetedKey.value === '0' ? 1 : 0);
const res: any = await modifyFaceTypeBatch(selected.value, selectedKey.value === '0' ? 1 : 0);
if (res && res.code === 200) {
await getFaceList();
selected.value = [];
@@ -216,9 +216,10 @@ const router = useRouter();
/**
* 点击人物跳转到详情页
* @param id
* @param name
*/
function handleClick(id: number) {
router.push({path: route.path + `/${id}`});
function handleClick(id: number, name: string | null) {
router.push({path: route.path + `/${id}`, query: {name: name}});
}
onMounted(() => {

View File

@@ -26,17 +26,18 @@
下载相册
</AButton>
</div>
<ImageToolbar :selected="selected"/>
<ImageToolbar :selected="imageStore.selected" :image-list="albumList"/>
<div class="phoalbum-detail-content">
<div class="phoalbum-detail-content-nav">
<div class="phoalbum-detail-content-nav-left">
<AButton type="text" size="large" class="phoalbum-detail-content-nav-title" @click="goBack()">全部相册
<AButton type="text" size="large" class="phoalbum-detail-content-nav-title" @click="goBack()">
{{ imageStore.tabMap[imageStore.tabActiveKey] }}
</AButton>
<span class="phoalbum-detail-content-nav-separator"> > </span>
<span class="phoalbum-detail-content-nav-name">网盘导入</span>
<span class="phoalbum-detail-content-nav-name">{{ route.query.name }}</span>
</div>
<div class="phoalbum-detail-content-nav-right">
<span class="phoalbum-detail-content-nav-date">15张照片,1个视频创建于2025年1月1日</span>
<span class="phoalbum-detail-content-nav-date"> {{ imageStore.countTotalImages(albumList) }} 张照片</span>
</div>
</div>
<div class="phoalbum-detail-content-desc">
@@ -44,7 +45,7 @@
<span>相册描述</span>
</div>
<div class="phoalbum-detail-content-list">
<div style="width:100%;height:100%;">
<div style="width:100%;height:100%;" v-if="albumList.length !== 0">
<div v-for="(itemList, index) in albumList" :key="index">
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
<AImagePreviewGroup>
@@ -54,7 +55,7 @@
class="photo-item"
margin="0"
border-radius="0"
v-model="selected"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
@@ -74,6 +75,15 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</div>
</div>
</div>
@@ -84,9 +94,10 @@ import 'vue3-justified-layout/dist/style.css';
import {queryAlbumDetailListApi} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import useStore from "@/store";
import empty from "@/assets/svgs/empty.svg";
const selected = ref<(string | number)[]>([]);
const imageStore = useStore().image;
const albumList = ref<any[]>([]);
const route = useRoute();

View File

@@ -31,7 +31,7 @@
排序
</AButton>
<template #overlay>
<AMenu selectable :selectedKeys="[selecetedKey]" @select="handleSelect">
<AMenu selectable :selectedKeys="[selectedKey]" @select="handleSelect">
<AMenuItem :key="true">按时间排序</AMenuItem>
<AMenuItem :key="false">按名称排序</AMenuItem>
</AMenu>
@@ -49,18 +49,20 @@
</div>
<div class="phoalbum-content">
<ATabs size="small" :tabBarGutter="50" type="line" tabPosition="top" :tabBarStyle="{position:'unset'}"
style="width: 100%;" @change="handleTabChange">
style="width: 100%;"
v-model:activeKey="imageStore.tabActiveKey"
@change="handleTabChange">
<template #rightExtra>
<span
style="color: #999; font-size: 12px;">已全部加载 {{ albumList ? albumList.length : 0 }} 个相册</span>
</template>
<ATabPane key="-1" tab="全部相册">
<ATabPane key="-1" :tab="imageStore.tabMap[-1]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@click.prevent="handleClick(album.id)"
@click.prevent="handleClick(album.id,album.name)"
@mouseover="isHovered = index"
@mouseleave="isHovered = null">
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
@@ -104,13 +106,13 @@
</div>
</ASpin>
</ATabPane>
<ATabPane key="0" tab="我的相册">
<ATabPane key="0" :tab="imageStore.tabMap[0]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@click.prevent="handleClick(album.id)"
@click.prevent="handleClick(album.id,album.name)"
@mouseover="isHovered = index"
@mouseleave="isHovered = null">
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
@@ -154,13 +156,13 @@
</div>
</ASpin>
</ATabPane>
<ATabPane key="1" tab="我的分享">
<ATabPane key="1" :tab="imageStore.tabMap[1]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@click.prevent="handleClick(album.id)"
@click.prevent="handleClick(album.id,album.name)"
@mouseover="isHovered = index"
@mouseleave="isHovered = null">
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
@@ -204,13 +206,13 @@
</div>
</ASpin>
</ATabPane>
<ATabPane key="2" tab="收藏相册">
<ATabPane key="2" :tab="imageStore.tabMap[2]">
<ASpin tip="Loading..." :spinning="loading" size="large">
<div class="phoalbum-item-container">
<div class="phoalbum-item"
v-for="(album, index) in albumList"
:key="album.id"
@click.prevent="handleClick(album.id)"
@click.prevent="handleClick(album.id,album.name)"
@mouseover="isHovered = index"
@mouseleave="isHovered = null">
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
@@ -263,18 +265,21 @@ import more from "@/assets/svgs/more.svg";
import {albumListApi, createAlbumApi, deleteAlbumApi, renameAlbumApi} from "@/api/storage";
import {message} from "ant-design-vue";
import default_cover from "@/assets/images/default-cover.png";
import useStore from "@/store";
const isHovered = ref<number | null>(null);
const albumNameValue = ref<string>("未命名相册");
const albumRenameValue = ref<string>("");
const selecetedKey = ref<boolean>(true);
const selectedKey = ref<boolean>(true);
const albumList = ref<any[]>([]);
const loading = ref<boolean>(false);
const imageStore = useStore().image;
/**
* 创建相册
*/
@@ -286,7 +291,7 @@ async function createAlbumSubmit() {
const res: any = await createAlbumApi(albumNameValue.value);
if (res && res.code === 200) {
albumNameValue.value = "未命名相册";
await getAlbumList(0, selecetedKey.value);
await getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
} else {
message.error("创建相册失败");
}
@@ -297,8 +302,8 @@ async function createAlbumSubmit() {
* @param key
*/
async function handleSelect({key}) {
selecetedKey.value = key;
await getAlbumList(0, key);
selectedKey.value = key;
await getAlbumList(parseInt(imageStore.tabActiveKey), key);
}
/**
@@ -306,7 +311,8 @@ async function handleSelect({key}) {
* @param activeKey
*/
async function handleTabChange(activeKey: string) {
await getAlbumList(parseInt(activeKey), selecetedKey.value);
imageStore.tabActiveKey = activeKey;
await getAlbumList(parseInt(activeKey), selectedKey.value);
}
/**
@@ -337,7 +343,7 @@ async function renameAlbum(id: number, name: string) {
const res: any = await renameAlbumApi(id, name);
if (res && res.code === 200) {
albumRenameValue.value = "";
await getAlbumList(0, selecetedKey.value);
await getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
}
}
@@ -348,7 +354,7 @@ async function renameAlbum(id: number, name: string) {
async function deleteAlbum(id: number) {
const res: any = await deleteAlbumApi(id);
if (res && res.code === 200) {
await getAlbumList(0, selecetedKey.value);
await getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
} else {
message.error("删除相册失败");
}
@@ -360,13 +366,16 @@ const router = useRouter();
/**
* 点击相册跳转到详情页
* @param id
* @param albumName
*/
function handleClick(id: number) {
router.push({path: route.path + `/${id}`});
function handleClick(id: number, albumName: string) {
router.push({
path: route.path + `/${id}`, query: {name: albumName}
});
}
onMounted(() => {
getAlbumList(0, selecetedKey.value);
getAlbumList(parseInt(imageStore.tabActiveKey), selectedKey.value);
});
</script>

View File

@@ -2,14 +2,16 @@
<div class="thing-album-detail">
<div class="thing-album-detail-header">
<div class="thing-detail-content-nav">
<AButton size="large" type="text" class="thing-detail-content-nav-title" @click="goBack">人物</AButton>
<AButton size="large" type="text" class="thing-detail-content-nav-title" @click="goBack">
{{ getZhCategoryNameByEnName(route.query.category as string) }}
</AButton>
<span class="thing-detail-content-nav-separator"> > </span>
<span class="thing-detail-content-nav-name">人物</span>
<span class="thing-detail-content-nav-name">{{ getZhLabelNameByEnName(route.query.tag as string) }}</span>
</div>
</div>
<ImageToolbar :selected="selected"/>
<ImageToolbar :selected="imageStore.selected" :image-list="albumList"/>
<div class="thing-album-detail-info">
<span style="font-size: 14px;color: #999999">12张照片</span>
<span style="font-size: 14px;color: #999999">{{ imageStore.countTotalImages(albumList) }}张照片</span>
</div>
<div class="thing-album-detail-list">
<div style="width:100%;height:100%;">
@@ -22,7 +24,7 @@
class="photo-item"
margin="0"
border-radius="0"
v-model="selected"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
@@ -53,9 +55,10 @@ import 'vue3-justified-layout/dist/style.css';
import {queryThingDetailListApi} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import useStore from "@/store";
import {getZhCategoryNameByEnName, getZhLabelNameByEnName} from "@/constant/coco_ssd_label_category.ts";
const selected = ref<(string | number)[]>([]);
const imageStore = useStore().image;
const albumList = ref<any[]>([]);
const upload = useStore().upload;
const route = useRoute();
@@ -113,7 +116,7 @@ function goBack(): void {
justify-content: flex-start;
width: 1000%;
height: 100%;
gap: 10px;
gap: 5px;
.thing-detail-content-nav-title {
font-size: 20px;
@@ -121,6 +124,7 @@ function goBack(): void {
display: flex;
align-items: center;
justify-content: center;
padding: 7px;
}
.thing-detail-content-nav-separator {
@@ -141,13 +145,13 @@ function goBack(): void {
flex-direction: row;
align-items: center;
justify-content: flex-start;
margin-left: 30px;
margin-left: 15px;
width: 100%;
height: 22px;
}
.thing-album-detail-list {
width: 99%;
width: 100%;
height: 100%;
//margin-left: 5px;
}

View File

@@ -10,7 +10,7 @@
<span class="thing-album-title">{{ getZhCategoryNameByEnName(item.category) }}</span>
<div class="thing-album-wrapper">
<div class="thing-album-container" v-for="(tags, indexList) in item.list" :key="indexList"
@click="handleClick(tags.tag_name)">
@click="handleClick(tags.tag_name,item.category,tags.tag_name)">
<img class="background-image" :src="tags.cover_image" :alt="tags.tag_name"/>
<div class="overlay">
<span>{{ getZhLabelNameByEnName(tags.tag_name) }}</span>
@@ -46,9 +46,11 @@ const upload = useStore().upload;
/**
* 点击事件
* @param id
* @param tag
* @param category
*/
function handleClick(id: string) {
router.push({path: route.path + `/${id}`});
function handleClick(id: string, category: string, tag: string) {
router.push({path: route.path + `/${id}`, query: {category: category, tag: tag}});
}
onMounted(() => {

View File

@@ -1,100 +0,0 @@
.image-share {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
height: 100%;
gap: 20px;
.image-share-left {
height: 100%;
width: 65%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
.image-share-left-top {
width: 100%;
height: 30%;
display: flex;
flex-direction: column;
gap: 10px;
.image-share-left-title {
width: 100%;
height: 20%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.image-share-left-content {
width: 100%;
height: 80%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.image-share-left-content-item {
height: 100%;
width: 30%;
color: #fff;
overflow: auto;
.image-share-left-item-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
overflow: hidden;
}
}
}
}
.image-share-left-bottom {
width: 100%;
height: 70%;
display: flex;
flex-direction: column;
.image-share-left-bottom-title {
width: 100%;
height: 20%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.image-share-left-bottom-content {
width: 100%;
height: 80%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
.ant-card {
height: 100%;
.ant-table {
flex: 1; // 让 ATable 填满剩余空间
height: 100%;
}
}
}
}
}
}

View File

@@ -383,7 +383,13 @@ async function phoneLoginSubmit() {
message.success(t('login.loginSuccess'));
loginLoading.value = false;
setTimeout(() => {
router.push('/main/photo/all');
const currentUrl = new URL(window.location.href);
const redirect = currentUrl.searchParams.get('redirect');
if (redirect) {
router.push(redirect);
} else {
router.push('/main/photo/all');
}
}, 1000);
} else {
loginLoading.value = false;
@@ -472,7 +478,13 @@ async function checkAccountLoginCaptcha(angle: number) {
loginLoading.value = false;
showAccountRotateCaptcha.value = false;
setTimeout(() => {
router.push('/main/photo/all');
const currentUrl = new URL(window.location.href);
const redirect = currentUrl.searchParams.get('redirect');
if (redirect) {
router.push(redirect);
} else {
router.push('/main/photo/all');
}
}, 1000);
} else {
showAccountRotateCaptcha.value = false;

View File

@@ -14,57 +14,216 @@
创建相册
</AButton>
</div>
<image-toolbar :selected="selected"/>
<image-toolbar :selected="imageStore.selected" :image-list="images"/>
<div class="photo-list">
<ATabs size="small" :tabBarGutter="50" type="line" tabPosition="top" :tabBarStyle="{position:'unset'}"
@change="handleTabChange"
v-model:activeKey="imageStore.homeTabActiveKey"
style="width: 99%;">
<template #rightExtra>
<ASwitch size="small" v-model:checked="switchValue"/>
</template>
<ATabPane key="image" tab="全部">
<div style="width:100%;height:100%;">
<div v-for="(itemList, index) in images" :key="index">
<span style="margin-left: 10px;font-size: 13px">{{ itemList.date }}</span>
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options" style="line-height: 0 !important;">
<template #default="{ item }">
<CheckCard :key="index"
class="photo-item"
margin="0"
border-radius="0"
v-model="selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
:value="item.id">
<AImage :src="item.thumbnail"
:alt="item.file_name"
:key="index"
:height="200"
:preview="{
<ATabPane key="all" :tab="imageStore.homeTabMap['all']">
<ASpin size="large" :spinning="loading" :delay="500">
<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>
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
style="line-height: 0 !important;">
<template #default="{ item }">
<CheckCard :key="index"
class="photo-item"
margin="0"
border-radius="0"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
:value="item.id">
<AImage :src="item.thumbnail"
:alt="item.file_name"
:key="index"
:height="200"
:preview="{
src: item.url,
}"
loading="lazy">
<template #previewMask>
</template>
</AImage>
</CheckCard>
</template>
</Vue3JustifiedLayout>
</AImagePreviewGroup>
loading="lazy">
<template #previewMask>
</template>
</AImage>
</CheckCard>
</template>
</Vue3JustifiedLayout>
</AImagePreviewGroup>
</div>
</div>
</div>
<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;">
还没检测到任何图片快去上传吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
<ATabPane key="video" tab="视频">
<div style="width:100%;height:100%;">
</div>
<ATabPane key="video" :tab="imageStore.homeTabMap['video']">
<ASpin size="large" :spinning="loading" :delay="500">
<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>
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
style="line-height: 0 !important;">
<template #default="{ item }">
<CheckCard :key="index"
class="photo-item"
margin="0"
border-radius="0"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
:value="item.id">
<AImage :src="item.thumbnail"
:alt="item.file_name"
:key="index"
:height="200"
:preview="{
src: item.url,
}"
loading="lazy">
<template #previewMask>
</template>
</AImage>
</CheckCard>
</template>
</Vue3JustifiedLayout>
</AImagePreviewGroup>
</div>
</div>
<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;">
还没检测到任何视频快去上传吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
<ATabPane key="gif" tab="动图">
<ATabPane key="gif" :tab="imageStore.homeTabMap['gif']">
<ASpin size="large" :spinning="loading" :delay="500">
<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>
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
style="line-height: 0 !important;">
<template #default="{ item }">
<CheckCard :key="index"
class="photo-item"
margin="0"
border-radius="0"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
:value="item.id">
<AImage :src="item.thumbnail"
:alt="item.file_name"
:key="index"
:height="200"
:preview="{
src: item.url,
}"
loading="lazy">
<template #previewMask>
</template>
</AImage>
</CheckCard>
</template>
</Vue3JustifiedLayout>
</AImagePreviewGroup>
</div>
</div>
<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;">
还没检测到任何动图快去上传吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
<ATabPane key="screenshot" tab="截图">
<ATabPane key="screenshot" :tab="imageStore.homeTabMap['screenshot']">
<ASpin size="large" :spinning="loading" :delay="500">
<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>
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="itemList.list" :options="options"
style="line-height: 0 !important;">
<template #default="{ item }">
<CheckCard :key="index"
class="photo-item"
margin="0"
border-radius="0"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
:value="item.id">
<AImage :src="item.thumbnail"
:alt="item.file_name"
:key="index"
:height="200"
:preview="{
src: item.url,
}"
loading="lazy">
<template #previewMask>
</template>
</AImage>
</CheckCard>
</template>
</Vue3JustifiedLayout>
</AImagePreviewGroup>
</div>
</div>
<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;">
还没检测到任何屏幕截图快去上传吧
</span>
</template>
</AEmpty>
</div>
</ASpin>
</ATabPane>
</ATabs>
</div>
@@ -80,9 +239,10 @@ import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
import useStore from "@/store";
import {queryAllImagesApi} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import empty from "@/assets/svgs/empty.svg";
const imageStore = useStore().image;
const selected = ref<(string | number)[]>([]);
const switchValue = ref<boolean>(false);
const upload = useStore().upload;
@@ -91,20 +251,29 @@ const options = reactive({
});
const images = ref<any[]>([]);
const loading = ref<boolean>(false);
/**
* 获取所有图片
*/
async function getAllImages() {
const res: any = await queryAllImagesApi("image", false, upload.storageSelected?.[0], upload.storageSelected?.[1]);
async function getAllImages(type: string) {
images.value = [];
loading.value = true;
const res: any = await queryAllImagesApi(type, false, upload.storageSelected?.[0], upload.storageSelected?.[1]);
if (res && res.code === 200) {
images.value = res.data.records;
}
loading.value = false;
}
async function handleTabChange(activeKey: string) {
imageStore.homeTabActiveKey = activeKey;
await getAllImages(activeKey);
}
onMounted(() => {
getAllImages();
getAllImages(imageStore.homeTabActiveKey);
});
</script>
<style scoped lang="scss">
@@ -144,4 +313,13 @@ onMounted(() => {
//transform: scale(0.99);
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%;
}
</style>

View File

@@ -1,18 +1,8 @@
<script setup lang="ts">
const props = defineProps({
selected: {
type: Array,
default: () => []
}
});
</script>
<template>
<transition name="slide-fade">
<div v-show="props.selected.length > 0" class="photo-toolbar-header">
<div class="photo-toolbar-left">
<AButton type="text" shape="circle" size="large" class="photo-toolbar-btn">
<AButton type="text" shape="circle" size="large" class="photo-toolbar-btn" @click="clearSelected">
<template #icon>
<CloseOutlined class="photo-toolbar-icon"/>
</template>
@@ -20,7 +10,7 @@ const props = defineProps({
<span style="font-size: 16px;font-weight: bold">
已选择 {{ props.selected.length }} 张照片
</span>
<AButton type="text" shape="default" class="photo-toolbar-btn" size="middle">
<AButton type="text" shape="default" class="photo-toolbar-btn" size="middle" @click="selectAll">
全选
</AButton>
</div>
@@ -43,7 +33,7 @@ const props = defineProps({
</template>
分享
</AButton>
<AButton type="text" shape="default" size="middle" class="photo-toolbar-btn">
<AButton type="text" shape="default" size="middle" class="photo-toolbar-btn" @click="deleteImages">
<template #icon>
<DeleteOutlined class="photo-toolbar-icon"/>
</template>
@@ -53,7 +43,41 @@ const props = defineProps({
</div>
</transition>
</template>
<script setup lang="ts">
import useStore from "@/store";
import {Image, ImageList, ImageRecord} from "@/types/image";
import {deletedImagesApi} from "@/api/storage";
const props = defineProps({
selected: {
type: Array as () => number[],
default: () => []
},
imageList: {
type: Array as () => ImageList,
default: () => []
}
});
const imageStore = useStore().image;
const uploadStore = useStore().upload;
const clearSelected = () => {
imageStore.selected = [];
};
const selectAll = () => {
imageStore.selected = props.imageList.flatMap((record: ImageRecord) => record.list.map((image: Image) => image.id));
};
const deleteImages = async () => {
const res: any = await deletedImagesApi(props.selected, uploadStore.storageSelected?.[0], uploadStore.storageSelected?.[1]);
if (res.code === 200) {
imageStore.selected = [];
}
};
</script>
<style scoped lang="scss">
.photo-toolbar-header {
position: fixed;

View File

@@ -2,10 +2,7 @@
<ADrawer v-model:open="upload.openUploadDrawer" placement="right" title="上传照片" width="40%" @close="cancelUpload">
<template #extra>
<AFlex :vertical="false" align="center" gap="large" justify="center">
<ASelect size="middle" style="width: 150px">
</ASelect>
<ASelect size="middle" style="width: 150px">
<ASelect size="middle" style="width: 150px" placeholder="选择上传的相册">
</ASelect>
</AFlex>
@@ -42,6 +39,13 @@
:status="progressStatus"
:show-info="true" size="small" type="line" v-show="predicting" style="width: 80%"/>
</AUploadDragger>
<AEmpty :image="empty" v-if="fileList.length === 0">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
上传列表为空可直接拖动文件到此区域上传
</span>
</template>
</AEmpty>
</div>
</ADrawer>
</template>
@@ -63,6 +67,7 @@ import exifr from 'exifr';
import isScreenshot from "@/utils/imageUtils/isScreenshot.ts";
import {getCategoryByLabel} from "@/constant/coco_ssd_label_category.ts";
import {generateThumbnail} from "@/utils/imageUtils/generateThumb.ts";
import empty from "@/assets/svgs/empty.svg";
const predicting = ref<boolean>(false);
const progressPercent = ref<number>(0);

View File

@@ -14,7 +14,7 @@
创建相册
</AButton>
</div>
<image-toolbar :selected="selected" />
<image-toolbar :selected="imageStore.selected" :image-list="images"/>
<div class="photo-list">
<div style="width:100%;height:100%;" v-if="images.length !== 0">
<div v-for="(itemList, index) in images" :key="index">
@@ -26,7 +26,7 @@
class="photo-item"
margin="0"
border-radius="0"
v-model="selected"
v-model="imageStore.selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
@@ -43,6 +43,15 @@
</AImagePreviewGroup>
</div>
</div>
<div v-else>
<AEmpty :image="empty">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</div>
<ImageUpload/>
</div>
@@ -55,8 +64,9 @@ import useStore from "@/store";
import ImageUpload from "@/views/Photograph/ImageUpload/ImageUpload.vue";
import {queryRecentImagesApi} from "@/api/storage";
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
import empty from "@/assets/svgs/empty.svg";
const selected = ref<(string | number)[]>([]);
const imageStore = useStore().image;
const upload = useStore().upload;
const images = ref<any[]>([]);
const options = reactive({

View File

@@ -109,7 +109,13 @@ async function handleListenMessage() {
userStore.token.expireAt = expire_at;
message.success(t('login.loginSuccess'));
setTimeout(() => {
router.push('/main/photo/all');
const currentUrl = new URL(window.location.href);
const redirect = currentUrl.searchParams.get('redirect');
if (redirect) {
router.push(redirect);
} else {
router.push('/main/photo/all');
}
}, 1000);
} else {
message.warning(t('login.loginError'));

View File

@@ -20,25 +20,42 @@
: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">
<template #description>
<span style="color: #999999;font-size: 16px;font-weight: 500;line-height: 1.5;">
暂无照片快去上传吧
</span>
</template>
</AEmpty>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Vue3JustifiedLayout from "vue3-justified-layout";
import 'vue3-justified-layout/dist/style.css';
import {queryShareImageApi} from "@/api/share";
import {getDeletedRecordApi} from "@/api/storage";
import useStore from "@/store";
import empty from "@/assets/svgs/empty.svg";
const selected = ref<(string | number)[]>([]);
@@ -46,14 +63,21 @@ const images = ref<any[]>([]);
const options = reactive({
targetRowHeight: 200 // 高度
});
const upload = useStore().upload;
async function getImages() {
const res = await queryShareImageApi("c09e3c571303448798c878095fbaa521", "123456");
console.log(res);
/**
* 查询回收站
*/
async function queryRecyclingBin() {
const res: any = await getDeletedRecordApi(upload.storageSelected?.[0], upload.storageSelected?.[1]);
if (res && res.code === 200) {
images.value = res.data.records;
}
}
getImages();
onMounted(() => {
queryRecyclingBin();
});
</script>
<style scoped lang="scss">

View File

@@ -5,15 +5,19 @@
<div class="image-share-left-title">
<h3>数据概览</h3>
</div>
<div class="image-share-left-content">
<ACard class="image-share-left-content-item"
type="inner"
style="background: linear-gradient(102.74deg, rgb(66, 230, 171) -7.03%, rgb(103, 235, 187) 97.7%);">
<div class="image-share-left-item-content">
<span style="font-weight: bolder;font-size: 2.3vh">浏览次数</span>
<span style="font-weight: bolder;font-size: 5vh">1</span>
<span style="font-weight: bolder;font-size: 5vh">{{ overviewData?overviewData.visit_count:0 }}</span>
<p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日浏览
<span style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+0</span>
<span
style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{
overviewData ? overviewData.visit_count_today : 0
}}</span>
</p>
</div>
</ACard>
@@ -22,9 +26,12 @@
style="background: linear-gradient(101.63deg, rgb(82, 138, 250) -12.83%, rgb(122, 167, 255) 100%);">
<div class="image-share-left-item-content">
<span style="font-weight: bolder;font-size: 2.3vh">浏览人数</span>
<span style="font-weight: bolder;font-size: 5vh">1</span>
<span style="font-weight: bolder;font-size: 5vh">{{ overviewData?overviewData.viewer_count:0 }}</span>
<p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日浏览人数
<span style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+0</span>
<span
style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{
overviewData?overviewData.viewer_count_today:0
}}</span>
</p>
</div>
</ACard>
@@ -33,13 +40,17 @@
style="background: linear-gradient(102.99deg, rgb(126, 92, 255) 3.18%, rgb(162, 139, 255) 102.52%);">
<div class="image-share-left-item-content">
<span style="font-weight: bolder;font-size: 2.3vh">发布次数</span>
<span style="font-weight: bolder;font-size: 5vh">1</span>
<span style="font-weight: bolder;font-size: 5vh">{{ overviewData?overviewData.publish_count:0 }}</span>
<p style="font-size: 2vh;color: hsla(0,0%,100%,.6);">今日发布
<span style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+0</span>
<span
style="font-weight: bolder;font-size: 2.8vh;color: #fff;">+{{
overviewData ? overviewData.publish_count_today : 0
}}</span>
</p>
</div>
</ACard>
</div>
</div>
<div class="image-share-left-bottom">
<div class="image-share-left-bottom-title">
@@ -88,8 +99,9 @@
</template>
<template v-else-if="column.key === 'action'">
<ATooltip title="复制分享链接">
<AButton type="text" size="small" @click="copyToClipboard(record.share_code)">
<LinkOutlined />
<AButton type="text" size="small"
@click="copyToClipboard(record.invite_code)">
<LinkOutlined/>
</AButton>
</ATooltip>
<ATooltip title="删除快传记录">
@@ -118,8 +130,8 @@
<script setup lang="ts">
import {Dayjs} from 'dayjs';
import dayjs from 'dayjs';
import ShareUpload from "@/views/ImageShare/ShareUpload.vue";
import {queryShareRecordListApi} from "@/api/share";
import ShareUpload from "@/views/Share/ImageShare/ShareUpload.vue";
import {queryShareOverviewApi, queryShareRecordListApi} from "@/api/share";
import {message} from "ant-design-vue";
import 'dayjs/locale/zh-cn';
@@ -132,6 +144,7 @@ const selectedDateRange = ref<RangeValue>();
const hackValue = ref<RangeValue>();
const loading = ref<boolean>(false);
const overviewDataLoading = ref<boolean>(false);
const disabledDate = (current: Dayjs) => {
@@ -229,7 +242,8 @@ const formatValidityPeriod = (period: number) => {
//
function copyToClipboard(text: string) {
navigator.clipboard.writeText(text).then(() => {
const url: string = import.meta.env.VITE_APP_WEB_URL + '/main/share/list/' + text;
navigator.clipboard.writeText(url).then(() => {
message.success('复制成功');
}).catch(() => {
message.error('复制失败');
@@ -250,13 +264,125 @@ async function getShareRecords(dateRange: string[]) {
loading.value = false;
}
const overviewData = ref<any>();
/**
* 获取分享概览
*/
async function getShareOverview() {
overviewDataLoading.value = true;
const res: any = await queryShareOverviewApi();
if (res && res.code === 200) {
overviewData.value = res.data;
}
overviewDataLoading.value = false;
}
onMounted(async () => {
const endDate = dayjs().format('YYYY-MM-DD'); //
const startDate = dayjs().subtract(30, 'day').format('YYYY-MM-DD'); // 30
await getShareRecords([startDate, endDate]);
await getShareOverview();
});
</script>
<style scoped lang="scss" src="./index.scss">
<style scoped lang="scss">
.image-share {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
width: 100%;
height: 100%;
gap: 20px;
.image-share-left {
height: 100%;
width: 65%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
.image-share-left-top {
width: 100%;
height: 30%;
display: flex;
flex-direction: column;
gap: 10px;
.image-share-left-title {
width: 100%;
height: 20%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.image-share-left-content {
width: 100%;
height: 80%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.image-share-left-content-item {
height: 100%;
width: 30%;
color: #fff;
overflow: auto;
.image-share-left-item-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
overflow: hidden;
}
}
}
}
.image-share-left-bottom {
width: 100%;
height: 70%;
display: flex;
flex-direction: column;
.image-share-left-bottom-title {
width: 100%;
height: 20%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.image-share-left-bottom-content {
width: 100%;
height: 80%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
.ant-card {
height: 100%;
.ant-table {
flex: 1;
height: 100%;
}
}
}
}
}
}
</style>

View File

@@ -23,18 +23,30 @@
</ABadge>
</p>
<p class="ant-upload-text" style="font-size: 2.6vh;font-weight: bolder">单击或拖动文件到此区域以上传</p>
<AButton type="primary" size="large" shape="round" style="width: 70%"> </AButton>
<AButton type="primary" size="large" shape="round" style="width: 70%">
<template #icon>
<CloudUploadOutlined/>
</template>
</AButton>
<APopover placement="top" trigger="click">
<template #content>
<AQrcode :bordered="false" color="rgba(126, 126, 135, 0.48)"
:size="qrcodeSize"
:value="`git.landaiqing.cneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjI1MTEyMjE3MzQyMDIxIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTczOTg3ODIyOCwibmJmIjoxNzM5ODcxMDI4LCJpYXQiOjE3Mzk4NzEwMjh9.EUiZsVjhGqHx1V5o90S3W5li6nIqucxy9eEY9LWgqXY`"
:icon="phone"
:iconSize="iconSize"
:status="`active`"
/>
</template>
<AButton @click.stop type="default" size="large" shape="round" style="width: 70%">
<template #icon>
<QrcodeOutlined/>
</template>
</AButton>
</APopover>
<div class="qr">
<AQrcode :bordered="false" color="rgba(126, 126, 135, 0.48)"
:size="qrcodeSize"
:value="`git.landaiqing.cneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjI1MTEyMjE3MzQyMDIxIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTczOTg3ODIyOCwibmJmIjoxNzM5ODcxMDI4LCJpYXQiOjE3Mzk4NzEwMjh9.EUiZsVjhGqHx1V5o90S3W5li6nIqucxy9eEY9LWgqXY`"
:icon="phone"
:iconSize="iconSize"
:status="`active`"
/>
<span style="font-size: 2vh;color: #999999">手机扫码上传</span>
</div>
</div>
</AUploadDragger>
</div>
@@ -143,7 +155,7 @@
</div>
<div v-if="loading && !uploadSuccess" class="image-share-right-bottom-loading">
<div class="image-share-right-bottom-loading-content">
<a-progress
<AProgress
type="circle"
:stroke-color="{
'0%': '#108ee9',
@@ -428,6 +440,9 @@ onMounted(() => {
}
});
});
onBeforeUnmount(() => {
websocket.close(false);
});
</script>
<style scoped lang="scss">
.image-share-right {
@@ -674,6 +689,6 @@ onMounted(() => {
display: flex;
flex-direction: column;
align-items: center;
gap: 2vh;
gap: 5vh;
}
</style>

View File

@@ -0,0 +1,154 @@
<template>
<div class="share-sidebar" v-if="shareInfo">
<div class="share-sidebar-header">
<AAvatar size="default" shape="circle" :src="shareInfo.sharer_avatar"/>
<p class="share-sidebar-title">来自
<span class="ellipsis" style="color: #00aced;cursor: pointer">{{ shareInfo.sharer_name }}</span>
的分享</p>
</div>
<div class="share-sidebar-body">
<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>
</template>
</AAvatar>
<p>{{ shareInfo.album_name }}</p>
<p style="color: #de3333; font-size: 12px">{{ formatTimeWithChinese(shareInfo.expire_time) }} 失效</p>
<AButton type="primary" size="large" shape="round">已加入去查看</AButton>
<AButton type="link" size="small" style="font-size: 13px">复制链接</AButton>
</div>
<div class="share-sidebar-body-bottom">
<div class="share-sidebar-body-bottom-item">
<p class="share-sidebar-body-bottom-item-title">照片</p>
<p class="share-sidebar-body-bottom-item-title">{{ shareInfo.image_count }}</p>
</div>
<div class="share-sidebar-body-bottom-item">
<p class="share-sidebar-body-bottom-item-title">共享人数</p>
<p class="share-sidebar-body-bottom-item-title">{{ shareInfo.viewer_count }}</p>
</div>
<div class="share-sidebar-body-bottom-item">
<p class="share-sidebar-body-bottom-item-title">浏览次数</p>
<p class="share-sidebar-body-bottom-item-title">{{ shareInfo.visit_count }}</p>
</div>
</div>
</div>
<div class="share-sidebar-footer">
</div>
</div>
</template>
<script setup lang="ts">
import {queryShareInfoApi} from "@/api/share";
const coverImageSize = ref<number>(130);
const shareInfo = ref<any>();
const route = useRoute();
async function getShareInfo(invite_code: string) {
const res: any = await queryShareInfoApi(invite_code);
if (res && res.code === 200) {
shareInfo.value = res.data;
}
}
function formatTimeWithChinese(dateString) {
const date = new Date(dateString);
const year = date.getFullYear();
const month = date.getMonth() + 1; // getMonth() 返回 0-11需要加 1
const day = date.getDate();
return `${year}${month}${day}`;
}
onMounted(() => {
const invite_code = route.params.id;
const code = Array.isArray(invite_code) ? invite_code[0] : invite_code;
getShareInfo(code);
});
</script>
<style scoped lang="scss">
.share-sidebar {
width: 220px;
height: 100%;
//height: calc(100vh - 271px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
overflow-y: auto;
overflow-x: hidden;
background-color: #fff;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
.share-sidebar-header {
width: 100%;
height: 15%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-bottom: 1px solid #eee;
gap: 10px;
.share-sidebar-title {
font-size: 1.7vh;
color: #333;
width: 100px;
}
}
.share-sidebar-body {
width: 100%;
height: 70%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 20px;
.share-sidebar-body-top {
width: 100%;
height: 80%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 20px;
}
.share-sidebar-body-bottom {
width: 80%;
height: 20%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 15px;
padding-left: 10px;
padding-right: 10px;
.share-sidebar-body-bottom-item {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.share-sidebar-body-bottom-item-title {
font-size: 1.8vh;
color: #333;
}
}
}
}
.share-sidebar-footer {
width: 100%;
height: 15%;
}
}
</style>

View File

@@ -0,0 +1,142 @@
<template>
<div class="share-view-main">
<div class="main-header">
<Header/>
</div>
<div class="share-view-content">
<ShareSidebar/>
<div class="share-view-content-container">
<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">
<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">
<AImagePreviewGroup>
<Vue3JustifiedLayout v-model:list="images" :options="options">
<template #default="{ item }">
<CheckCard
class="photo-item"
margin="0"
border-radius="0"
v-model="selected"
:showHoverCircle="true"
:iconSize="20"
:showSelectedEffect="true"
:value="item.id">
<AImage :src="item.thumbnail"
:alt="item.file_name"
style="height: 200px"
:preview="{
src: item.url,
}"
loading="lazy">
<template #previewMask>
</template>
</AImage>
</CheckCard>
</template>
</Vue3JustifiedLayout>
</AImagePreviewGroup>
</div>
</ASpin>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import Header from "@/layout/default/Header/Header.vue";
import ShareSidebar from "@/views/Share/ShareViewList/ShareSidebar.vue";
import Vue3JustifiedLayout from "vue3-justified-layout";
import 'vue3-justified-layout/dist/style.css';
import {queryShareImageApi} from "@/api/share";
const selected = ref<(string | number)[]>([]);
const images = ref<any[]>([]);
const options = reactive({
targetRowHeight: 200 // 高度
});
const route = useRoute();
const loading = ref<boolean>(false);
/**
* 获取分享图片列表
* @param e
*/
async function getShareImages(e) {
loading.value = true;
const invite_code = route.params.id;
const code = Array.isArray(invite_code) ? invite_code[0] : invite_code;
const res: any = await queryShareImageApi(code, e.target.value);
if (res && res.code === 200) {
images.value = res.data.records;
}
loading.value = false;
}
</script>
<style scoped lang="scss">
.share-view-main {
display: flex;
flex-direction: column;
background-color: #eaeef6;
color: var(--text-color);
width: 100vw;
height: 100%;
.share-view-content {
display: flex;
flex-direction: row;
width: 100%;
height: calc(100vh - 70px);
.share-view-content-container {
width: calc(100vw - 230px);
height: calc(100vh - 100px);
max-height: calc(100vh - 100px);
padding: 15px;
overflow: auto;
.share-content-header {
width: 100%;
height: 50px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
border-bottom: 1px solid #e2e2e2;
.share-content-header-button {
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
color: #333;
}
}
.share-content-verify {
width: 100%;
height: calc(100vh - 155px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
}
}
}
}
</style>