✨ improve image sharing function
This commit is contained in:
10
components.d.ts
vendored
10
components.d.ts
vendored
@@ -2,6 +2,7 @@
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
@@ -39,6 +40,7 @@ declare module 'vue' {
|
||||
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']
|
||||
@@ -64,6 +66,7 @@ declare module 'vue' {
|
||||
BoxDog: typeof import('./src/components/BoxDog/BoxDog.vue')['default']
|
||||
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']
|
||||
@@ -71,6 +74,7 @@ declare module 'vue' {
|
||||
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']
|
||||
@@ -87,6 +91,7 @@ declare module 'vue' {
|
||||
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']
|
||||
LoadingGraphic: typeof import('./src/components/LoadingGraphic/LoadingGraphic.vue')['default']
|
||||
LocationAlbum: typeof import('./src/views/Album/LocationAlbum/LocationAlbum.vue')['default']
|
||||
LocationAlbumDetail: typeof import('./src/views/Album/LocationAlbum/LocationAlbumDetail.vue')['default']
|
||||
@@ -105,7 +110,7 @@ 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/PhoneUpload/PhoneUpload.vue')['default']
|
||||
PhoneUpload: typeof import('./src/views/Phone/UpscalePhoneUpload/PhoneUpload.vue')['default']
|
||||
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']
|
||||
@@ -123,6 +128,8 @@ declare module 'vue' {
|
||||
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']
|
||||
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']
|
||||
@@ -132,6 +139,7 @@ declare module 'vue' {
|
||||
Tooltip: typeof import('./src/components/MyUI/Tooltip/Tooltip.vue')['default']
|
||||
UploadImage: typeof import('./src/views/Upscale/UploadImage.vue')['default']
|
||||
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']
|
||||
|
@@ -52,6 +52,7 @@
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"less": "^4.2.2",
|
||||
"localforage": "^1.10.0",
|
||||
"moment": "^2.30.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"nsfwjs": "^4.2.1",
|
||||
"pinia": "^3.0.1",
|
||||
@@ -79,15 +80,15 @@
|
||||
"sass": "^1.85.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"unplugin-vue-components": "^28.2.0",
|
||||
"vite": "^6.1.0",
|
||||
"unplugin-vue-components": "^28.4.0",
|
||||
"vite": "^6.1.1",
|
||||
"vite-plugin-bundle-obfuscator": "1.4.1",
|
||||
"vite-plugin-chunk-split": "^0.5.0",
|
||||
"vue-tsc": "2.2.2"
|
||||
},
|
||||
"overrides": {
|
||||
"vite-plugin-chunk-split": {
|
||||
"vite": "^6.1.0"
|
||||
"vite": "^6.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
43
src/api/share/index.ts
Normal file
43
src/api/share/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {service} from "@/utils/alova/service.ts";
|
||||
|
||||
/**
|
||||
* 上传分享图片
|
||||
* @param formData
|
||||
*/
|
||||
export const shareImageUploadApi = (formData) => {
|
||||
return service.Post('/api/auth/share/upload', {...formData}, {
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 查询分享图片列表
|
||||
* @param share_code
|
||||
* @param access_password
|
||||
*/
|
||||
export const queryShareImageApi = (share_code: string, access_password: string) => {
|
||||
return service.Post('/api/auth/share/image/list', {
|
||||
share_code: share_code,
|
||||
access_password: access_password,
|
||||
}, {
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 查询分享记录列表
|
||||
*/
|
||||
export const queryShareRecordListApi = (dataRequest: string[]) => {
|
||||
return service.Post('/api/auth/share/record/list', {
|
||||
date_range: dataRequest,
|
||||
}, {
|
||||
meta: {
|
||||
ignoreToken: false,
|
||||
signature: false,
|
||||
},
|
||||
});
|
||||
};
|
@@ -110,7 +110,7 @@ export const createAlbumApi = (name: string) => {
|
||||
* @param type
|
||||
* @param sort
|
||||
*/
|
||||
export const albumListApi = (type: string, sort: boolean) => {
|
||||
export const albumListApi = (type: number, sort: boolean) => {
|
||||
return service.Post('/api/auth/storage/album/list', {
|
||||
type: type,
|
||||
sort: sort,
|
||||
@@ -344,3 +344,5 @@ export const getStorageConfigListApi = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@@ -2,7 +2,7 @@ export default [
|
||||
{
|
||||
path: '/upscale/app',
|
||||
name: 'upscaleApp',
|
||||
component: () => import('@/views/PhoneUpload/PhoneUpload.vue'),
|
||||
component: () => import('@/views/Phone/UpscalePhoneUpload/UpscalePhoneUpload.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
title: '手机上传'
|
||||
|
@@ -62,7 +62,7 @@ const options = reactive({
|
||||
});
|
||||
|
||||
async function getImageList(id: number) {
|
||||
const res: any = await queryLocationDetailListApi(id, upload.storageSelected[0], upload.storageSelected[1]);
|
||||
const res: any = await queryLocationDetailListApi(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||
console.log(res);
|
||||
if (res && res.code === 200) {
|
||||
albumList.value = res.data.records;
|
||||
|
@@ -44,7 +44,7 @@ async function getLocationAlbums(provider: string, bucket: string) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLocationAlbums(upload.storageSelected[0], upload.storageSelected[1]);
|
||||
getLocationAlbums(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
@@ -72,7 +72,7 @@ const options = reactive({
|
||||
});
|
||||
|
||||
async function getAlbumList(id: number) {
|
||||
const res: any = await getFaceSamplesDetailList(id, upload.storageSelected[0], upload.storageSelected[1]);
|
||||
const res: any = await getFaceSamplesDetailList(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||
if (res && res.code === 200) {
|
||||
albumList.value = res.data.records;
|
||||
}
|
||||
|
@@ -98,7 +98,7 @@ const upload = useStore().upload;
|
||||
|
||||
|
||||
async function getAlbumList(id: number) {
|
||||
const res: any = await queryAlbumDetailListApi(id, upload.storageSelected[0], upload.storageSelected[1]);
|
||||
const res: any = await queryAlbumDetailListApi(id, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||
if (res && res.code === 200) {
|
||||
albumList.value = res.data.records;
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@
|
||||
<span
|
||||
style="color: #999; font-size: 12px;">已全部加载,共 {{ albumList ? albumList.length : 0 }} 个相册</span>
|
||||
</template>
|
||||
<ATabPane key="0" tab="全部相册">
|
||||
<ATabPane key="-1" tab="全部相册">
|
||||
<ASpin tip="Loading..." :spinning="loading" size="large">
|
||||
<div class="phoalbum-item-container">
|
||||
<div class="phoalbum-item"
|
||||
@@ -63,7 +63,8 @@
|
||||
@click.prevent="handleClick(album.id)"
|
||||
@mouseover="isHovered = index"
|
||||
@mouseleave="isHovered = null">
|
||||
<PhotoStack :src="album.cover_image" :default-src="default_cover"/>
|
||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
||||
:default-src="default_cover"/>
|
||||
<div class="phoalbum-item-info">
|
||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
||||
@@ -103,11 +104,155 @@
|
||||
</div>
|
||||
</ASpin>
|
||||
</ATabPane>
|
||||
<ATabPane key="1" tab="我的相册">
|
||||
|
||||
<ATabPane key="0" tab="我的相册">
|
||||
<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)"
|
||||
@mouseover="isHovered = index"
|
||||
@mouseleave="isHovered = null">
|
||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
||||
:default-src="default_cover"/>
|
||||
<div class="phoalbum-item-info">
|
||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
||||
</div>
|
||||
<div class="phoalbum-item-operation"
|
||||
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
||||
<ADropdown trigger="click" @click.stop>
|
||||
<AButton type="text" shape="circle" size="small" @click.prevent>
|
||||
<template #icon>
|
||||
<AAvatar shape="circle" size="small" :src="more"/>
|
||||
</template>
|
||||
</AButton>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<APopover placement="left" trigger="click">
|
||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
||||
<template #content>
|
||||
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
||||
v-model:value="albumRenameValue">
|
||||
<template #suffix>
|
||||
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
||||
@click="renameAlbum(album.id, albumRenameValue)">
|
||||
确认
|
||||
</AButton>
|
||||
</template>
|
||||
</AInput>
|
||||
</template>
|
||||
</APopover>
|
||||
<AMenuItem key="2">分享相册</AMenuItem>
|
||||
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
||||
<AMenuItem key="4">下载相册</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
</ADropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ASpin>
|
||||
</ATabPane>
|
||||
<ATabPane key="1" tab="我的分享">
|
||||
<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)"
|
||||
@mouseover="isHovered = index"
|
||||
@mouseleave="isHovered = null">
|
||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
||||
:default-src="default_cover"/>
|
||||
<div class="phoalbum-item-info">
|
||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
||||
</div>
|
||||
<div class="phoalbum-item-operation"
|
||||
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
||||
<ADropdown trigger="click" @click.stop>
|
||||
<AButton type="text" shape="circle" size="small" @click.prevent>
|
||||
<template #icon>
|
||||
<AAvatar shape="circle" size="small" :src="more"/>
|
||||
</template>
|
||||
</AButton>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<APopover placement="left" trigger="click">
|
||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
||||
<template #content>
|
||||
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
||||
v-model:value="albumRenameValue">
|
||||
<template #suffix>
|
||||
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
||||
@click="renameAlbum(album.id, albumRenameValue)">
|
||||
确认
|
||||
</AButton>
|
||||
</template>
|
||||
</AInput>
|
||||
</template>
|
||||
</APopover>
|
||||
<AMenuItem key="2">分享相册</AMenuItem>
|
||||
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
||||
<AMenuItem key="4">下载相册</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
</ADropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ASpin>
|
||||
</ATabPane>
|
||||
<ATabPane key="2" tab="收藏相册">
|
||||
|
||||
<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)"
|
||||
@mouseover="isHovered = index"
|
||||
@mouseleave="isHovered = null">
|
||||
<PhotoStack :src="album.cover_image ?`data:image/png;base64,`+album.cover_image: ``"
|
||||
:default-src="default_cover"/>
|
||||
<div class="phoalbum-item-info">
|
||||
<span class="phoalbum-item-name">{{ album.name }}</span>
|
||||
<span class="phoalbum-item-date">{{ album.created_at }}</span>
|
||||
</div>
|
||||
<div class="phoalbum-item-operation"
|
||||
:class="{ 'fade-in': isHovered === index, 'fade-out': isHovered !== index }">
|
||||
<ADropdown trigger="click" @click.stop>
|
||||
<AButton type="text" shape="circle" size="small" @click.prevent>
|
||||
<template #icon>
|
||||
<AAvatar shape="circle" size="small" :src="more"/>
|
||||
</template>
|
||||
</AButton>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<APopover placement="left" trigger="click">
|
||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
||||
<template #content>
|
||||
<AInput :placeholder="album.name" class="phoalbum-create-input"
|
||||
v-model:value="albumRenameValue">
|
||||
<template #suffix>
|
||||
<AButton type="text" @click.prevent size="small" style="color: #0e87cc"
|
||||
@click="renameAlbum(album.id, albumRenameValue)">
|
||||
确认
|
||||
</AButton>
|
||||
</template>
|
||||
</AInput>
|
||||
</template>
|
||||
</APopover>
|
||||
<AMenuItem key="2">分享相册</AMenuItem>
|
||||
<AMenuItem key="3" @click.prevent="deleteAlbum(album.id)">删除相册</AMenuItem>
|
||||
<AMenuItem key="4">下载相册</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
</ADropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ASpin>
|
||||
</ATabPane>
|
||||
</ATabs>
|
||||
</div>
|
||||
@@ -141,7 +286,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(0, selecetedKey.value);
|
||||
} else {
|
||||
message.error("创建相册失败");
|
||||
}
|
||||
@@ -153,7 +298,7 @@ async function createAlbumSubmit() {
|
||||
*/
|
||||
async function handleSelect({key}) {
|
||||
selecetedKey.value = key;
|
||||
await getAlbumList("0", key);
|
||||
await getAlbumList(0, key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +306,7 @@ async function handleSelect({key}) {
|
||||
* @param activeKey
|
||||
*/
|
||||
async function handleTabChange(activeKey: string) {
|
||||
await getAlbumList(activeKey, selecetedKey.value);
|
||||
await getAlbumList(parseInt(activeKey), selecetedKey.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +314,7 @@ async function handleTabChange(activeKey: string) {
|
||||
* @param type
|
||||
* @param sort
|
||||
*/
|
||||
async function getAlbumList(type: string = "0", sort: boolean = true) {
|
||||
async function getAlbumList(type: number = 0, sort: boolean = true) {
|
||||
albumList.value = [];
|
||||
loading.value = true;
|
||||
const res: any = await albumListApi(type, sort);
|
||||
@@ -192,7 +337,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(0, selecetedKey.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +348,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(0, selecetedKey.value);
|
||||
} else {
|
||||
message.error("删除相册失败");
|
||||
}
|
||||
@@ -221,7 +366,7 @@ function handleClick(id: number) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getAlbumList("0", selecetedKey.value);
|
||||
getAlbumList(0, selecetedKey.value);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@@ -52,11 +52,12 @@ import Vue3JustifiedLayout from "vue3-justified-layout";
|
||||
import 'vue3-justified-layout/dist/style.css';
|
||||
import {queryThingDetailListApi} from "@/api/storage";
|
||||
import ImageToolbar from "@/views/Photograph/ImageToolbar/ImageToolbar.vue";
|
||||
import useStore from "@/store";
|
||||
|
||||
|
||||
const selected = ref<(string | number)[]>([]);
|
||||
const albumList = ref<any[]>([]);
|
||||
|
||||
const upload = useStore().upload;
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const options = reactive({
|
||||
@@ -64,7 +65,7 @@ const options = reactive({
|
||||
});
|
||||
|
||||
async function getImageList(tag_name: string) {
|
||||
const res: any = await queryThingDetailListApi(tag_name, "ali", "schisandra-album");
|
||||
const res: any = await queryThingDetailListApi(tag_name, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||
if (res && res.code === 200) {
|
||||
albumList.value = res.data.records;
|
||||
}
|
||||
|
@@ -34,7 +34,6 @@ const thingAlbumList = ref<any[]>([]);
|
||||
|
||||
async function getThingAlbumList(provider: string, bucket: string) {
|
||||
const res: any = await queryThingAlbumApi(provider, bucket);
|
||||
console.log(res);
|
||||
if (res && res.code === 200) {
|
||||
thingAlbumList.value = res.data.records;
|
||||
}
|
||||
@@ -53,7 +52,7 @@ function handleClick(id: string) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getThingAlbumList(upload.storageSelected[0], upload.storageSelected[1]);
|
||||
getThingAlbumList(upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@@ -45,8 +45,9 @@
|
||||
<div class="image-share-left-bottom-title">
|
||||
<h3>快传管理</h3>
|
||||
<ARangePicker
|
||||
:value="hackValue || value"
|
||||
:value="selectedDateRange"
|
||||
:disabled-date="disabledDate"
|
||||
format="YYYY-MM-DD"
|
||||
@change="onChange"
|
||||
@openChange="onOpenChange"
|
||||
@calendarChange="onCalendarChange"
|
||||
@@ -55,167 +56,82 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="image-share-left-bottom-content">
|
||||
<ACard style="width: 100%;height: 100%;" :bodyStyle="{padding: 0}">
|
||||
<ATable :columns="columns" size="large" style="width: 100%;height: 100%;" :bordered="false">
|
||||
<ACard style="width: 100%;height: 100%;"
|
||||
:bodyStyle="{padding: 0, overflow: 'auto', display: 'flex', flexDirection: 'column'}">
|
||||
<ATable :columns="columns" :data-source="dataSources" size="large"
|
||||
style="flex: 1"
|
||||
:pagination="false"
|
||||
:loading="loading"
|
||||
:scroll="{ y: '40vh',x:true }"
|
||||
:bordered="false" @resizeColumn="handleResizeColumn">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cover_image'">
|
||||
<AAvatar shape="square" size="large" :src="`data:image/png;base64,`+record.cover_image"/>
|
||||
</template>
|
||||
<!-- 访问密码 -->
|
||||
<template v-else-if="column.key === 'access_password'">
|
||||
<AInputPassword
|
||||
v-if="record.access_password"
|
||||
v-model:value="record.access_password"
|
||||
type="password"
|
||||
:visibilityToggle="true"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
readOnly
|
||||
style="width: 100px;"
|
||||
/>
|
||||
<p v-else style="color: #999">无密码</p>
|
||||
</template>
|
||||
<!-- 有效期 -->
|
||||
<template v-else-if="column.key === 'validity_period'">
|
||||
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<ATooltip title="复制分享链接">
|
||||
<AButton type="text" size="small" @click="copyToClipboard(record.share_code)">
|
||||
<LinkOutlined />
|
||||
</AButton>
|
||||
</ATooltip>
|
||||
<ATooltip title="删除快传记录">
|
||||
<APopconfirm
|
||||
title="确定删除该快传记录?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
>
|
||||
<AButton type="text" size="small" danger>
|
||||
<DeleteOutlined/>
|
||||
</AButton>
|
||||
</APopconfirm>
|
||||
</ATooltip>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</ATable>
|
||||
</ACard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-share-right">
|
||||
<div class="image-share-right-top">
|
||||
<h3>照片快传</h3>
|
||||
</div>
|
||||
<div class="image-share-right-bottom">
|
||||
<div class="image-share-right-bottom-content">
|
||||
<div class="image-share-right-bottom-upload" ref="qrContainer" v-if="fileList.length<=0">
|
||||
<AUploadDragger
|
||||
name="file"
|
||||
:multiple="true"
|
||||
:showUploadList="false"
|
||||
:beforeUpload="beforeUpload"
|
||||
v-model:fileList="fileList"
|
||||
class="image-share-right-upload"
|
||||
>
|
||||
<div class="image-share-right-upload-item">
|
||||
<p class="ant-upload-drag-icon">
|
||||
<ABadge :offset="[-15, 20]" :count="fileList.length">
|
||||
<AAvatar shape="square" :size="folderIconSize" :src="folder"/>
|
||||
</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>
|
||||
<ShareUpload/>
|
||||
|
||||
<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>
|
||||
<div class="image-share-right-bottom-container" v-else>
|
||||
<div class="image-share-right-bottom-container-header">
|
||||
<AInput v-model:value="titleName" :bordered="false" size="large" placeholder="给快传起个标题"/>
|
||||
<ADropdown placement="bottomLeft" :trigger="['click']">
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<AMenuItem key="1">
|
||||
<AUpload
|
||||
name="file"
|
||||
:multiple="true"
|
||||
:showUploadList="false"
|
||||
:beforeUpload="beforeUpload"
|
||||
v-model:fileList="fileList"
|
||||
>
|
||||
上传文件
|
||||
</AUpload>
|
||||
</AMenuItem>
|
||||
<AMenuItem key="2">
|
||||
<APopover placement="bottomLeft" trigger="hover">
|
||||
<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>
|
||||
手机上传
|
||||
</APopover>
|
||||
</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
<AButton size="middle" shape="circle">
|
||||
<template #icon>
|
||||
<PlusOutlined/>
|
||||
</template>
|
||||
</AButton>
|
||||
</ADropdown>
|
||||
</div>
|
||||
<div class="image-share-right-bottom-content-list">
|
||||
<p style="font-size: 2.0vh;color: #999999;cursor: default">共{{ fileList.length }}个文件
|
||||
{{ calculateTotalSize(fileList) }}</p>
|
||||
<div class="image-share-right-bottom-content-list-wrapper">
|
||||
<div class="image-share-right-bottom-content-list-item" v-for="(item, index) in fileList" :key="index">
|
||||
<div class="file-thumbnail">
|
||||
<AImage :width="50" :height="50" :src="convertFileToUrl(item.originFileObj)">
|
||||
<template #previewMask>
|
||||
</template>
|
||||
</AImage>
|
||||
</div>
|
||||
<div class="file-info">
|
||||
<p style="font-size: 2.0vh;color: #333333;cursor: default;font-weight: bold">{{ item.name }}</p>
|
||||
<p style="font-size: 1.5vh;color: #999999;cursor: default">{{
|
||||
formatByteSize(item.size)
|
||||
}}</p>
|
||||
</div>
|
||||
<div class="file-operation">
|
||||
<AButton size="middle" shape="circle" type="text" @click="removeBase64Image(index)">
|
||||
<template #icon>
|
||||
<CloseOutlined/>
|
||||
</template>
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ADivider/>
|
||||
<div class="image-share-right-bottom-operation">
|
||||
<div class="image-share-right-operation-select">
|
||||
<div class="image-share-right-operation-item">
|
||||
<span class="label-text">访问时效</span>
|
||||
<ASelect style="width: 50%">
|
||||
<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="image-share-right-operation-item">
|
||||
<span class="label-text">访问密码</span>
|
||||
<AInputPassword style="width: 50%"></AInputPassword>
|
||||
</div>
|
||||
<div class="image-share-right-operation-item">
|
||||
<span class="label-text">访问限制</span>
|
||||
<AInputNumber style="width: 50%" :defaultValue="100" :min="1"></AInputNumber>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-share-right-bottom-operation-btn">
|
||||
<AButton type="primary" size="large" shape="default" style="width: 100%">开始上传</AButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {Dayjs} from 'dayjs';
|
||||
import folder from "@/assets/svgs/folder.svg";
|
||||
import {NSFWJS} from "nsfwjs";
|
||||
import {initNSFWJs, predictNSFW} from "@/utils/tfjs/nsfw.ts";
|
||||
import dayjs from 'dayjs';
|
||||
import ShareUpload from "@/views/ImageShare/ShareUpload.vue";
|
||||
import {queryShareRecordListApi} from "@/api/share";
|
||||
import {message} from "ant-design-vue";
|
||||
import i18n from "@/locales";
|
||||
import phone from "@/assets/svgs/qr-phone.svg";
|
||||
import 'dayjs/locale/zh-cn';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
|
||||
type RangeValue = [Dayjs, Dayjs];
|
||||
const dates = ref<RangeValue>();
|
||||
const value = ref<RangeValue>();
|
||||
const selectedDateRange = ref<RangeValue>();
|
||||
const hackValue = ref<RangeValue>();
|
||||
const titleName = ref<string>("");
|
||||
|
||||
const qrContainer = ref<HTMLDivElement | null>(null);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
|
||||
const disabledDate = (current: Dayjs) => {
|
||||
@@ -236,81 +152,65 @@ const onOpenChange = (open: boolean) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (val: RangeValue) => {
|
||||
value.value = val;
|
||||
const onChange = async (val: RangeValue) => {
|
||||
selectedDateRange.value = val;
|
||||
|
||||
// 将日期范围处理成一个数组
|
||||
if (val && val.length === 2) {
|
||||
const startDate = val[0].format('YYYY-MM-DD'); // 格式化开始日期
|
||||
const endDate = val[1].format('YYYY-MM-DD'); // 格式化结束日期
|
||||
const dateRangeArray = [startDate, endDate]; // 将日期范围存入数组
|
||||
await getShareRecords(dateRangeArray);
|
||||
} else {
|
||||
console.log('No date range selected');
|
||||
}
|
||||
};
|
||||
|
||||
const onCalendarChange = (val: RangeValue) => {
|
||||
dates.value = val;
|
||||
};
|
||||
|
||||
/**
|
||||
* 格式化字节大小
|
||||
* @param bytes
|
||||
*/
|
||||
function formatByteSize(bytes) {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} Bytes`;
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(2)} KB`;
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节大小
|
||||
* @param bytes
|
||||
* @param decimals
|
||||
*/
|
||||
function formatBytes(bytes: number, decimals: number = 2): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件总大小
|
||||
* @param fileDataArray
|
||||
*/
|
||||
function calculateTotalSize(fileDataArray: { size: number }[]): string {
|
||||
const totalSize = fileDataArray.reduce((acc, file) => acc + file.size, 0);
|
||||
return formatBytes(totalSize);
|
||||
}
|
||||
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '快传记录',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
dataIndex: 'cover_image',
|
||||
key: 'cover_image',
|
||||
ellipsis: true,
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
dataIndex: 'created_at',
|
||||
key: 'created_at',
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => {
|
||||
return dayjs(text).format('YYYY-MM-DD'); // 格式化时间
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '浏览次数',
|
||||
dataIndex: 'views',
|
||||
key: 'views',
|
||||
title: '访问密码',
|
||||
key: 'access_password',
|
||||
dataIndex: 'access_password',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '浏览人数',
|
||||
key: 'viewers',
|
||||
dataIndex: 'viewers',
|
||||
title: '访问限制',
|
||||
key: 'visit_limit',
|
||||
dataIndex: 'visit_limit',
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => {
|
||||
return `${text}次`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '传输状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
title: '有效期',
|
||||
key: 'validity_period',
|
||||
dataIndex: 'validity_period',
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => {
|
||||
return formatValidityPeriod(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -319,339 +219,44 @@ const columns = [
|
||||
},
|
||||
];
|
||||
|
||||
const qrcodeSize = ref<number>(220);
|
||||
const iconSize = ref<number>(30);
|
||||
const folderIconSize = ref<number>(100);
|
||||
|
||||
/**
|
||||
* 更新二维码大小
|
||||
*/
|
||||
const updateQrcodeSize = () => {
|
||||
if (qrContainer.value) {
|
||||
// 设置 QRCode 大小
|
||||
const containerWidth = qrContainer.value.clientWidth;
|
||||
qrcodeSize.value = containerWidth * 0.5; // 设置 QRCode 为父盒子宽度的80%
|
||||
folderIconSize.value = containerWidth * 0.3; // 设置文件夹图标大小为父盒子宽度的10%
|
||||
iconSize.value = Math.min(containerWidth * 0.1, 40); // 设置 icon 大小为父盒子宽度的10%
|
||||
function handleResizeColumn(w, col) {
|
||||
col.width = w;
|
||||
}
|
||||
|
||||
const formatValidityPeriod = (period: number) => {
|
||||
return period === 0 ? '永久' : `${period}天`;
|
||||
};
|
||||
|
||||
const fileList = ref<any[]>([]);
|
||||
|
||||
/**
|
||||
* 上传文件前置
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 base64 图片
|
||||
* @param index
|
||||
*/
|
||||
async function removeBase64Image(index: number) {
|
||||
fileList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换文件为 URL
|
||||
* @param file
|
||||
*/
|
||||
function convertFileToUrl(file: any) {
|
||||
return URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("resize", updateQrcodeSize);
|
||||
// 复制功能
|
||||
function copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('复制成功');
|
||||
}).catch(() => {
|
||||
message.error('复制失败');
|
||||
});
|
||||
}
|
||||
|
||||
const dataSources = ref<any[]>([]);
|
||||
|
||||
/**
|
||||
* 获取分享记录
|
||||
*/
|
||||
async function getShareRecords(dateRange: string[]) {
|
||||
loading.value = true;
|
||||
const res: any = await queryShareRecordListApi(dateRange);
|
||||
if (res && res.code === 200) {
|
||||
dataSources.value = res.data.records;
|
||||
}
|
||||
loading.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]);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right {
|
||||
height: 100%;
|
||||
width: 35%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.image-share-right-top {
|
||||
width: 100%;
|
||||
height: 6%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.image-share-right-bottom {
|
||||
width: 100%;
|
||||
height: 94%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.image-share-right-bottom-content {
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.image-share-right-bottom-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.image-share-right-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-bottom-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.image-share-right-bottom-container-header {
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.image-share-right-bottom-content-list {
|
||||
width: 95%;
|
||||
height: 40%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 15px;
|
||||
flex-wrap: nowrap;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 10px;
|
||||
|
||||
.image-share-right-bottom-content-list-wrapper {
|
||||
width: 100%;
|
||||
height: 27vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
overflow: auto;
|
||||
gap: 10px;
|
||||
|
||||
.image-share-right-bottom-content-list-item {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.file-thumbnail {
|
||||
height: 100%;
|
||||
width: 17%;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
height: 100%;
|
||||
width: 63%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.file-operation {
|
||||
height: 100%;
|
||||
width: 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.image-share-right-bottom-operation {
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.image-share-right-operation-select {
|
||||
width: 100%;
|
||||
height: 75%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.image-share-right-operation-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-bottom-operation-btn {
|
||||
width: 100%;
|
||||
height: 35%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.image-share-right-upload-item {
|
||||
//width: 100% !important;
|
||||
//height: 100% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
//justify-content: center;
|
||||
gap: 2vh;
|
||||
}
|
||||
|
||||
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
</style>
|
||||
|
679
src/views/ImageShare/ShareUpload.vue
Normal file
679
src/views/ImageShare/ShareUpload.vue
Normal file
@@ -0,0 +1,679 @@
|
||||
<template>
|
||||
<div class="image-share-right">
|
||||
<div class="image-share-right-top">
|
||||
<h3>照片快传</h3>
|
||||
</div>
|
||||
<div class="image-share-right-bottom">
|
||||
<div class="image-share-right-bottom-content">
|
||||
<div class="image-share-right-bottom-upload" ref="qrContainer"
|
||||
v-if="fileList.length <= 0 && !uploadSuccess">
|
||||
<AUploadDragger
|
||||
name="file"
|
||||
:multiple="true"
|
||||
:showUploadList="false"
|
||||
:beforeUpload="beforeUpload"
|
||||
v-model:fileList="fileList"
|
||||
:customRequest="customRequest"
|
||||
class="image-share-right-upload"
|
||||
>
|
||||
<div class="image-share-right-upload-item">
|
||||
<p class="ant-upload-drag-icon">
|
||||
<ABadge :offset="[-15, 20]" :count="fileList.length">
|
||||
<AAvatar shape="square" :size="folderIconSize" :src="folder"/>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
<div class="image-share-right-bottom-container" v-if="fileList.length > 0 && !loading">
|
||||
<div class="image-share-right-bottom-container-header">
|
||||
<AInput v-model:value="titleName" :bordered="false" size="large" placeholder="给快传起个标题"/>
|
||||
<ADropdown placement="bottomLeft" :trigger="['click']">
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<AMenuItem key="1">
|
||||
<AUpload
|
||||
name="file"
|
||||
:multiple="true"
|
||||
:showUploadList="false"
|
||||
:beforeUpload="beforeUpload"
|
||||
v-model:fileList="fileList"
|
||||
>
|
||||
上传文件
|
||||
</AUpload>
|
||||
</AMenuItem>
|
||||
<AMenuItem key="2">
|
||||
<APopover placement="bottomLeft" trigger="hover">
|
||||
<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>
|
||||
手机上传
|
||||
</APopover>
|
||||
</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
<AButton size="middle" shape="circle">
|
||||
<template #icon>
|
||||
<PlusOutlined/>
|
||||
</template>
|
||||
</AButton>
|
||||
</ADropdown>
|
||||
</div>
|
||||
<div class="image-share-right-bottom-content-list">
|
||||
<p style="font-size: 2.0vh;color: #999999;cursor: default">共{{ fileList.length }}个文件
|
||||
{{ calculateTotalSize(fileList) }}</p>
|
||||
<div class="image-share-right-bottom-content-list-wrapper">
|
||||
<div class="image-share-right-bottom-content-list-item"
|
||||
v-for="(item, index) in fileList" :key="index">
|
||||
<div class="file-thumbnail" ref="fileContainer">
|
||||
<AImage :width="50" :height="50"
|
||||
:src="convertFileToUrl(item.originFileObj)">
|
||||
<template #previewMask>
|
||||
</template>
|
||||
</AImage>
|
||||
</div>
|
||||
<div class="file-info">
|
||||
<p style="font-size: 2.0vh;color: #333333;cursor: default;font-weight: bold">{{ item.name }}</p>
|
||||
<p style="font-size: 1.5vh;color: #999999;cursor: default">{{
|
||||
formatByteSize(item.size)
|
||||
}}</p>
|
||||
</div>
|
||||
<div class="file-operation">
|
||||
<AButton size="middle" shape="circle" type="text" @click="removeImage(index)">
|
||||
<template #icon>
|
||||
<CloseOutlined/>
|
||||
</template>
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ADivider/>
|
||||
<div class="image-share-right-bottom-operation">
|
||||
<div class="image-share-right-operation-select">
|
||||
<div class="image-share-right-operation-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="image-share-right-operation-item">
|
||||
<span class="label-text">访问密码</span>
|
||||
<AInputPassword style="width: 50%" v-model:value="access_password" :maxlength="10"
|
||||
:showCount="true"></AInputPassword>
|
||||
</div>
|
||||
<div class="image-share-right-operation-item">
|
||||
<span class="label-text">访问限制</span>
|
||||
<AInputNumber style="width: 50%" :defaultValue="100" :min="1"
|
||||
v-model:value="access_limit"></AInputNumber>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-share-right-bottom-operation-btn">
|
||||
<AButton type="primary" size="middle" shape="round" style="width: 100%"
|
||||
:loading="loading"
|
||||
@click="customUploader">
|
||||
开始上传
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading && !uploadSuccess" class="image-share-right-bottom-loading">
|
||||
<div class="image-share-right-bottom-loading-content">
|
||||
<a-progress
|
||||
type="circle"
|
||||
:stroke-color="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
}"
|
||||
:percent="percent"
|
||||
:size="180"
|
||||
/>
|
||||
<p>{{ fileList.length }} 个文件 / {{ calculateTotalSize(fileList) }}</p>
|
||||
</div>
|
||||
<div class="image-share-right-bottom-loading-footer">
|
||||
<AButton type="primary" size="large" shape="round" style="width: 80%" @click="abort">
|
||||
取 消
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="uploadSuccess" class="image-share-right-bottom-success">
|
||||
<div class="image-share-right-bottom-success-header">
|
||||
<div class="image-share-right-bottom-success-header-title">
|
||||
<CheckCircleOutlined style="font-size: 3vh;color: #52c41a"/>
|
||||
<h3>上 传 成 功</h3>
|
||||
</div>
|
||||
<div class="image-share-right-bottom-success-header-close">
|
||||
<AButton type="text" size="middle" shape="circle" @click="uploadSuccess = false">
|
||||
<CloseOutlined/>
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-share-right-bottom-success-content">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
import folder from "@/assets/svgs/folder.svg";
|
||||
import phone from "@/assets/svgs/qr-phone.svg";
|
||||
import useStore from "@/store";
|
||||
import {NSFWJS} from "nsfwjs";
|
||||
import {initNSFWJs, predictNSFW} from "@/utils/tfjs/nsfw.ts";
|
||||
import {message} from "ant-design-vue";
|
||||
import i18n from "@/locales";
|
||||
import {useRequest} from "alova/client";
|
||||
import {shareImageUploadApi} from "@/api/share";
|
||||
import imageCompression from "browser-image-compression";
|
||||
import {generateThumbnail} from "@/utils/imageUtils/generateThumb.ts";
|
||||
|
||||
const titleName = ref<string>("");
|
||||
|
||||
const upload = useStore().upload;
|
||||
const percent = ref<number>(0);
|
||||
const uploadSuccess = ref<boolean>(false);
|
||||
const qrContainer = ref<HTMLDivElement | null>(null);
|
||||
|
||||
/**
|
||||
* 格式化字节大小
|
||||
* @param bytes
|
||||
*/
|
||||
function formatByteSize(bytes: number) {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} Bytes`;
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return `${(bytes / 1024).toFixed(2)} KB`;
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
||||
} else {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节大小
|
||||
* @param bytes
|
||||
* @param decimals
|
||||
*/
|
||||
function formatBytes(bytes: number, decimals: number = 2): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件总大小
|
||||
* @param fileDataArray
|
||||
*/
|
||||
function calculateTotalSize(fileDataArray: { size: number }[]): string {
|
||||
const totalSize = fileDataArray.reduce((acc, file) => acc + file.size, 0);
|
||||
return formatBytes(totalSize);
|
||||
}
|
||||
|
||||
const qrcodeSize = ref<number>(220);
|
||||
const iconSize = ref<number>(30);
|
||||
const folderIconSize = ref<number>(100);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
|
||||
/**
|
||||
* 更新二维码大小
|
||||
*/
|
||||
const updateQrcodeSize = () => {
|
||||
if (qrContainer.value) {
|
||||
// 设置 QRCode 大小
|
||||
const containerWidth = qrContainer.value.clientWidth;
|
||||
qrcodeSize.value = containerWidth * 0.5; // 设置 QRCode 为父盒子宽度的80%
|
||||
folderIconSize.value = containerWidth * 0.3; // 设置文件夹图标大小为父盒子宽度的10%
|
||||
iconSize.value = Math.min(containerWidth * 0.1, 40); // 设置 icon 大小为父盒子宽度的10%
|
||||
}
|
||||
};
|
||||
|
||||
const fileList = ref<any[]>([]);
|
||||
|
||||
const expire_date = ref<string>("1");
|
||||
|
||||
const access_limit = ref<number>(100);
|
||||
|
||||
const access_password = ref<string>("");
|
||||
|
||||
/**
|
||||
* 上传文件前置
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义请求
|
||||
* @param _file
|
||||
* @param _fileList
|
||||
*/
|
||||
function customRequest(_file: any, _fileList: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {uploading, send: submitFile, abort} = useRequest(shareImageUploadApi, {
|
||||
immediate: false,
|
||||
debounce: 500,
|
||||
});
|
||||
|
||||
/**
|
||||
* 自定义上传器
|
||||
*/
|
||||
async function customUploader() {
|
||||
if (fileList.value.length <= 0) return;
|
||||
loading.value = true;
|
||||
uploadSuccess.value = false;
|
||||
// 存储所有图片信息的数组
|
||||
const images: any[] = [];
|
||||
for (const file of fileList.value) {
|
||||
// 压缩图片
|
||||
const compressedFile = await imageCompression(file.originFileObj, {
|
||||
maxSizeMB: 0.1,
|
||||
maxWidthOrHeight: 750,
|
||||
maxIteration: 10,
|
||||
useWebWorker: true,
|
||||
initialQuality: 0.6,
|
||||
});
|
||||
|
||||
// 生成缩略图
|
||||
const {binaryData, width, height, size} = await generateThumbnail(compressedFile);
|
||||
// 将文件转换为 Base64 编码
|
||||
const base64Thumbnail = binaryData ? await toBase64(binaryData) : '';
|
||||
const base64Image = await toBase64(file.originFileObj);
|
||||
// 创建文件的元数据对象
|
||||
const fileObj = {
|
||||
file_name: file.name, // 文件名
|
||||
origin_image: base64Image, // 原始图片的 base64 数据
|
||||
file_type: file.type, // 文件类型
|
||||
thumbnail: base64Thumbnail, // 缩略图的 base64 数据
|
||||
thumb_w: width, // 缩略图宽度
|
||||
thumb_h: height, // 缩略图高度
|
||||
thumb_size: size, // 缩略图文件大小
|
||||
};
|
||||
|
||||
// 将文件对象添加到图片数组
|
||||
images.push(fileObj);
|
||||
}
|
||||
|
||||
// 准备发送给后端的请求数据
|
||||
const requestData = {
|
||||
title: titleName.value,
|
||||
expire_date: expire_date.value,
|
||||
access_limit: access_limit.value,
|
||||
access_password: access_password.value,
|
||||
provider: upload.storageSelected?.[0],
|
||||
bucket: upload.storageSelected?.[1],
|
||||
images: images,
|
||||
};
|
||||
watch(
|
||||
() => uploading.value,
|
||||
(upload) => {
|
||||
if (upload && upload.loaded && upload.total) {
|
||||
percent.value = Number(Math.round((upload.loaded / upload.total) * 100).toFixed(2));
|
||||
}
|
||||
},
|
||||
);
|
||||
const res: any = await submitFile(requestData);
|
||||
if (res && res.code === 200) {
|
||||
uploadSuccess.value = true;
|
||||
fileList.value = [];
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件转换为 Base64 编码
|
||||
* @param file
|
||||
*/
|
||||
function toBase64(file: Blob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onloadend = () => {
|
||||
// 确保 reader.result 是字符串类型
|
||||
if (typeof reader.result === 'string') {
|
||||
resolve(reader.result.split(',')[1]); // 只获取 base64 部分
|
||||
} else {
|
||||
reject(new Error('FileReader result is not a string.'));
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除图片
|
||||
* @param index
|
||||
*/
|
||||
async function removeImage(index: number) {
|
||||
fileList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换文件为 URL
|
||||
* @param file
|
||||
*/
|
||||
function convertFileToUrl(file: any) {
|
||||
return URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
const user = useStore().user;
|
||||
const websocket = useStore().websocket;
|
||||
const wsOptions = {
|
||||
url: import.meta.env.VITE_FILE_SOCKET_URL + "?user_id=" + user.user.uid,
|
||||
protocols: [user.token.accessToken],
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("resize", updateQrcodeSize);
|
||||
websocket.initialize(wsOptions);
|
||||
websocket.on("message", async (res: any) => {
|
||||
if (res && res.code === 200) {
|
||||
const {data} = res;
|
||||
console.log(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.image-share-right {
|
||||
height: 100%;
|
||||
width: 35%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.image-share-right-top {
|
||||
width: 100%;
|
||||
height: 6%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.image-share-right-bottom {
|
||||
width: 100%;
|
||||
height: 94%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.image-share-right-bottom-content {
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.image-share-right-bottom-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.image-share-right-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-bottom-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.image-share-right-bottom-container-header {
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.image-share-right-bottom-content-list {
|
||||
width: 95%;
|
||||
height: 40%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 15px;
|
||||
flex-wrap: nowrap;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 10px;
|
||||
|
||||
.image-share-right-bottom-content-list-wrapper {
|
||||
width: 100%;
|
||||
height: 27vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
overflow: auto;
|
||||
gap: 10px;
|
||||
|
||||
.image-share-right-bottom-content-list-item {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
|
||||
.file-thumbnail {
|
||||
height: 100%;
|
||||
width: 17%;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
height: 100%;
|
||||
width: 63%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.file-operation {
|
||||
height: 100%;
|
||||
width: 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.image-share-right-bottom-operation {
|
||||
width: 100%;
|
||||
height: 40%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.image-share-right-operation-select {
|
||||
width: 100%;
|
||||
height: 75%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
.image-share-right-operation-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-bottom-operation-btn {
|
||||
width: 100%;
|
||||
height: 35%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-bottom-loading {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
|
||||
.image-share-right-bottom-loading-content {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.image-share-right-bottom-loading-footer {
|
||||
width: 100%;
|
||||
height: 20%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-bottom-success {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
|
||||
.image-share-right-bottom-success-header {
|
||||
width: 100%;
|
||||
height: 10%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.image-share-right-bottom-success-header-title {
|
||||
height: 100%;
|
||||
width: 90%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.image-share-right-bottom-success-header-close {
|
||||
height: 100%;
|
||||
width: 10%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-bottom-success-content {
|
||||
width: 100%;
|
||||
height: 90%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-share-right-upload-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2vh;
|
||||
}
|
||||
</style>
|
100
src/views/ImageShare/index.scss
Normal file
100
src/views/ImageShare/index.scss
Normal file
@@ -0,0 +1,100 @@
|
||||
.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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
9
src/views/Phone/SharePhoneUpload/SharePhoneUpload.vue
Normal file
9
src/views/Phone/SharePhoneUpload/SharePhoneUpload.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
@@ -96,7 +96,7 @@ const images = ref<any[]>([]);
|
||||
* 获取所有图片
|
||||
*/
|
||||
async function getAllImages() {
|
||||
const res: any = await queryAllImagesApi("image", false, upload.storageSelected[0], upload.storageSelected[1]);
|
||||
const res: any = await queryAllImagesApi("image", false, upload.storageSelected?.[0], upload.storageSelected?.[1]);
|
||||
if (res && res.code === 200) {
|
||||
images.value = res.data.records;
|
||||
}
|
||||
|
@@ -224,8 +224,8 @@ async function customUploadRequest(file: any) {
|
||||
formData.append("thumbnail", binaryData);
|
||||
}
|
||||
formData.append("data", JSON.stringify({
|
||||
provider: upload.storageSelected[0],
|
||||
bucket: upload.storageSelected[1],
|
||||
provider: upload.storageSelected?.[0],
|
||||
bucket: upload.storageSelected?.[1],
|
||||
fileType: file.file.type,
|
||||
...upload.predictResult,
|
||||
}));
|
||||
|
@@ -38,6 +38,7 @@
|
||||
<script setup lang="ts">
|
||||
import Vue3JustifiedLayout from "vue3-justified-layout";
|
||||
import 'vue3-justified-layout/dist/style.css';
|
||||
import {queryShareImageApi} from "@/api/share";
|
||||
|
||||
|
||||
const selected = ref<(string | number)[]>([]);
|
||||
@@ -47,6 +48,13 @@ const options = reactive({
|
||||
});
|
||||
|
||||
|
||||
async function getImages() {
|
||||
const res = await queryShareImageApi("c09e3c571303448798c878095fbaa521", "123456");
|
||||
console.log(res);
|
||||
}
|
||||
|
||||
getImages();
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.recycling-bin {
|
||||
|
Reference in New Issue
Block a user