✨ add image recognition classification
This commit is contained in:
2
auto-import.d.ts
vendored
2
auto-import.d.ts
vendored
@@ -64,6 +64,7 @@ declare global {
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||
@@ -373,6 +374,7 @@ declare module 'vue' {
|
||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||
readonly onElementRemoval: UnwrapRef<typeof import('@vueuse/core')['onElementRemoval']>
|
||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
||||
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
||||
|
10
components.d.ts
vendored
10
components.d.ts
vendored
@@ -15,6 +15,7 @@ declare module 'vue' {
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
||||
AFlex: typeof import('ant-design-vue/es')['Flex']
|
||||
@@ -57,6 +58,7 @@ declare module 'vue' {
|
||||
BackgroundAnimation: typeof import('./src/components/BackgroundAnimation/BackgroundAnimation.vue')['default']
|
||||
BackTop: typeof import('./src/components/MyUI/BackTop/BackTop.vue')['default']
|
||||
Badge: typeof import('./src/components/MyUI/Badge/Badge.vue')['default']
|
||||
BarsOutlined: typeof import('@ant-design/icons-vue')['BarsOutlined']
|
||||
BoxDog: typeof import('./src/components/BoxDog/BoxDog.vue')['default']
|
||||
Breadcrumb: typeof import('./src/components/MyUI/Breadcrumb/Breadcrumb.vue')['default']
|
||||
Button: typeof import('./src/components/MyUI/Button/Button.vue')['default']
|
||||
@@ -80,9 +82,11 @@ declare module 'vue' {
|
||||
DatePicker: typeof import('./src/components/MyUI/DatePicker/DatePicker.vue')['default']
|
||||
DeleteOutlined: typeof import('@ant-design/icons-vue')['DeleteOutlined']
|
||||
Descriptions: typeof import('./src/components/MyUI/Descriptions/Descriptions.vue')['default']
|
||||
Detail: typeof import('./src/views/Album/LocationAlbum/Detail.vue')['default']
|
||||
Dialog: typeof import('./src/components/MyUI/Dialog/Dialog.vue')['default']
|
||||
Divider: typeof import('./src/components/MyUI/Divider/Divider.vue')['default']
|
||||
DownloadOutlined: typeof import('@ant-design/icons-vue')['DownloadOutlined']
|
||||
DownOutlined: typeof import('@ant-design/icons-vue')['DownOutlined']
|
||||
Drawer: typeof import('./src/components/MyUI/Drawer/Drawer.vue')['default']
|
||||
DynamicTitle: typeof import('./src/components/DynamicTitle/DynamicTitle.vue')['default']
|
||||
Ellipsis: typeof import('./src/components/MyUI/Ellipsis/Ellipsis.vue')['default']
|
||||
@@ -97,6 +101,8 @@ declare module 'vue' {
|
||||
Image: typeof import('./src/components/MyUI/Image/Image.vue')['default']
|
||||
ImageShare: typeof import('./src/views/ImageShare/ImageShare.vue')['default']
|
||||
ImageWaterfall: typeof import('./src/components/MyUI/Waterfall/ImageWaterfall.vue')['default']
|
||||
InboxOutlined: typeof import('@ant-design/icons-vue')['InboxOutlined']
|
||||
Index: typeof import('./src/views/Album/LocationAlbum/Index.vue')['default']
|
||||
Input: typeof import('./src/components/MyUI/Input/Input.vue')['default']
|
||||
InputSearch: typeof import('./src/components/MyUI/InputSearch/InputSearch.vue')['default']
|
||||
LandingPage: typeof import('./src/views/Landing/LandingPage.vue')['default']
|
||||
@@ -115,11 +121,14 @@ declare module 'vue' {
|
||||
NotFound: typeof import('./src/views/404/NotFound.vue')['default']
|
||||
Notification: typeof import('./src/components/MyUI/Notification/Notification.vue')['default']
|
||||
NumberAnimation: typeof import('./src/components/MyUI/NumberAnimation/NumberAnimation.vue')['default']
|
||||
OrderedListOutlined: typeof import('@ant-design/icons-vue')['OrderedListOutlined']
|
||||
Pagination: typeof import('./src/components/MyUI/Pagination/Pagination.vue')['default']
|
||||
ParameterSetting: typeof import('./src/views/Upscale/ParameterSetting.vue')['default']
|
||||
PeopleAlbum: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbum.vue')['default']
|
||||
Phoalbum: typeof import('./src/views/Album/Phoalbum/Phoalbum.vue')['default']
|
||||
PhoneUpload: typeof import('./src/views/PhoneUpload/PhoneUpload.vue')['default']
|
||||
PhotoStack: typeof import('./src/components/MyUI/PhotoStack/PhotoStack.vue')['default']
|
||||
PictureStack: typeof import('./src/views/Album/Phoalbum/PictureStack.vue')['default']
|
||||
PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined']
|
||||
PlusSquareOutlined: typeof import('@ant-design/icons-vue')['PlusSquareOutlined']
|
||||
Popconfirm: typeof import('./src/components/MyUI/Popconfirm/Popconfirm.vue')['default']
|
||||
@@ -141,6 +150,7 @@ declare module 'vue' {
|
||||
Row: typeof import('./src/components/MyUI/Grid/Row.vue')['default']
|
||||
SafetyOutlined: typeof import('@ant-design/icons-vue')['SafetyOutlined']
|
||||
Scrollbar: typeof import('./src/components/MyUI/Scrollbar/Scrollbar.vue')['default']
|
||||
SearchOutlined: typeof import('@ant-design/icons-vue')['SearchOutlined']
|
||||
Segmented: typeof import('./src/components/MyUI/Segmented/Segmented.vue')['default']
|
||||
Select: typeof import('./src/components/MyUI/Select/Select.vue')['default']
|
||||
SendOutlined: typeof import('@ant-design/icons-vue')['SendOutlined']
|
||||
|
21
package.json
21
package.json
@@ -13,6 +13,9 @@
|
||||
"@alova/adapter-axios": "^2.0.12",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@intlify/eslint-plugin-vue-i18n": "^3.2.0",
|
||||
"@tensorflow-models/coco-ssd": "^2.2.3",
|
||||
"@tensorflow-models/knn-classifier": "^1.2.6",
|
||||
"@tensorflow-models/mobilenet": "^2.1.1",
|
||||
"@tensorflow/tfjs": "^4.22.0",
|
||||
"@tensorflow/tfjs-backend-webgl": "^4.22.0",
|
||||
"@tensorflow/tfjs-backend-webgpu": "^4.22.0",
|
||||
@@ -20,11 +23,11 @@
|
||||
"@types/animejs": "^3.1.12",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/json-stringify-safe": "^5.0.3",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@vuepic/vue-datepicker": "^10.0.0",
|
||||
"@vueuse/core": "^12.2.0",
|
||||
"@vueuse/integrations": "^12.2.0",
|
||||
"@vueuse/core": "^12.3.0",
|
||||
"@vueuse/integrations": "^12.3.0",
|
||||
"alova": "^3.2.7",
|
||||
"animejs": "^3.2.2",
|
||||
"ant-design-vue": "^4.2.6",
|
||||
@@ -33,7 +36,7 @@
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.5.1",
|
||||
"echarts": "^5.6.0",
|
||||
"eslint": "9.17.0",
|
||||
"go-captcha-vue": "^2.0.5",
|
||||
"gsap": "^3.12.5",
|
||||
@@ -47,7 +50,7 @@
|
||||
"pinia-plugin-persistedstate-2": "^2.0.28",
|
||||
"qrcode": "^1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"swiper": "^11.1.15",
|
||||
"swiper": "^11.2.0",
|
||||
"unplugin-auto-import": "^0.19.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
@@ -56,7 +59,7 @@
|
||||
"vue-dompurify-html": "^5.2.0",
|
||||
"vue-i18n": "^11.0.1",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-waterfall-plugin-next": "^2.6.4",
|
||||
"vue-waterfall-plugin-next": "^2.6.5",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -64,11 +67,11 @@
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"globals": "^15.14.0",
|
||||
"sass": "^1.83.0",
|
||||
"sass": "^1.83.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"typescript-eslint": "^8.19.0",
|
||||
"unplugin-vue-components": "^0.28.0",
|
||||
"vite": "^6.0.6",
|
||||
"vite": "^6.0.7",
|
||||
"vite-plugin-bundle-obfuscator": "1.4.0",
|
||||
"vite-plugin-chunk-split": "^0.5.0",
|
||||
"vue-tsc": "2.2.0"
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
17877
public/tfjs/mobilenet/ssd-mobilenet-v2-tfjs-default-v1/model.json
Normal file
17877
public/tfjs/mobilenet/ssd-mobilenet-v2-tfjs-default-v1/model.json
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
1
src/assets/svgs/more.svg
Normal file
1
src/assets/svgs/more.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1735790957970" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7635" width="200" height="200"><path d="M512 0c282.331429 0 512 229.668571 512 512s-229.668571 512-512 512-512-229.668571-512-512 229.668571-512 512-512z m256 438.857143a73.142857 73.142857 0 1 0-0.073143 146.212571A73.142857 73.142857 0 0 0 768 438.857143zM512 438.857143a73.142857 73.142857 0 1 0 0 146.285714 73.142857 73.142857 0 0 0 0-146.285714zM256 438.857143a73.142857 73.142857 0 1 0 0 146.285714 73.142857 73.142857 0 0 0 0-146.285714z" fill="#536076" p-id="7636"></path></svg>
|
After Width: | Height: | Size: 603 B |
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div class="check-card" :class="{ 'selected': isSelected }" @click="handleClick" :style="cardStyle">
|
||||
<div
|
||||
class="check-card"
|
||||
:class="{ 'selected': isSelected && props.showSelectedEffect }"
|
||||
@click="handleClick"
|
||||
:style="cardStyle">
|
||||
<div class="hover-circle" @click.stop="toggleSelection"
|
||||
v-if="showHoverCircle"
|
||||
:style="{ width: iconSize + 'px', height: iconSize + 'px' }">
|
||||
@@ -35,6 +39,7 @@ interface Props {
|
||||
backgroundColor?: string;
|
||||
showHoverCircle?: boolean; // 控制是否显示悬停圆环
|
||||
iconSize?: number; // 控制图标大小
|
||||
showSelectedEffect?: boolean; // 控制是否显示选中效果
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -45,6 +50,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
backgroundColor: '#e5eeff',
|
||||
showHoverCircle: true, // 默认显示悬停圆环
|
||||
iconSize: 24, // 默认图标大小
|
||||
showSelectedEffect: true, // 默认显示选中效果
|
||||
});
|
||||
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
@@ -90,6 +96,7 @@ function toggleSelection() {
|
||||
transition: border-color 0.3s, background-color 0.3s;
|
||||
overflow: visible; /* Ensure the icon is not cut off */
|
||||
}
|
||||
|
||||
.check-card.selected {
|
||||
border: 1px solid rgba(125, 167, 255, 0.68);
|
||||
box-shadow: 0 0 2px rgba(77, 167, 255, 0.89);
|
||||
|
91
src/components/MyUI/PhotoStack/PhotoStack.vue
Normal file
91
src/components/MyUI/PhotoStack/PhotoStack.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="stack rotated">
|
||||
<img :src="imageSrc" alt="">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
src?: string;
|
||||
defaultSrc: string;
|
||||
}>();
|
||||
|
||||
const imageSrc = computed(() => props.src || props.defaultSrc);
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.stack {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.stack img {
|
||||
width: 200px;
|
||||
height: 180px;
|
||||
vertical-align: bottom;
|
||||
border: 5px #fff solid;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.stack:before,
|
||||
.stack:after {
|
||||
content: "";
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border: 10px solid #fff;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
||||
transition: 0.3s all ease-out;
|
||||
}
|
||||
|
||||
.stack:before {
|
||||
top: 4px;
|
||||
z-index: -10;
|
||||
}
|
||||
|
||||
.stack:after {
|
||||
top: 8px;
|
||||
z-index: -20;
|
||||
}
|
||||
|
||||
.stack.rotated:before {
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.stack.rotated:after {
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(4deg);
|
||||
}
|
||||
|
||||
.stack.twisted:before {
|
||||
transform: rotate(4deg);
|
||||
}
|
||||
|
||||
.stack.twisted:after {
|
||||
transform: rotate(-4deg);
|
||||
}
|
||||
|
||||
.stack.rotated-left:before {
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
|
||||
.stack.rotated-left:after {
|
||||
transform-origin: bottom left;
|
||||
transform: rotate(-6deg);
|
||||
}
|
||||
|
||||
.stack:hover:before,
|
||||
.stack:hover:after {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
</style>
|
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<div class="sidebar">
|
||||
<AMenu
|
||||
:selectedKeys="[route.path.split('/').slice(-2).join('/')]"
|
||||
:selectedKeys="[menu.currentMenu]"
|
||||
:selectable="true"
|
||||
:multiple="false"
|
||||
mode="vertical"
|
||||
@@ -96,10 +96,12 @@ import recyclingbin from '@/assets/svgs/recyclingbin.svg';
|
||||
import Folder from "@/components/Folder/Folder.vue";
|
||||
import ai from '@/assets/svgs/ai.svg';
|
||||
import share from '@/assets/svgs/share.svg';
|
||||
import useStore from "@/store";
|
||||
|
||||
const {t} = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const menu = useStore().menu;
|
||||
|
||||
/**
|
||||
* handle click event of menu item
|
||||
@@ -107,6 +109,7 @@ const route = useRoute();
|
||||
*/
|
||||
function handleClick({key}) {
|
||||
router.push(`/main/${key}`);
|
||||
menu.currentMenu = key;
|
||||
}
|
||||
|
||||
const menuCSSStyle: any = reactive({
|
||||
@@ -135,6 +138,17 @@ function scrollToSelectedMenuItem() {
|
||||
onMounted(() => {
|
||||
scrollToSelectedMenuItem();
|
||||
});
|
||||
|
||||
// watch(
|
||||
// () => route.path,
|
||||
// (newPath) => {
|
||||
// if (!newPath.includes(menu.currentMenu)) {
|
||||
// router.push(`/main/${menu.currentMenu}`);
|
||||
// }
|
||||
// scrollToSelectedMenuItem();
|
||||
// }
|
||||
// );
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
|
||||
|
@@ -1,43 +1,115 @@
|
||||
import PhoalbumIndex from "@/views/Album/Phoalbum/Index.vue";
|
||||
import PeopleAlbumIndex from "@/views/Album/PeopleAlbum/Index.vue";
|
||||
import LocationAlbum from "@/views/Album/LocationAlbum/LocationAlbum.vue";
|
||||
import LocationAlbumIndex from "@/views/Album/LocationAlbum/Index.vue";
|
||||
import ThingAlbum from "@/views/Album/ThingAlbum/ThingAlbum.vue";
|
||||
import ThingAlbumIndex from "@/views/Album/ThingAlbum/Index.vue";
|
||||
import Phoalbum from "@/views/Album/Phoalbum/Phoalbum.vue";
|
||||
import PeopleAlbum from "@/views/Album/PeopleAlbum/PeopleAlbum.vue";
|
||||
import LocationAlbum from "@/views/Album/LocationAlbum/LocationAlbum.vue";
|
||||
import ThingAlbum from "@/views/Album/ThingAlbum/ThingAlbum.vue";
|
||||
import PhoalbumDetail from "@/views/Album/Phoalbum/Detail.vue";
|
||||
import PeopleAlbumDetail from "@/views/Album/PeopleAlbum/Detail.vue";
|
||||
import LocationAlbumDetail from "@/views/Album/LocationAlbum/Detail.vue";
|
||||
import ThingAlbumDetail from "@/views/Album/ThingAlbum/Detail.vue";
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/main/album/albums',
|
||||
name: 'albums',
|
||||
component: Phoalbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '相册'
|
||||
},
|
||||
component: PhoalbumIndex,
|
||||
redirect: '/main/album/albums',
|
||||
children: [
|
||||
{
|
||||
path: '/main/album/albums',
|
||||
name: 'album',
|
||||
component: Phoalbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '相册'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/album/albums/:id',
|
||||
name: 'albumList',
|
||||
component: PhoalbumDetail,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '相册'
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/main/album/people',
|
||||
name: 'people',
|
||||
component: PeopleAlbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '人物相册'
|
||||
},
|
||||
component: PeopleAlbumIndex,
|
||||
redirect: '/main/album/people',
|
||||
children: [
|
||||
{
|
||||
path: '/main/album/people',
|
||||
name: 'people',
|
||||
component: PeopleAlbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '人物相册'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/album/people/:id',
|
||||
name: 'peopleList',
|
||||
component: PeopleAlbumDetail,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '人物相册'
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/main/album/location',
|
||||
name: 'location',
|
||||
component: LocationAlbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '地点相册'
|
||||
},
|
||||
component: LocationAlbumIndex,
|
||||
redirect: '/main/album/location',
|
||||
children: [
|
||||
{
|
||||
path: '/main/album/location',
|
||||
name: 'location',
|
||||
component: LocationAlbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '地点相册'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/album/location/:id',
|
||||
name: 'locationList',
|
||||
component: LocationAlbumDetail,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '地点相册'
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/main/album/thing',
|
||||
name: 'thing',
|
||||
component: ThingAlbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '事物相册'
|
||||
},
|
||||
component: ThingAlbumIndex,
|
||||
redirect: '/main/album/thing',
|
||||
children: [
|
||||
{
|
||||
path: '/main/album/thing',
|
||||
name: 'thing',
|
||||
component: ThingAlbum,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '事物相册'
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/main/album/thing/:id',
|
||||
name: 'thingList',
|
||||
component: ThingAlbumDetail,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: '事物相册'
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
@@ -4,6 +4,8 @@ import {langStore} from "@/store/modules/langStore.ts";
|
||||
import {useCommentStore} from "@/store/modules/commentStore.ts";
|
||||
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";
|
||||
|
||||
export default function useStore() {
|
||||
return {
|
||||
@@ -13,5 +15,7 @@ export default function useStore() {
|
||||
comment: useCommentStore(),
|
||||
websocket: useWebSocketStore(),
|
||||
upscale: useUpscaleStore(),
|
||||
menu: useMenuStore(),
|
||||
upload: useUploadStore(),
|
||||
};
|
||||
}
|
||||
|
18
src/store/modules/menuStore.ts
Normal file
18
src/store/modules/menuStore.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const useMenuStore = defineStore(
|
||||
'menu',
|
||||
() => {
|
||||
const currentMenu = ref<string>('photo/all');
|
||||
return {
|
||||
currentMenu,
|
||||
};
|
||||
},
|
||||
{
|
||||
// 开启数据持久化
|
||||
persistedState: {
|
||||
persist: true,
|
||||
storage: localStorage,
|
||||
key: 'menu',
|
||||
includePaths: ['currentMenu']
|
||||
}
|
||||
}
|
||||
);
|
80
src/store/modules/uploadStore.ts
Normal file
80
src/store/modules/uploadStore.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import {initNSFWJs, predictNSFW} from "@/utils/nsfw/nsfw.ts";
|
||||
import i18n from "@/locales";
|
||||
|
||||
import {NSFWJS} from "nsfwjs";
|
||||
import {message} from "ant-design-vue";
|
||||
|
||||
import {loadCocoSsd, loadMobileNet} from "@/utils/tfjs";
|
||||
|
||||
export const useUploadStore = defineStore(
|
||||
'upload',
|
||||
() => {
|
||||
const openUploadDrawer = ref<boolean>(false);
|
||||
const image: HTMLImageElement = document.createElement('img');
|
||||
|
||||
/**
|
||||
* 图片上传前的校验
|
||||
* @param file
|
||||
*/
|
||||
async function beforeUpload(file: File) {
|
||||
image.src = URL.createObjectURL(file);
|
||||
// 图片 NSFW 检测
|
||||
const nsfw: NSFWJS = await initNSFWJs();
|
||||
const isNSFW: boolean = await predictNSFW(nsfw, image);
|
||||
if (isNSFW) {
|
||||
message.error(i18n.global.t('comment.illegalImage'));
|
||||
return false;
|
||||
}
|
||||
const predictions = await loadMobileNet(image);
|
||||
console.log(predictions);
|
||||
|
||||
const prediction = await loadCocoSsd(image);
|
||||
console.log(prediction);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义上传请求
|
||||
* @param file
|
||||
*/
|
||||
async function customUploadRequest(file: any) {
|
||||
|
||||
const progress = {percent: 1};
|
||||
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
if (progress.percent < 100) {
|
||||
progress.percent++;
|
||||
file.onProgress(progress);
|
||||
} else {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 100);
|
||||
file.onSuccess(true);
|
||||
|
||||
// file.onSuccess = () => {
|
||||
// message.success(i18n.global.t('comment.uploadSuccess'));
|
||||
// };
|
||||
// file.onError = () => {
|
||||
// message.error(i18n.global.t('comment.uploadError'));
|
||||
// };
|
||||
// return Promise.resolve(file);
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
openUploadDrawer,
|
||||
beforeUpload,
|
||||
customUploadRequest
|
||||
};
|
||||
},
|
||||
{
|
||||
// 开启数据持久化
|
||||
persistedState: {
|
||||
persist: false,
|
||||
storage: localStorage,
|
||||
key: 'upload',
|
||||
includePaths: []
|
||||
}
|
||||
}
|
||||
);
|
@@ -101,6 +101,13 @@ export const useUpscaleStore = defineStore(
|
||||
const msg = ref<string>("");
|
||||
const progressBar = ref<number>(0);
|
||||
const status = ref<string>('loading');
|
||||
|
||||
|
||||
const dragging = ref<boolean>(false);
|
||||
const linePosition = ref(0);
|
||||
const draggingLine = ref(false);
|
||||
|
||||
|
||||
/**
|
||||
* 图片上传前的校验
|
||||
* @param file
|
||||
@@ -118,10 +125,11 @@ export const useUpscaleStore = defineStore(
|
||||
uploading.value = false;
|
||||
return false;
|
||||
}
|
||||
await clear();
|
||||
fileData.value = urlData;
|
||||
await loadImg(image);
|
||||
uploading.value = false;
|
||||
await clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -142,6 +150,12 @@ export const useUpscaleStore = defineStore(
|
||||
msg.value = "";
|
||||
progressBar.value = 0;
|
||||
isProcessing.value = false;
|
||||
dragging.value = false;
|
||||
linePosition.value = 0;
|
||||
draggingLine.value = false;
|
||||
input.value = null;
|
||||
inputAlpha.value = null;
|
||||
wasmModule.value = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,6 +217,9 @@ export const useUpscaleStore = defineStore(
|
||||
msg,
|
||||
progressBar,
|
||||
status,
|
||||
dragging,
|
||||
linePosition,
|
||||
draggingLine,
|
||||
loadImg,
|
||||
beforeUpload,
|
||||
customUploadRequest,
|
||||
|
84
src/utils/tfjs/index.ts
Normal file
84
src/utils/tfjs/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import * as tf from '@tensorflow/tfjs';
|
||||
import * as mobilenet from '@tensorflow-models/mobilenet';
|
||||
import * as cocoSsd from '@tensorflow-models/coco-ssd';
|
||||
|
||||
// 确保 TensorFlow.js 已准备好并设置后端
|
||||
async function initializeTensorFlow(backend = "webgl") {
|
||||
await tf.ready();
|
||||
if (!(await tf.setBackend(backend))) {
|
||||
console.error(`${backend} is not supported in your browser.`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 加载 MobileNet 模型的工具函数
|
||||
export async function loadMobileNet(image) {
|
||||
const modelName = "mobilenet-model";
|
||||
const modelUrl = '/tfjs/mobilenet/mobilenet-v3-tfjs-large-100-224-feature-vector-v1/model.json';
|
||||
|
||||
// 初始化 TensorFlow.js
|
||||
if (!(await initializeTensorFlow())) {
|
||||
return;
|
||||
}
|
||||
|
||||
let model;
|
||||
|
||||
try {
|
||||
// 尝试从 IndexedDB 加载模型
|
||||
model = await mobilenet.load({
|
||||
version: 2,
|
||||
alpha: 1.0,
|
||||
modelUrl: `indexeddb://${modelName}`,
|
||||
});
|
||||
console.log("MobileNet model loaded from IndexedDB successfully");
|
||||
} catch (_error) {
|
||||
console.log("Downloading MobileNet model...");
|
||||
// 如果 IndexedDB 中没有模型则从 URL 加载并保存到 IndexedDB
|
||||
model = await mobilenet.load({
|
||||
version: 2,
|
||||
alpha: 1.0,
|
||||
modelUrl: modelUrl,
|
||||
});
|
||||
const Model = await tf.loadGraphModel(modelUrl);
|
||||
await Model.save(`indexeddb://${modelName}`);
|
||||
console.log("MobileNet model downloaded and saved to IndexedDB");
|
||||
}
|
||||
|
||||
// 使用模型进行分类
|
||||
return await model.classify(image, 3);
|
||||
}
|
||||
|
||||
// 加载 COCO SSD 模型的工具函数
|
||||
export async function loadCocoSsd(image) {
|
||||
const modelName = "cocoSsd-model";
|
||||
const modelUrl = '/tfjs/mobilenet/ssd-mobilenet-v2-tfjs-default-v1/model.json';
|
||||
|
||||
// 初始化 TensorFlow.js
|
||||
if (!(await initializeTensorFlow())) {
|
||||
return;
|
||||
}
|
||||
|
||||
let model;
|
||||
|
||||
try {
|
||||
// 尝试从 IndexedDB 加载模型
|
||||
model = await cocoSsd.load({
|
||||
base: 'mobilenet_v2',
|
||||
modelUrl: `indexeddb://${modelName}`,
|
||||
});
|
||||
console.log("COCO SSD model loaded from IndexedDB successfully");
|
||||
} catch (_error) {
|
||||
console.log("Downloading COCO SSD model...");
|
||||
// 如果 IndexedDB 中没有模型则从 URL 加载并保存到 IndexedDB
|
||||
model = await cocoSsd.load({
|
||||
base: 'mobilenet_v2',
|
||||
modelUrl: modelUrl,
|
||||
});
|
||||
const Model = await tf.loadGraphModel(modelUrl);
|
||||
await Model.save(`indexeddb://${modelName}`);
|
||||
console.log("COCO SSD model downloaded and saved to IndexedDB");
|
||||
}
|
||||
// 使用模型进行检测
|
||||
return await model.detect(image);
|
||||
}
|
11
src/views/Album/LocationAlbum/Detail.vue
Normal file
11
src/views/Album/LocationAlbum/Detail.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
11
src/views/Album/LocationAlbum/Index.vue
Normal file
11
src/views/Album/LocationAlbum/Index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
@@ -1,11 +1,122 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="location-album">
|
||||
<div class="location-album-header">
|
||||
<AButton type="link" size="large" class="location-album-button">地点</AButton>
|
||||
<span class="location-album-count">你一共在2个地点留下足迹</span>
|
||||
</div>
|
||||
<div class="location-album-content">
|
||||
<div class="location-album-container" @click="handleClick">
|
||||
<img class="background-image" src="/test/5.png" alt=""/>
|
||||
<div class="overlay">
|
||||
<span>乌鲁木齐市</span>
|
||||
<span class="location-album-overlay-count">---</span>
|
||||
<span class="location-album-overlay-count">16张照片</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
function handleClick() {
|
||||
router.push({ path: route.path + '/1' });
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.location-album {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.location-album-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
|
||||
.location-album-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.location-album-count {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.location-album-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-top: 20px;
|
||||
gap: 20px;
|
||||
|
||||
.location-album-container {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.background-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.2); /* 黑色半透明 */
|
||||
backdrop-filter: blur(2px); /* 背景虚化 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s ease, backdrop-filter 0.3s ease;
|
||||
gap: 0;
|
||||
|
||||
.location-album-overlay-count {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1); /* 黑色半透明 */
|
||||
backdrop-filter: blur(0px); /* 背景虚化 */
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.location-album-container:hover {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
9
src/views/Album/PeopleAlbum/Detail.vue
Normal file
9
src/views/Album/PeopleAlbum/Detail.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
11
src/views/Album/PeopleAlbum/Index.vue
Normal file
11
src/views/Album/PeopleAlbum/Index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
@@ -1,11 +1,170 @@
|
||||
<template>
|
||||
<div class="people-album">
|
||||
<div class="people-album-header">
|
||||
<ADropdown>
|
||||
<AButton type="text" size="large" class="people-album-button">
|
||||
人物
|
||||
<DownOutlined class="people-album-icon"/>
|
||||
</AButton>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<AMenuItem>人 物</AMenuItem>
|
||||
<AMenuItem>已隐藏</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
|
||||
</ADropdown>
|
||||
</div>
|
||||
<div class="people-album-content">
|
||||
<div class="people-album-item" @mouseover="showButton = true" @mouseleave="showButton = false">
|
||||
<div class="people-album-item-avatar">
|
||||
<AAvatar :size="86" shape="circle" src="/test/4.png"/>
|
||||
</div>
|
||||
<div class="people-album-item-name">
|
||||
<AButton @click="showAddNameInput" class="people-album-add-name" v-show="showButton && !showInput" type="link"
|
||||
size="small">
|
||||
添加名字
|
||||
</AButton>
|
||||
<AInput v-show="showInput" @blur="hideAddNameInput" size="small" class="people-album-add-input">
|
||||
<template #suffix>
|
||||
<AButton type="link" size="small">完成</AButton>
|
||||
</template>
|
||||
</AInput>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="people-album-item" @mouseover="showButton = true" @mouseleave="showButton = false">
|
||||
<div class="people-album-item-avatar">
|
||||
<AAvatar :size="86" shape="circle" src="/test/4.png"/>
|
||||
</div>
|
||||
<div class="people-album-item-name">
|
||||
<AButton @click="showAddNameInput" class="people-album-add-name" v-show="showButton && !showInput" type="link"
|
||||
size="small">
|
||||
添加名字
|
||||
</AButton>
|
||||
<AInput v-show="showInput" @blur="hideAddNameInput" size="small" class="people-album-add-input">
|
||||
<template #suffix>
|
||||
<AButton type="link" size="small">完成</AButton>
|
||||
</template>
|
||||
</AInput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const showButton = ref(false);
|
||||
const showInput = ref(false);
|
||||
|
||||
function showAddNameInput() {
|
||||
showInput.value = true;
|
||||
showButton.value = false;
|
||||
}
|
||||
|
||||
function hideAddNameInput() {
|
||||
showInput.value = false;
|
||||
showButton.value = false;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.people-album {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
<template>
|
||||
.people-album-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
|
||||
</template>
|
||||
.people-album-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
.people-album-icon {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.people-album-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
padding-top: 20px;
|
||||
padding-left: 20px;
|
||||
gap: 20px;
|
||||
|
||||
.people-album-item {
|
||||
width: 130px;
|
||||
height: 160px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.people-album-item-avatar {
|
||||
width: 100%;
|
||||
height: 75%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.people-album-item-name {
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
.people-album-add-input {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.people-album-add-name {
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.people-album-add-name:hover {
|
||||
color: #0e87cc;
|
||||
}
|
||||
}
|
||||
|
||||
.people-album-item:hover {
|
||||
background-color: rgba(248, 248, 248, 0.74);
|
||||
opacity: 1;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
9
src/views/Album/Phoalbum/Detail.vue
Normal file
9
src/views/Album/Phoalbum/Detail.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
9
src/views/Album/Phoalbum/Index.vue
Normal file
9
src/views/Album/Phoalbum/Index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
@@ -1,11 +1,180 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="phoalbum">
|
||||
<div class="phoalbum-header">
|
||||
<AButton type="primary" shape="round" size="middle">
|
||||
<template #icon>
|
||||
<PlusSquareOutlined/>
|
||||
</template>
|
||||
创建相册
|
||||
</AButton>
|
||||
<ADropdown>
|
||||
<AButton type="default" shape="round" size="middle">
|
||||
<template #icon>
|
||||
<OrderedListOutlined/>
|
||||
</template>
|
||||
排序
|
||||
</AButton>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<AMenuItem key="1">按时间排序</AMenuItem>
|
||||
<AMenuItem key="2">按名称排序</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
</ADropdown>
|
||||
<AInput class="phoalbum-search" placeholder="搜索相册">
|
||||
<template #suffix>
|
||||
<AButton size="small" type="text" shape="circle" @click.prevent>
|
||||
<template #icon>
|
||||
<SearchOutlined/>
|
||||
</template>
|
||||
</AButton>
|
||||
</template>
|
||||
</AInput>
|
||||
</div>
|
||||
<div class="phoalbum-content">
|
||||
<ATabs size="small" :tabBarGutter="50" type="line" tabPosition="top" :tabBarStyle="{position:'unset'}"
|
||||
style="width: 100%;">
|
||||
<template #rightExtra>
|
||||
<span style="color: #999; font-size: 12px;">已全部加载,共 0 个相册</span>
|
||||
</template>
|
||||
<ATabPane key="1" tab="全部相册">
|
||||
<div class="phoalbum-item-container">
|
||||
<div class="phoalbum-item" @mouseover="isHovered = true" @mouseleave="isHovered = false">
|
||||
<PhotoStack :src="'/test/1.png'" default-src=""/>
|
||||
<div class="phoalbum-item-info">
|
||||
<span class="phoalbum-item-name">我的相册</span>
|
||||
<span class="phoalbum-item-date">2022-01-01</span>
|
||||
</div>
|
||||
<div class="phoalbum-item-operation" :class="{ 'fade-in': isHovered, 'fade-out': !isHovered }">
|
||||
<ADropdown trigger="click">
|
||||
<AButton type="text" shape="circle" size="small">
|
||||
<template #icon>
|
||||
<AAvatar shape="circle" size="small" :src="more"/>
|
||||
</template>
|
||||
</AButton>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<AMenuItem key="1">重命名相册</AMenuItem>
|
||||
<AMenuItem key="2">分享相册</AMenuItem>
|
||||
<AMenuItem key="3">删除相册</AMenuItem>
|
||||
<AMenuItem key="4">下载相册</AMenuItem>
|
||||
</AMenu>
|
||||
</template>
|
||||
</ADropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ATabPane>
|
||||
<ATabPane key="2" tab="我的相册">
|
||||
|
||||
</ATabPane>
|
||||
<ATabPane key="3" tab="收藏相册">
|
||||
</ATabPane>
|
||||
</ATabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import more from "@/assets/svgs/more.svg";
|
||||
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
const isHovered = ref<boolean>(false);
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.phoalbum {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.phoalbum-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
|
||||
.phoalbum-search {
|
||||
width: 300px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.phoalbum-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: calc(100% - 65px);
|
||||
|
||||
.phoalbum-item-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
.phoalbum-item {
|
||||
width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.phoalbum-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
.phoalbum-item-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.phoalbum-item-date {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.phoalbum-item-operation {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
opacity: 1; /* 显示时透明度为1 */
|
||||
transform: scale(1); /* 显示时缩放为1 */
|
||||
z-index: 10; /* 显示时z-index为10 */
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
opacity: 0; /* 隐藏时透明度为0 */
|
||||
transform: scale(0); /* 隐藏时缩放为0 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
11
src/views/Album/ThingAlbum/Detail.vue
Normal file
11
src/views/Album/ThingAlbum/Detail.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
11
src/views/Album/ThingAlbum/Index.vue
Normal file
11
src/views/Album/ThingAlbum/Index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
@@ -1,11 +1,120 @@
|
||||
<template>
|
||||
<div class="thing-album">
|
||||
<div class="thing-album-header">
|
||||
|
||||
<AButton type="link" size="large" class="thing-album-button">事物</AButton>
|
||||
|
||||
</div>
|
||||
<div class="thing-album-content">
|
||||
<span class="thing-album-title">动物</span>
|
||||
<div class="thing-album-container">
|
||||
<img class="background-image" src="/test/7.png" alt=""/>
|
||||
<div class="overlay">
|
||||
<span>猫</span>
|
||||
<span class="thing-album-overlay-count">---</span>
|
||||
<span class="thing-album-overlay-count">16张照片</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.thing-album {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
<template>
|
||||
.thing-album-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
|
||||
</template>
|
||||
.thing-album-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
.thing-album-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-top: 20px;
|
||||
padding-left: 25px;
|
||||
gap: 20px;
|
||||
|
||||
.thing-album-title {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.thing-album-container {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.background-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.2); /* 黑色半透明 */
|
||||
backdrop-filter: blur(2px); /* 背景虚化 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s ease, backdrop-filter 0.3s ease;
|
||||
gap: 0;
|
||||
|
||||
.thing-album-overlay-count {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1); /* 黑色半透明 */
|
||||
backdrop-filter: blur(0px); /* 背景虚化 */
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.thing-album-container:hover {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="all-photo">
|
||||
<div class="photo-header">
|
||||
<AButton type="primary" shape="round" size="middle">
|
||||
<AButton type="primary" shape="round" size="middle" @click="upload.openUploadDrawer = true">
|
||||
<template #icon>
|
||||
<PlusOutlined/>
|
||||
</template>
|
||||
@@ -83,11 +83,13 @@
|
||||
:breakpoints="breakpoints">
|
||||
<template #default="{ item, url, index }">
|
||||
<CheckCard :key="index"
|
||||
class="photo-item"
|
||||
margin="0"
|
||||
border-radius="0"
|
||||
v-model="selected"
|
||||
:showHoverCircle="true"
|
||||
:iconSize="20"
|
||||
:showSelectedEffect="true"
|
||||
:value="url">
|
||||
<AImage :src="url"
|
||||
:alt="item.title"
|
||||
@@ -113,6 +115,7 @@
|
||||
</ATabPane>
|
||||
</ATabs>
|
||||
</div>
|
||||
<Upload/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -121,9 +124,12 @@ import {Waterfall} from 'vue-waterfall-plugin-next';
|
||||
import 'vue-waterfall-plugin-next/dist/style.css';
|
||||
import loading from '@/assets/gif/loading.gif';
|
||||
import error from '@/assets/svgs/no-image.svg';
|
||||
import Upload from "@/views/Photograph/Upload/Upload.vue";
|
||||
import useStore from "@/store";
|
||||
|
||||
const selected = ref<(string | number)[]>([]);
|
||||
const switchValue = ref<boolean>(false);
|
||||
const upload = useStore().upload;
|
||||
const breakpoints = reactive({
|
||||
breakpoints: {
|
||||
1200: {
|
||||
@@ -162,6 +168,11 @@ function loadImages() {
|
||||
tag: '全部',
|
||||
date: '2022-01-01',
|
||||
});
|
||||
images.value.push({
|
||||
title: `image-${i}`,
|
||||
link: '',
|
||||
src: `/test/${i}.png`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,4 +284,10 @@ onBeforeMount(() => { // 组件已完成响应式状态设置,但未创建DOM
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.photo-item:hover {
|
||||
transition: all 0.3s ease-in-out, transform 0.3s ease-in-out;
|
||||
//transform: scale(0.99);
|
||||
box-shadow: 0 0 10px 0 rgba(77, 167, 255, 0.89);
|
||||
}
|
||||
</style>
|
||||
|
@@ -15,23 +15,125 @@
|
||||
</AButton>
|
||||
</div>
|
||||
<div class="photo-list">
|
||||
<CheckCard style="margin-top: 20px;" v-for="item in items" :key="item.id" :value="item.id" v-model="selectedItems">
|
||||
{{ item.name }}
|
||||
</CheckCard>
|
||||
<div>Selected Items: {{ selectedItems }}</div>
|
||||
<div style="width:100%;height:100%;" v-if="images.length !== 0">
|
||||
<span style="margin-left: 10px;font-size: 13px">2024年12月27日 星期日</span>
|
||||
<AImagePreviewGroup>
|
||||
<Waterfall :list="images"
|
||||
:backgroundColor="`transparent`"
|
||||
:width="400"
|
||||
:gutter="15"
|
||||
align="left"
|
||||
:lazyload="true"
|
||||
:animationDelay="300"
|
||||
:animationDuration="1000"
|
||||
:animationCancel="false"
|
||||
:hasAroundGutter="true"
|
||||
rowKey="id"
|
||||
:imgSelector="'src'"
|
||||
:loadProps="loadProps"
|
||||
:breakpoints="breakpoints">
|
||||
<template #default="{ item, url, index }">
|
||||
<CheckCard :key="index"
|
||||
margin="0"
|
||||
border-radius="0"
|
||||
v-model="selected"
|
||||
:showHoverCircle="true"
|
||||
:iconSize="20"
|
||||
:value="url">
|
||||
<AImage :src="url"
|
||||
:alt="item.title"
|
||||
:key="index"
|
||||
:previewMask="false"
|
||||
loading="lazy"/>
|
||||
</CheckCard>
|
||||
</template>
|
||||
</Waterfall>
|
||||
</AImagePreviewGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const items = ref([
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
]);
|
||||
import {Waterfall} from 'vue-waterfall-plugin-next';
|
||||
import 'vue-waterfall-plugin-next/dist/style.css';
|
||||
import loading from '@/assets/gif/loading.gif';
|
||||
import error from '@/assets/svgs/no-image.svg';
|
||||
|
||||
const selectedItems = ref<(string | number)[]>([]);
|
||||
const selected = ref<(string | number)[]>([]);
|
||||
const breakpoints = reactive({
|
||||
breakpoints: {
|
||||
1200: {
|
||||
// 当屏幕宽度小于等于1200
|
||||
rowPerView: 4,
|
||||
},
|
||||
800: {
|
||||
// 当屏幕宽度小于等于800
|
||||
rowPerView: 3,
|
||||
},
|
||||
500: {
|
||||
// 当屏幕宽度小于等于500
|
||||
rowPerView: 2,
|
||||
},
|
||||
},
|
||||
});
|
||||
const loadProps = reactive({
|
||||
loading,
|
||||
error,
|
||||
ratioCalculator: (_width: number, _height: number) => {
|
||||
// 我设置了最小宽高比
|
||||
const minRatio = 3 / 4;
|
||||
const maxRatio = 4 / 3;
|
||||
return Math.random() > 0.5 ? minRatio : maxRatio;
|
||||
},
|
||||
});
|
||||
|
||||
const images = ref<any[]>([]);
|
||||
|
||||
function loadImages() {
|
||||
for (let i = 1; i < 10; i++) {
|
||||
images.value.push({
|
||||
title: `image-${i}`,
|
||||
link: '',
|
||||
src: `https://cdn.jsdelivr.net/gh/themusecatcher/resources@0.0.5/${i}.jpg`,
|
||||
tag: '全部',
|
||||
date: '2022-01-01',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => { // 组件已完成响应式状态设置,但未创建DOM节点
|
||||
loadImages();
|
||||
});
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
<style scoped lang="scss">
|
||||
.recent-upload {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.photo-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
}
|
||||
|
||||
.photo-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: calc(100% - 65px);
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,19 +0,0 @@
|
||||
.recent-upload {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.photo-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
}
|
||||
}
|
74
src/views/Photograph/Upload/Upload.vue
Normal file
74
src/views/Photograph/Upload/Upload.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<ADrawer v-model:open="upload.openUploadDrawer" width="40%" placement="right" title="上传照片">
|
||||
<template #extra>
|
||||
<AFlex :vertical="false" justify="center" align="center" gap="large">
|
||||
<ASelect size="middle" style="width: 150px">
|
||||
|
||||
</ASelect>
|
||||
<ASelect size="middle" style="width: 150px">
|
||||
|
||||
</ASelect>
|
||||
</AFlex>
|
||||
</template>
|
||||
<div>
|
||||
<AUploadDragger
|
||||
v-model:fileList="fileList"
|
||||
accept="image/*"
|
||||
name="file"
|
||||
:directory="false"
|
||||
:multiple="true"
|
||||
@drop="handleDrop"
|
||||
:beforeUpload="upload.beforeUpload"
|
||||
:customRequest="upload.customUploadRequest"
|
||||
:progress="progress"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<inbox-outlined></inbox-outlined>
|
||||
</p>
|
||||
<p class="ant-upload-text">Click or drag file to this area to upload</p>
|
||||
<p class="ant-upload-hint">
|
||||
Support for a single or bulk upload. Strictly prohibit from uploading company data or other
|
||||
band files
|
||||
</p>
|
||||
</AUploadDragger>
|
||||
</div>
|
||||
</ADrawer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import useStore from "@/store";
|
||||
import {InboxOutlined} from '@ant-design/icons-vue';
|
||||
import type {UploadProps} from 'ant-design-vue';
|
||||
|
||||
const upload = useStore().upload;
|
||||
|
||||
|
||||
const fileList = ref([]);
|
||||
// const handleChange = (info: UploadChangeParam) => {
|
||||
// const status = info.file.status;
|
||||
// if (status !== 'uploading') {
|
||||
// console.log(info.file, info.fileList);
|
||||
// }
|
||||
// if (status === 'done') {
|
||||
// message.success(`${info.file.name} file uploaded successfully.`);
|
||||
// } else if (status === 'error') {
|
||||
// message.error(`${info.file.name} file upload failed.`);
|
||||
// }
|
||||
// };
|
||||
|
||||
function handleDrop(e: DragEvent) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
const progress: UploadProps['progress'] = {
|
||||
strokeColor: {
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
},
|
||||
strokeWidth: 3,
|
||||
format: (percent: any) => `${parseFloat(percent.toFixed(2))}%`,
|
||||
class: 'progress-bar',
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
</style>
|
@@ -110,11 +110,8 @@ import deleted from '@/assets/svgs/deleted.svg';
|
||||
import getImageSizeWithUnit from "@/utils/imageUtils/getImageSizeWithUnit.ts";
|
||||
|
||||
const canvasContainer = ref<HTMLDivElement | null>(null);
|
||||
const dragging = ref<boolean>(false);
|
||||
const linePosition = ref(0);
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||
const draggingLine = ref(false);
|
||||
const imgX = ref(0);
|
||||
const imgY = ref(0);
|
||||
const imgScale = ref(1);
|
||||
@@ -197,8 +194,8 @@ function deletedImage() {
|
||||
store.isProcessing = false;
|
||||
store.progressBar = 0;
|
||||
store.msg = "";
|
||||
draggingLine.value = false;
|
||||
dragging.value = false;
|
||||
store.draggingLine = false;
|
||||
store.dragging = false;
|
||||
imgX.value = 0;
|
||||
imgY.value = 0;
|
||||
imgScale.value = 1;
|
||||
@@ -217,11 +214,11 @@ function startDragging(event: any) {
|
||||
if (canvas.value) {
|
||||
const rect = canvas.value.getBoundingClientRect();
|
||||
const mouseX = event.clientX - rect.left;
|
||||
if (Math.abs(mouseX - linePosition.value / dpr) < 12) {
|
||||
if (Math.abs(mouseX - store.linePosition / dpr) < 12) {
|
||||
startDraggingLine(event);
|
||||
return;
|
||||
}
|
||||
dragging.value = true;
|
||||
store.dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,11 +226,11 @@ function startDragging(event: any) {
|
||||
* 停止拖动
|
||||
*/
|
||||
function stopDragging() {
|
||||
if (draggingLine.value) {
|
||||
if (store.draggingLine) {
|
||||
stopDraggingLine();
|
||||
return;
|
||||
}
|
||||
dragging.value = false;
|
||||
store.dragging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,12 +238,12 @@ function stopDragging() {
|
||||
* @param event
|
||||
*/
|
||||
function dragImage(event: any) {
|
||||
if (dragging.value) {
|
||||
if (store.dragging) {
|
||||
imgX.value += event.movementX * dpr;
|
||||
imgY.value += event.movementY * dpr;
|
||||
drawImage();
|
||||
}
|
||||
if (draggingLine.value) {
|
||||
if (store.draggingLine) {
|
||||
updateLinePosition(event);
|
||||
drawImage();
|
||||
}
|
||||
@@ -290,9 +287,9 @@ function touchStart(event: any) {
|
||||
touchStartImgY.value = imgY.value;
|
||||
if (event.touches.length == 1) {
|
||||
if (
|
||||
Math.abs(event.touches[0].clientX - linePosition.value / dpr) < 12
|
||||
Math.abs(event.touches[0].clientX - store.linePosition / dpr) < 12
|
||||
) {
|
||||
draggingLine.value = true;
|
||||
store.draggingLine = true;
|
||||
return;
|
||||
}
|
||||
touchStartX.value = event.touches[0].clientX * dpr;
|
||||
@@ -332,7 +329,7 @@ function touchMove(event: any) {
|
||||
touchStartY.value +
|
||||
touchStartImgY.value -
|
||||
imgY.value;
|
||||
if (draggingLine.value) {
|
||||
if (store.draggingLine) {
|
||||
updateLinePosition(event.touches[0]);
|
||||
drawImage();
|
||||
return;
|
||||
@@ -402,7 +399,7 @@ function touchEnd(event: any) {
|
||||
return;
|
||||
}
|
||||
touching.value = false;
|
||||
draggingLine.value = false;
|
||||
store.draggingLine = false;
|
||||
touchStartImgX.value = 0;
|
||||
touchStartImgY.value = 0;
|
||||
touchStartX.value = 0;
|
||||
@@ -416,14 +413,14 @@ function touchEnd(event: any) {
|
||||
*/
|
||||
function startDraggingLine(event: any) {
|
||||
event.preventDefault();
|
||||
draggingLine.value = true;
|
||||
store.draggingLine = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止拖动线
|
||||
*/
|
||||
function stopDraggingLine() {
|
||||
draggingLine.value = false;
|
||||
store.draggingLine = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,17 +451,17 @@ function drawImage_() {
|
||||
ctx.drawImage(
|
||||
processedImg.value,
|
||||
((processedImg.value.width / img.value.width) *
|
||||
(linePosition.value - imgX.value)) /
|
||||
(store.linePosition - imgX.value)) /
|
||||
imgScale.value,
|
||||
0,
|
||||
processedImg.value.width -
|
||||
((processedImg.value.width / img.value.width) *
|
||||
(linePosition.value - imgX.value)) /
|
||||
(store.linePosition - imgX.value)) /
|
||||
imgScale.value,
|
||||
processedImg.value.height,
|
||||
linePosition.value,
|
||||
store.linePosition,
|
||||
imgY.value,
|
||||
imgX.value + img.value.width * imgScale.value - linePosition.value,
|
||||
imgX.value + img.value.width * imgScale.value - store.linePosition,
|
||||
img.value.height * imgScale.value
|
||||
);
|
||||
}
|
||||
@@ -488,7 +485,7 @@ function updateLinePosition(event: any) {
|
||||
newPosition = Math.max(0, Math.min(newPosition, containerRect.width - lineWidth));
|
||||
|
||||
// 更新线的位置
|
||||
linePosition.value = newPosition * dpr;
|
||||
store.linePosition = newPosition * dpr;
|
||||
const line: any = dragLine.value;
|
||||
line.style.left = Math.floor(newPosition) + "px";
|
||||
}
|
||||
@@ -500,7 +497,7 @@ function updateLinePosition(event: any) {
|
||||
*/
|
||||
function dragLineFn(event: any) {
|
||||
event.preventDefault();
|
||||
if (draggingLine.value) {
|
||||
if (store.draggingLine) {
|
||||
requestAnimationFrame(() => {
|
||||
updateLinePosition(event);
|
||||
drawImage();
|
||||
@@ -522,8 +519,8 @@ function updateCanvasSize() {
|
||||
// canvas.value.height) * container.offsetHeight * dpr - (img.value.height * imgScale.value) / 2;
|
||||
imgX.value = (container.offsetWidth * dpr - img.value.width * imgScale.value) / 2;
|
||||
imgY.value = (container.offsetHeight * dpr - img.value.height * imgScale.value) / 2;
|
||||
linePosition.value = (linePosition.value / canvas.value.width) * container.offsetWidth * dpr;
|
||||
// dragLine.value.style.left = linePosition.value / dpr + "px";
|
||||
store.linePosition = (store.linePosition / canvas.value.width) * container.offsetWidth * dpr;
|
||||
// dragLine.value.style.left = store.linePosition / dpr + "px";
|
||||
}
|
||||
if (canvas.value) {
|
||||
canvas.value.width = container.offsetWidth * dpr;
|
||||
|
Reference in New Issue
Block a user