🎨 update

This commit is contained in:
2024-12-08 01:10:08 +08:00
parent dbdfd835bd
commit 4d9b23c443
73 changed files with 1248 additions and 328 deletions

6
components.d.ts vendored
View File

@@ -80,6 +80,7 @@ declare module 'vue' {
CommentList: typeof import('./src/components/CommentReply/src/CommentList/CommentList.vue')['default']
CommentOutlined: typeof import('@ant-design/icons-vue')['CommentOutlined']
CommentReply: typeof import('./src/components/CommentReply/index.vue')['default']
CompareImage: typeof import('./src/components/ImageCompare/CompareImage.vue')['default']
Countdown: typeof import('./src/components/MyUI/Countdown/Countdown.vue')['default']
CustomerServiceOutlined: typeof import('@ant-design/icons-vue')['CustomerServiceOutlined']
DatePicker: typeof import('./src/components/MyUI/DatePicker/DatePicker.vue')['default']
@@ -100,6 +101,9 @@ declare module 'vue' {
GaugeChart: typeof import('./src/components/MyUI/GaugeChart/GaugeChart.vue')['default']
GradientText: typeof import('./src/components/MyUI/GradientText/GradientText.vue')['default']
Image: typeof import('./src/components/MyUI/Image/Image.vue')['default']
ImageCompare: typeof import('./src/components/ImageCompare/ImageCompare.vue')['default']
ImageInPainting: typeof import('./src/views/Upscale/Upscale.vue')['default']
ImageShare: typeof import('./src/views/ImageShare/ImageShare.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']
@@ -170,9 +174,11 @@ declare module 'vue' {
TreeChart: typeof import('./src/components/MyUI/TreeChart/TreeChart.vue')['default']
UngroupOutlined: typeof import('@ant-design/icons-vue')['UngroupOutlined']
Upload: typeof import('./src/components/MyUI/Upload/Upload.vue')['default']
Upscale: typeof import('./src/views/Upscale/Upscale.vue')['default']
UserInfoCard: typeof import('./src/components/CommentReply/src/UserInfoCard/UserInfoCard.vue')['default']
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
Video: typeof import('./src/components/MyUI/Video/Video.vue')['default']
VueCompareImage: typeof import('./src/components/VueCompareImage/VueCompareImage.vue')['default']
WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined']
Waterfall: typeof import('./src/components/MyUI/Waterfall/Waterfall.vue')['default']
}

View File

@@ -13,6 +13,8 @@
"@alova/adapter-axios": "^2.0.11",
"@ant-design/icons-vue": "^7.0.1",
"@tensorflow/tfjs": "^4.22.0",
"@tensorflow/tfjs-backend-webgpu": "^4.22.0",
"@tensorflow/tfjs-core": "^4.22.0",
"@types/animejs": "^3.1.12",
"@types/crypto-js": "^4.2.2",
"@types/json-stringify-safe": "^5.0.3",
@@ -63,7 +65,7 @@
"typescript": "^5.7.2",
"typescript-eslint": "^8.17.0",
"unplugin-vue-components": "^0.27.5",
"vite": "^6.0.2",
"vite": "^6.0.3",
"vite-plugin-bundle-obfuscator": "1.3.2",
"vite-plugin-chunk-split": "^0.5.0",
"vue-tsc": "^2.1.10"

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.

File diff suppressed because one or more lines are too long

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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -0,0 +1 @@
<svg t="1733505571634" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="27271" width="200" height="200"><path d="M325.315 764.323V255.492c0-28.16 22.598-50.934 50.757-50.934 13.093 0 24.932 5.024 33.901 13.092l335.755 251.633c22.24 16.859 26.905 48.607 10.044 71.024-2.871 3.947-6.281 7.355-10.045 10.045l-339.338 254.51c-22.241 16.676-54.16 12.193-70.844-10.225-6.996-9.15-10.225-19.73-10.225-30.31z" fill="#515151" p-id="27272"></path></svg>

After

Width:  |  Height:  |  Size: 487 B

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

@@ -0,0 +1 @@
<svg t="1733416434686" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="29741" width="200" height="200"><path d="M512.009309 1023.999069a508.59241 508.59241 0 0 1-199.214364-40.233854 510.137718 510.137718 0 0 1-162.759998-109.735464 510.137718 510.137718 0 0 1-109.716845-162.741379 509.076483 509.076483 0 0 1-40.233855-199.214365 509.076483 509.076483 0 0 1 40.233855-199.214364 509.988773 509.988773 0 0 1 109.716845-162.741379A510.137718 510.137718 0 0 1 312.794945 40.3828 508.59241 508.59241 0 0 1 512.009309 0.148945a508.815828 508.815828 0 0 1 199.214364 40.233855 510.137718 510.137718 0 0 1 162.759998 109.735464 509.988773 509.988773 0 0 1 109.716846 162.741379 509.076483 509.076483 0 0 1 40.233854 199.214364 509.076483 509.076483 0 0 1-40.233854 199.214365 510.137718 510.137718 0 0 1-109.716846 162.741379 510.137718 510.137718 0 0 1-162.759998 109.735464A508.815828 508.815828 0 0 1 512.009309 1023.999069z m5.734395-814.99155a285.826067 285.826067 0 0 0-210.2177 85.643559 290.201336 290.201336 0 0 0-84.936068 211.893334 293.906351 293.906351 0 0 0 81.919925 214.872241 285.677122 285.677122 0 0 0 211.520972 82.999779 541.658271 541.658271 0 0 0 119.156255-12.008716v-42.821779a453.631588 453.631588 0 0 1-118.876983 14.70835 278.955965 278.955965 0 0 1-101.692416-17.705875 225.577686 225.577686 0 0 1-80.05811-53.099006 256.409367 256.409367 0 0 1-69.129246-186.330594 275.25095 275.25095 0 0 1 17.966529-100.817363 247.826393 247.826393 0 0 1 53.825115-82.683271 239.094474 239.094474 0 0 1 81.06349-55.240095 262.832634 262.832634 0 0 1 99.6258-18.413365 280.873636 280.873636 0 0 1 100.631182 16.961148 204.53916 204.53916 0 0 1 76.18553 50.864827c42.41218 44.962868 63.934778 107.315102 63.934779 185.343831a275.04615 275.04615 0 0 1-30.366227 136.545622h-85.829741V348.569283h-49.058864v19.027765a194.168842 194.168842 0 0 0-90.205009-25.078668 130.196827 130.196827 0 0 0-101.692417 46.936393 180.130745 180.130745 0 0 0-40.959962 122.35858 192.977279 192.977279 0 0 0 34.071241 120.887745 110.703608 110.703608 0 0 0 91.843408 43.771305 167.861375 167.861375 0 0 0 106.216631-46.433703l10.333081 43.734069h143.601906a296.196385 296.196385 0 0 0 53.750642-172.646243c0-88.641083-25.618595-160.265163-76.129676-212.880097s-119.826509-79.23891-206.512685-79.23891z m-17.333512 419.765146a75.589749 75.589749 0 0 1-64.884304-29.472555 149.727282 149.727282 0 0 1-21.61569-88.417665 140.771945 140.771945 0 0 1 26.195758-90.838027 89.497519 89.497519 0 0 1 72.983207-32.041862 193.628915 193.628915 0 0 1 79.834691 20.033146v183.351688a157.528293 157.528293 0 0 1-92.53228 37.366657z" fill="#48B596" p-id="29742"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1 @@
<svg t="1733459147421" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21619" width="200" height="200"><path d="M742.56 674.816l148.8 63.776a32 32 0 0 1 0 58.816l-328.96 140.992a128 128 0 0 1-100.8 0l-328.96-140.992a32 32 0 0 1 0-58.816l148.8-63.776 154.912 66.4a192 192 0 0 0 142.72 3.424l8.576-3.424 154.912-66.4z" fill="#3796FB" p-id="21620"></path><path d="M742.56 418.816l148.8 63.776a32 32 0 0 1 0 58.816l-328.96 140.992a128 128 0 0 1-100.8 0l-328.96-140.992a32 32 0 0 1 0-58.816l148.8-63.776 154.912 66.4a192 192 0 0 0 142.72 3.424l8.576-3.424 154.912-66.4z" fill="#11D6AF" p-id="21621"></path><path d="M461.568 85.6L132.608 226.592a32 32 0 0 0 0 58.816l328.96 140.992a128 128 0 0 0 100.864 0l328.96-140.992a32 32 0 0 0 0-58.816l-328.96-140.992a128 128 0 0 0-100.864 0z" fill="#3796FB" p-id="21622"></path></svg>

After

Width:  |  Height:  |  Size: 864 B

View File

@@ -0,0 +1 @@
<svg t="1733418592324" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="58999" width="200" height="200"><path d="M510.577931 0.04352a37.944035 37.944035 0 0 1 32.271118 18.964337 38.571231 38.571231 0 0 1 0 37.928676 317.39154 317.39154 0 0 0 277.128161 473.102372 311.462624 311.462624 0 0 0 148.01553-36.963563 38.65571 38.65571 0 0 1 37.959396 0.931833 39.730902 39.730902 0 0 1 18.032504 33.202951c-3.793892 133.679637-58.820679 257.877106-154.66892 351.741362a505.852206 505.852206 0 0 1-356.843403 145.040832c-282.788279 0-512.456957-229.422919-512.456957-511.95264C0.01536 230.439232 228.718925 0.975353 510.577931 0z m417.33575 267.167276a20.072809 20.072809 0 0 1 20.249448 19.798891c0 29.634338 25.264451 54.384232 55.605343 54.384233a19.773292 19.773292 0 1 1 0 39.536343c-30.343452 0-55.605343 24.749894-55.605343 54.384232a20.254568 20.254568 0 0 1-40.498896 0c0-29.667617-25.264451-54.384232-55.605343-54.384232a19.773292 19.773292 0 1 1 0.03328-39.536343l1.927665-0.064h0.995833c29.025062-1.535988 52.681845-25.617728 52.681844-54.320233a20.072809 20.072809 0 0 1 20.249449-19.798891h-0.03328zM736.570476 0.04352a19.773292 19.773292 0 0 1 19.734892 19.711852 127.95808 127.95808 0 0 0 128.216638 128.255038 19.719532 19.719532 0 1 1 0 39.439064 127.95808 127.95808 0 0 0-128.216638 128.196159 19.734892 19.734892 0 0 1-39.469784 0 127.95808 127.95808 0 0 0-128.183359-128.255038 19.798892 19.798892 0 0 1-19.199856-15.103887 19.768172 19.768172 0 0 1 19.199856-24.319818A127.95808 127.95808 0 0 0 716.835584 19.745132a19.793772 19.793772 0 0 1 19.734892-19.711852z" fill="#222222" p-id="59000"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

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

@@ -0,0 +1 @@
<svg t="1733417025409" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="48933" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#66BF81" p-id="48934"></path><path d="M423.563636 466.618182c0-10.472727 6.981818-20.945455 18.618182-23.272727 48.872727-13.963636 69.818182-64 69.818182-130.327273v-11.636364c0-34.909091 25.6-65.163636 60.509091-68.654545 39.563636-3.490909 73.309091 27.927273 73.309091 66.327272 0 32.581818-1.163636 84.945455-9.309091 121.018182h62.836364c9.309091 0 16.290909 1.163636 24.436363 3.490909 50.036364 12.8 79.127273 65.163636 66.327273 115.2-11.636364 43.054545-69.818182 188.509091-72.145455 195.490909 0 1.163636-1.163636 1.163636-1.163636 1.163637-11.636364 19.781818-32.581818 32.581818-54.690909 32.581818-1.163636-1.163636-2.327273-1.163636-3.490909-1.163636H446.836364c-13.963636 0-24.436364-10.472727-24.436364-24.436364V466.618182z m233.890909 300.218182M377.018182 766.836364h-67.490909c-30.254545 0-53.527273-23.272727-53.527273-53.527273V499.2c0-30.254545 23.272727-53.527273 53.527273-53.527273h66.327272c8.145455 0 12.8 5.818182 12.8 12.8v294.4c1.163636 8.145455-4.654545 13.963636-11.636363 13.963637z m-67.490909-294.4" fill="#FFFFFF" p-id="48935"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg t="1733480492069" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="86870" width="200" height="200"><path d="M446.250667 837.717333c0 1.578667 0.213333 5.205333 0.554666 11.264 0.341333 6.058667 0.426667 11.050667 0.213334 14.933334-0.213333 3.84-0.682667 8.362667-1.664 13.226666a19.242667 19.242667 0 0 1-5.76 11.008 16.512 16.512 0 0 1-11.477334 3.626667H248.192c-44.672 0-82.858667-15.872-114.645333-47.530667a155.648 155.648 0 0 1-47.530667-114.517333V333.397333c0-44.672 15.872-82.858667 47.530667-114.602666 31.786667-31.786667 69.845333-47.573333 114.645333-47.573334h180.181333c4.821333 0 9.130667 1.792 12.672 5.333334a17.450667 17.450667 0 0 1 5.290667 12.672c0 1.536 0.213333 5.162667 0.554667 11.221333 0.341333 6.101333 0.426667 11.050667 0.256 14.933333-0.256 3.84-0.682667 8.362667-1.706667 13.226667a19.285333 19.285333 0 0 1-5.717333 11.008 16.512 16.512 0 0 1-11.477334 3.669333h-180.053333a87.04 87.04 0 0 0-63.573333 26.368c-17.621333 17.621333-26.325333 38.826667-26.325334 63.530667v396.202667c0 24.746667 8.704 45.909333 26.368 63.573333 17.621333 17.621333 38.826667 26.325333 63.573334 26.325333h175.616c1.194667 0 3.285333 0.256 6.485333 0.554667a17.664 17.664 0 0 1 6.528 1.706667c1.194667 0.64 2.645333 1.706667 4.522667 3.072 1.877333 1.28 3.2 2.986667 3.84 5.077333 0.682667 2.56 1.024 5.077333 1.024 8.021333z m522.410666-306.261333a34.645333 34.645333 0 0 1-10.709333 25.386667l-306.176 306.261333a34.986667 34.986667 0 0 1-25.386667 10.666667 34.730667 34.730667 0 0 1-25.386666-10.666667 34.773333 34.773333 0 0 1-10.666667-25.386667v-162.048H338.218667a34.730667 34.730667 0 0 1-25.386667-10.709333 34.773333 34.773333 0 0 1-10.666667-25.386667V423.552c0-9.685333 3.498667-18.176 10.666667-25.386667a34.56 34.56 0 0 1 25.386667-10.666666h252.117333V225.28c0-9.685333 3.541333-18.176 10.666667-25.344a34.986667 34.986667 0 0 1 25.429333-10.709333c9.685333 0 18.176 3.541333 25.344 10.666666l306.304 306.304a34.816 34.816 0 0 1 10.581333 25.258667z" fill="#FE4444" p-id="86871"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg t="1733418660891" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="61751" width="200" height="200"><path d="M167.253 733.867H844.8l88.747 109.226c30.72 39.254 17.066 69.974-34.134 69.974h-793.6c-49.493 0-63.146-30.72-32.426-68.267l93.866-110.933zM435.2 158.72c44.373 0 58.027 29.013 58.027 49.493v235.52c0 20.48-25.6 42.667-47.787 42.667H199.68c-22.187 0-39.253-22.187-39.253-42.667V196.267c0-20.48 17.066-37.547 39.253-37.547H435.2z" fill="#DAE9FF" p-id="61752"></path><path d="M803.84 523.947c46.08 0 59.733 30.72 59.733 49.493v235.52c0 18.773-15.36 35.84-35.84 35.84h-235.52c-18.773 0-35.84-15.36-35.84-35.84V573.44c0-18.773 15.36-35.84 35.84-35.84l211.627-13.653z m-365.227 0c51.2 5.12 61.44 30.72 61.44 49.493v235.52c0 18.773-15.36 35.84-35.84 35.84h-235.52c-18.773 0-35.84-15.36-35.84-35.84V573.44c0-18.773 15.36-35.84 35.84-35.84l209.92-13.653z" fill="#DAE9FF" p-id="61753"></path><path d="M431.787 158.72c18.773 0 35.84 15.36 35.84 35.84v235.52c0 18.773-15.36 35.84-35.84 35.84H194.56c-18.773 0-35.84-15.36-35.84-35.84V194.56c0-18.773 15.36-35.84 35.84-35.84h237.227zM793.6 520.533c18.773 0 35.84 15.36 35.84 35.84v235.52c0 18.774-15.36 35.84-35.84 35.84H558.08c-18.773 0-35.84-15.36-35.84-35.84v-235.52c0-18.773 15.36-35.84 35.84-35.84H793.6z m-361.813 0c18.773 0 35.84 15.36 35.84 35.84v235.52c0 18.774-15.36 35.84-35.84 35.84H194.56c-18.773 0-35.84-15.36-35.84-35.84v-235.52c0-18.773 15.36-35.84 35.84-35.84h237.227z" fill="#3889FF" p-id="61754"></path><path d="M899.413 223.573l-194.56-102.4c-17.066-10.24-71.68-3.413-80.213 15.36l-90.453 223.574c-8.534 17.066-3.414 39.253 15.36 49.493l184.32 105.813c42.666 17.067 68.266-1.706 76.8-18.773L916.48 295.253c10.24-18.773 10.24-58.026-17.067-71.68z" fill="#FFD6C2" p-id="61755"></path><path d="M868.693 233.813L658.773 122.88c-17.066-8.533-39.253-1.707-47.786 15.36L500.053 348.16c-8.533 17.067-1.706 39.253 15.36 47.787l209.92 110.933c17.067 8.533 39.254 1.707 47.787-15.36L884.053 281.6c8.534-17.067 1.707-37.547-15.36-47.787z" fill="#FB560A" p-id="61756"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg t="1733480134293" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="81195" width="200" height="200"><path d="M332.100265 328.251268m-38.063325 34.890835a51.635134 51.635134 0 1 0 76.12665-69.78167 51.635134 51.635134 0 1 0-76.12665 69.78167Z" fill="#1AC3B6" p-id="81196"></path><path d="M556.783712 328.252893m-38.063325 34.890835a51.635134 51.635134 0 1 0 76.126651-69.78167 51.635134 51.635134 0 1 0-76.126651 69.78167Z" fill="#1AC3B6" p-id="81197"></path><path d="M511.987638 1024C229.669419 1024 0 794.293496 0 512S229.669419 0 511.987638 0s512.012362 229.694143 512.012362 512-229.681781 512-512.012362 512z m0-921.286011c-225.664204 0-409.273649 183.609445-409.273649 409.286011s183.609445 409.286011 409.273649 409.286011 409.298373-183.609445 409.298373-409.286011S737.664204 102.713989 511.987638 102.713989z" fill="#06E4D3" p-id="81198"></path><path d="M511.987638 846.892849V744.178859c128.018543 0 232.178859-104.160317 232.17886-232.178859h102.713989c0 184.635472-150.232652 334.892849-334.892849 334.892849z" fill="#1AC3B6" p-id="81199"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg t="1733480049006" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="80031" width="200" height="200"><path d="M627.2 972.8c19.456 19.456 51.2 25.6 76.8 12.8 45.056-19.456 83.456-38.4 121.856-70.656 19.456-19.456 32.256-44.544 25.6-76.8-6.656-38.4 0-83.456 19.456-121.856s57.344-64 96.256-76.8c25.6-6.656 44.544-32.256 51.2-57.344 6.656-45.056 6.656-96.256 0-140.8-6.656-25.6-25.6-51.2-51.2-57.856-38.4-12.8-76.8-38.4-96.256-76.8s-25.6-83.456-19.456-121.856c6.656-25.6-6.656-57.856-25.6-76.8-38.4-31.744-76.8-51.2-121.856-70.656-25.6-12.8-57.344-6.656-76.8 12.8C563.2 109.056 460.8 109.056 396.8 51.2c-19.456-19.456-51.2-25.6-76.8-12.8-44.544 18.944-83.456 38.4-121.856 70.656-25.6 19.456-32.256 45.056-25.6 76.8C192 268.8 140.8 358.4 57.856 384c-32.256 6.656-51.2 32.256-51.2 57.856C0 467.456 0 486.4 0 512s0 45.056 6.656 70.656c0 25.6 19.456 51.2 51.2 57.344 38.4 12.8 76.8 38.4 96.256 76.8s25.6 83.456 18.944 121.856c-6.656 25.6 6.656 57.344 25.6 76.8 38.4 32.256 76.8 51.2 121.856 70.656 25.6 12.8 57.856 6.656 76.8-12.8 63.488-64.512 165.888-64.512 229.888-0.512z" fill="#919BF2" p-id="80032"></path><path d="M512 512m-147.456 0a147.456 147.456 0 1 0 294.912 0 147.456 147.456 0 1 0-294.912 0Z" fill="#FFFFFF" p-id="80033"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg t="1733506078323" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="29321" width="200" height="200"><path d="M902.4 291.2L692.2 128.7c-44-34-107.9-2.6-107.9 53v45c-188.9 1.3-341.9 159.8-341.9 356 0 42.7 7.1 80.7 19 118.7C308.8 563.7 434.7 464 584.2 464v42.7c0 55.6 63.9 87 107.9 53l210.3-162.5c34.7-26.8 34.7-79.2 0-106z" fill="#2867CE" p-id="29322"></path><path d="M768.3 901.9H257.7c-93.1 0-168.5-75.5-168.5-168.5V353.6c0-93.1 75.5-168.5 168.5-168.5h49.6c26.6 0 48.1 21.5 48.1 48.1s-21.5 48.1-48.1 48.1h-49.6c-40 0-72.4 32.4-72.4 72.4v379.8c0 40 32.4 72.4 72.4 72.4h510.5c40 0 72.4-32.4 72.4-72.4v-132c0-26.6 21.5-48.1 48.1-48.1s48.1 21.5 48.1 48.1v132c0 93-75.5 168.4-168.5 168.4z" fill="#BDD2EF" p-id="29323"></path></svg>

After

Width:  |  Height:  |  Size: 774 B

View File

@@ -0,0 +1 @@
<svg t="1733416536479" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="35442" width="200" height="200"><path d="M511.390476 14.628571c282.453333 0 511.414857 223.085714 511.414857 498.322286 0 275.21219-228.961524 498.297905-511.414857 498.297905C228.961524 1011.248762 0 788.163048 0 512.950857 0 237.738667 228.937143 14.628571 511.390476 14.628571z" fill="#3BD1CA" p-id="35443"></path><path d="M507.782095 291.230476L369.152 377.904762h-69.168762a70.217143 70.217143 0 0 0-49.200762 19.651048 66.535619 66.535619 0 0 0-20.382476 47.811047v135.216762c-0.024381 17.944381 7.314286 35.157333 20.358095 47.835429 13.06819 12.678095 30.793143 19.72419 49.225143 19.602285h69.168762l138.630095 86.625524c19.577905 8.289524 34.791619-0.560762 34.791619-19.334095v-404.72381c0-18.822095-15.555048-27.428571-34.791619-19.358476z m116.809143 108.909714a17.67619 17.67619 0 0 0-24.015238-0.292571 16.579048 16.579048 0 0 0-1.29219 23.356952c3.218286 3.388952 8.752762 10.849524 14.384761 22.113524 9.752381 19.260952 15.60381 41.837714 15.60381 67.657143 0.24381 23.405714-5.12 46.567619-15.60381 67.632762-5.656381 11.288381-11.166476 18.748952-14.384761 22.113524a16.579048 16.579048 0 0 0-4.632381 16.822857 17.188571 17.188571 0 0 0 13.043809 11.873524 17.65181 17.65181 0 0 0 16.920381-5.607619c5.461333-5.656381 12.970667-15.798857 20.236191-30.281143 11.971048-23.722667 19.114667-51.346286 19.114666-82.578286a181.394286 181.394286 0 0 0-19.114666-82.553905c-7.314286-14.433524-14.799238-24.600381-20.260572-30.232381z m106.349714-20.870095c-13.994667-23.113143-28.135619-39.107048-38.058666-47.737905a17.578667 17.578667 0 0 0-24.502857 1.389715 16.505905 16.505905 0 0 0 1.414095 23.82019c1.633524 1.438476 4.900571 4.583619 9.264762 9.532953 7.509333 8.43581 14.994286 18.529524 22.040381 30.110476a222.110476 222.110476 0 0 1 32.231619 116.589714 222.061714 222.061714 0 0 1-54.296381 146.700191c-4.388571 4.949333-7.631238 8.094476-9.264762 9.532952a16.62781 16.62781 0 0 0-1.438476 23.844571c6.38781 6.948571 17.310476 7.582476 24.502857 1.414096 9.947429-8.630857 24.064-24.600381 38.083047-47.737905a254.537143 254.537143 0 0 0 37.059048-133.729524 254.439619 254.439619 0 0 0-37.034667-133.729524z" fill="#FFFFFF" p-id="35444"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -0,0 +1 @@
<svg t="1733461479786" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="46525" width="200" height="200"><path d="M939 485l-0.3-4.2c-0.2-2.9-0.4-5.7-0.7-8.6l-0.6-4.8c-0.3-2.6-0.6-5.2-1-7.8-0.2-1.6-0.5-3.3-0.8-4.9-0.4-2.6-0.9-5.1-1.4-7.7-0.3-1.6-0.6-3.1-1-4.7-0.6-2.6-1.2-5.2-1.8-7.7-0.4-1.5-0.7-2.9-1.1-4.3-0.7-2.7-1.5-5.4-2.3-8.1-0.4-1.3-0.8-2.5-1.1-3.7-0.9-2.9-1.9-5.9-3-8.8-0.3-0.9-0.7-1.9-1-2.8-1.3-3.4-2.6-6.8-4-10.2-0.2-0.4-0.3-0.7-0.4-1.1-28.1-66.4-82.9-119-151.1-144.1-0.4-0.1-0.7-0.3-1.1-0.4-3.4-1.2-6.9-2.4-10.4-3.5-0.8-0.2-1.5-0.5-2.3-0.7-3.1-0.9-6.2-1.8-9.4-2.7l-3.3-0.9c-2.8-0.7-5.7-1.4-8.5-2l-4.2-0.9c-2.6-0.5-5.2-1-7.8-1.4-1.6-0.3-3.3-0.6-5-0.8-0.4-0.1-0.8-0.1-1.2-0.2-57.2-68.2-142.5-108.3-232.1-108.3-145.4 0-269 102.8-297.2 242.9-59.2 38.5-96.2 105.4-96.2 177 0 116.3 94.6 210.9 210.9 210.9h377.3c144.8 0 262.6-117.8 262.6-262.6-0.2-4.3-0.3-8.6-0.5-12.9zM676.8 664.3H299.5c-63.3 0-114.7-51.5-114.7-114.7 0-44.4 24.8-84 64.8-103.3l23.7-11.5 3.1-26.2C288.6 304.5 377 225.9 482.1 225.9c35.9 0 70.7 9.5 101.3 26.7-11.9 4.5-23.4 9.8-34.5 16-23.2 12.9-31.5 42.2-18.5 65.4 12.9 23.2 42.2 31.5 65.4 18.5 24.6-13.7 52.6-21 81-21 2.8 0 5.6 0.1 8.4 0.2h0.5l3.9 0.3c1.6 0.1 3.2 0.2 4.7 0.4 1.8 0.2 3.6 0.4 5.4 0.7 0.9 0.1 1.8 0.2 2.6 0.3 52.1 8 96.4 40.4 120.7 85 0.1 0.1 0.1 0.2 0.2 0.3 1.2 2.2 2.3 4.4 3.4 6.6 0.1 0.3 0.2 0.5 0.4 0.8 1 2.1 1.9 4.2 2.8 6.3 0.2 0.4 0.4 0.8 0.5 1.2l2.4 6c0.2 0.6 0.4 1.1 0.6 1.7 0.7 1.9 1.3 3.8 1.9 5.7 0.2 0.7 0.5 1.4 0.7 2 0.6 1.8 1.1 3.6 1.6 5.5 0.2 0.8 0.5 1.6 0.7 2.4 0.4 1.8 0.8 3.5 1.2 5.3l0.6 2.7c0.4 1.7 0.6 3.5 0.9 5.2 0.2 1 0.3 1.9 0.5 2.9 0.3 1.8 0.5 3.5 0.7 5.3 0.1 1 0.3 1.9 0.4 2.9 0.2 1.9 0.3 3.8 0.4 5.7 0.1 0.9 0.1 1.7 0.2 2.6 0.1 2.8 0.2 5.6 0.2 8.4-0.1 91.7-74.8 166.4-166.5 166.4z" fill="#BDD2EF" p-id="46526"></path><path d="M478.2 425.8L314.1 589.9c-22.9 22.9-22.9 60.2 0 83.1 22.9 22.9 60.2 22.9 83.1 0l63.8-63.8v228.7c0 32.5 26.3 58.8 58.8 58.8s58.8-26.3 58.8-58.8V609.2l63.8 63.8c11.5 11.5 26.5 17.2 41.5 17.2s30.1-5.7 41.5-17.2c22.9-22.9 22.9-60.2 0-83.1L561.3 425.8c-22.9-22.9-60.1-22.9-83.1 0z" fill="#2867CE" p-id="46527"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,6 +1,6 @@
<template>
<div class="folder-wrapper" @click="handleClick">
<div class="download" style="--scale-pages: 1; --scale-folder: 1;">
<div ref="download" class="download" style="--scale-pages: 1; --scale-folder: 1;">
<svg class="folder-back" viewBox="0 0 48 48">
<path d="
M 3.50 7.50
@@ -19,7 +19,7 @@
</svg>
<div class="page-1"></div>
<div class="page-2"></div>
<svg class="folder-front" viewBox="0 0 48 48">
<svg ref="folderFront" class="folder-front" viewBox="0 0 48 48">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#47DB99;stop-opacity:1"></stop>
@@ -52,7 +52,7 @@
<script setup lang="ts">
import {gsap} from 'gsap';
import {onMounted, ref} from 'vue';
import {ref} from 'vue';
const download = ref<Element | null>(null);
const folderFront = ref<Element | null>(null);
@@ -68,7 +68,7 @@ const keyframes = [
/* 5 */2.00 //s
];
const timespan = (start, end) => ({
const timespan = (start: number, end: number) => ({
delay: keyframes[start] * (1 / playspeed),
duration: (keyframes[end] - keyframes[start]) * (1 / playspeed)
});
@@ -142,11 +142,6 @@ const handleClick = () => {
...timespan(4, 5)
});
};
onMounted(() => {
download.value = document.querySelector('.download');
folderFront.value = document.querySelector('.folder-front');
});
</script>
<style lang="scss">
.folder-wrapper {

View File

@@ -0,0 +1,446 @@
<script setup lang="ts">
// utilities
import type {CSSProperties} from 'vue';
import {computed, getCurrentInstance, onBeforeUnmount, onMounted, ref, toRefs, watch} from 'vue';
// prop types
export interface Props {
aspectRatio?: 'taller' | 'wider'
handle?: string | number | boolean
handleSize?: number
hover?: boolean
slideOnClick?: boolean
keyboard?: boolean
keyboardStep?: number
leftImage: string
leftImageAlt?: string
leftImageCss?: object
leftImageLabel?: string
onSliderPositionChange?: (position: number) => void
rightImage: string
rightImageAlt?: string
rightImageCss?: object
rightImageLabel?: string
skeleton?: string | number | boolean
sliderLineColor?: string
sliderLineWidth?: number
sliderPositionPercentage?: number
vertical?: boolean
}
// props
const props = withDefaults(defineProps<Props>(), {
keyboard: false,
keyboardStep: 0.01,
hover: false,
slideOnClick: true,
handleSize: 40,
sliderLineWidth: 2,
sliderPositionPercentage: 0.5,
vertical: false,
onSliderPositionChange: () => {
},
sliderLineColor: '#ffffff',
aspectRatio: 'wider',
});
// emits
const emit = defineEmits<{
(e: 'slideStart', pos: number): void
(e: 'slideEnd', pos: number): void
(e: 'isSliding', state: boolean): void
}>();
// variables
const {
aspectRatio,
leftImage,
leftImageAlt,
leftImageLabel,
leftImageCss,
rightImage,
rightImageAlt,
rightImageLabel,
rightImageCss,
hover,
handle,
handleSize,
sliderLineWidth,
sliderPositionPercentage,
skeleton,
sliderLineColor,
vertical,
onSliderPositionChange,
slideOnClick,
keyboard,
keyboardStep
} = toRefs(props);
const componentId = Math.random().toString(36).substring(2, 9);
const horizontal = !vertical.value;
const containerRef = ref();
const rightImageRef = ref<HTMLImageElement | null>(null);
const leftImageRef = ref<HTMLImageElement | null>(null);
const sliderPosition = ref(sliderPositionPercentage.value);
const containerWidth = ref(0);
const containerHeight = ref(0);
const leftImgLoaded = ref(false);
const rightImgLoaded = ref(false);
const isSliding = ref(false);
// computed refs
const allImagesLoaded = computed(() => leftImgLoaded.value && rightImgLoaded.value);
// Introduce refs(rightImageClip|leftImageClip) to correct bug caused when shifting from deprecated
// css property 'clip' to 'clipPath'. clip-path:inset works as paddings or margin
// so when right image clip reduces, left image clip has to increase for the comparison
// effect to work
const rightImageClip = computed(() => sliderPosition.value);
const leftImageClip = computed(() => 1 - sliderPosition.value);
// computed styles
const containerStyle = computed((): CSSProperties => {
return {
display: allImagesLoaded.value ? 'flex' : 'none',
height: `${containerHeight.value}px`,
};
});
const rightImageStyle = computed((): CSSProperties => {
return {
clipPath: horizontal ? `inset(0px 0px 0px ${containerWidth.value * rightImageClip.value}px)` : `inset(${containerHeight.value * rightImageClip.value}px 0px 0px 0px)`,
...rightImageCss,
};
});
const leftImageStyle = computed((): CSSProperties => {
return {
clipPath: horizontal ? `inset(0px ${containerWidth.value * leftImageClip.value}px 0px 0px)` : `inset(0px 0px ${containerHeight.value * leftImageClip.value}px 0px)`,
...leftImageCss,
};
});
const sliderStyle = computed((): CSSProperties => {
return {
cursor: !hover.value && horizontal ? 'ew-resize !important' : !hover.value && !horizontal ? 'ns-resize !important' : undefined,
flexDirection: horizontal ? 'column' : 'row',
height: horizontal ? '100%' : `${handleSize.value}px`,
left: horizontal ? `${containerWidth.value * sliderPosition.value - handleSize.value / 2}px` : '0',
top: horizontal ? '0' : `${containerHeight.value * sliderPosition.value - handleSize.value / 2}px`,
width: horizontal ? `${handleSize.value}px` : '100%',
};
});
const lineStyle = computed((): CSSProperties => {
return {
background: sliderLineColor.value,
height: horizontal ? '100%' : `${sliderLineWidth.value}px`,
width: horizontal ? `${sliderLineWidth.value}px` : '100%',
};
});
const handleDefaultStyle = computed((): CSSProperties => {
return {
border: `${sliderLineWidth.value}px solid ${sliderLineColor.value}`,
height: `${handleSize.value}px`,
width: `${handleSize.value}px`,
transform: horizontal ? 'none' : 'rotate(90deg)',
};
});
const leftArrowStyle = computed((): CSSProperties => {
return {
border: `inset ${handleSize.value * 0.15}px rgba(0,0,0,0)`,
borderRight: `${handleSize.value * 0.15}px solid ${sliderLineColor.value}`,
marginLeft: `-${handleSize.value * 0.25}px`, // for IE11
marginRight: `${handleSize.value * 0.25}px`,
};
});
const rightArrowStyle = computed((): CSSProperties => {
return {
border: `inset ${handleSize.value * 0.15}px rgba(0,0,0,0)`,
borderLeft: `${handleSize.value * 0.15}px solid ${sliderLineColor.value}`,
marginRight: `-${handleSize.value * 0.25}px`, // for IE11
};
});
const leftLabelStyle = computed((): CSSProperties => {
return {
left: horizontal ? '5%' : '50%',
opacity: isSliding.value ? 0 : 1,
top: horizontal ? '50%' : '3%',
transform: horizontal ? 'translate(0,-50%)' : 'translate(-50%, 0)',
borderRadius: '10px',
};
});
const rightLabelStyle = computed((): CSSProperties => {
return {
opacity: isSliding.value ? 0 : 1,
left: horizontal ? 'unset' : '50%',
right: horizontal ? '5%' : 'unset',
top: horizontal ? '50%' : 'unset',
bottom: horizontal ? 'unset' : '3%',
transform: horizontal ? 'translate(0,-50%)' : 'translate(-50%, 0)',
borderRadius: '10px',
};
});
const leftLabelContainerStyle = computed((): CSSProperties => {
return {
clip: horizontal ? `rect(auto, ${containerWidth.value * sliderPosition.value}px, auto, auto)` : `rect(auto, auto, ${containerHeight.value * sliderPosition.value}px, auto)`,
};
});
const rightLabelContainerStyle = computed((): CSSProperties => {
return {
clipPath: horizontal ? `inset(0px 0px 0px ${containerWidth.value * rightImageClip.value}px)` : `inset(${containerHeight.value * rightImageClip.value}px 0px 0px 0px)`,
};
});
function handleSliding(event: MouseEvent | TouchEvent | KeyboardEvent) {
const e = event as TouchEvent;
// Calc cursor position from the:
// - left edge of the viewport (for horizontal)
// - top edge of the viewport (for vertical)
// @ts-expect-error it is necessary
const cursorXfromViewport = e.touches ? e.touches[0].pageX : e.pageX;
// @ts-expect-error it is necessary
const cursorYfromViewport = e.touches ? e.touches[0].pageY : e.pageY;
// Calc Cursor Position from the:
// - left edge of the window (for horizontal)
// - top edge of the window (for vertical)
// to consider any page scrolling
const cursorXfromWindow = cursorXfromViewport - window.scrollX;
const cursorYfromWindow = cursorYfromViewport - window.scrollY;
// Calc Cursor Position from the left edge of the image
const imagePosition = rightImageRef.value!.getBoundingClientRect();
let pos = horizontal ? cursorXfromWindow - imagePosition.left : cursorYfromWindow - imagePosition.top;
// Set minimum and maximum value-to-prevent the slider from overflowing
const minPos = sliderLineWidth.value / 2;
const maxPos = horizontal ? containerWidth.value - sliderLineWidth.value / 2 : containerHeight.value - sliderLineWidth.value / 2;
if (pos < minPos)
pos = minPos;
if (pos > maxPos)
pos = maxPos;
sliderPosition.value = horizontal ? pos / containerWidth.value : pos / containerHeight.value;
if (onSliderPositionChange.value)
onSliderPositionChange.value(horizontal ? pos / containerWidth.value : pos / containerHeight.value);
}
function startSliding(e: MouseEvent | TouchEvent | KeyboardEvent) {
isSliding.value = true;
emit('slideStart', sliderPosition.value);
emit('isSliding', isSliding.value);
if (!horizontal)
e.preventDefault(); // prevent all default + mobile scrolling if vertical
else if (!('touches' in e))
e.preventDefault(); // prevent default except from mobile scrolling
// Slide the image even if you just click or tap (not drag)
if (slideOnClick.value)
handleSliding(e);
window.addEventListener('mousemove', handleSliding);
window.addEventListener('touchmove', handleSliding);
window.addEventListener('mouseup', finishSliding);
window.addEventListener('touchend', finishSliding);
}
function finishSliding() {
isSliding.value = false;
emit('slideEnd', sliderPosition.value);
emit('isSliding', isSliding.value);
window.removeEventListener('mousemove', handleSliding);
window.removeEventListener('touchmove', handleSliding);
window.removeEventListener('mouseup', finishSliding);
window.removeEventListener('touchend', finishSliding);
}
function handleFocusIn() {
if (keyboard.value)
window.addEventListener('keydown', handleKeyDown);
}
function handleFocusOut() {
if (keyboard.value)
window.removeEventListener('keydown', handleKeyDown);
}
function handleOnClick() {
if (keyboard.value)
window.addEventListener('keydown', handleKeyDown);
}
function handleOnClickOutside(event: KeyboardEvent | MouseEvent) {
if (containerRef.value && !containerRef.value.contains(event.target)) {
// The click is outside the container, remove the event listener
containerRef.value.blur();
window.removeEventListener('keydown', handleKeyDown);
}
}
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'ArrowDown' && !horizontal) {
e.preventDefault();
if ((sliderPosition.value + keyboardStep.value) > 1)
sliderPosition.value = 1;
else
sliderPosition.value += keyboardStep.value;
} else if (e.key === 'ArrowUp' && !horizontal) {
e.preventDefault();
if ((sliderPosition.value - keyboardStep.value) < 0)
sliderPosition.value = 0;
else
sliderPosition.value -= keyboardStep.value;
} else if (e.key === 'ArrowLeft' && horizontal) {
e.preventDefault();
if ((sliderPosition.value - keyboardStep.value) < 0)
sliderPosition.value = 0;
else
sliderPosition.value -= keyboardStep.value;
} else if (e.key === 'ArrowRight' && horizontal) {
e.preventDefault();
if ((sliderPosition.value + keyboardStep.value) > 1)
sliderPosition.value = 1;
else
sliderPosition.value += keyboardStep.value;
} else {
// do something
}
}
function forceRenderHover(): void {
const instance = getCurrentInstance();
instance?.proxy?.$forceUpdate();
const containerElement = containerRef.value;
if (props.hover) {
containerElement?.addEventListener('mousemove', startSliding);
containerElement?.addEventListener('mouseleave', finishSliding);
} else {
containerElement?.removeEventListener('mousemove', startSliding);
containerElement?.removeEventListener('mouseleave', finishSliding);
containerElement?.addEventListener('mouseup', finishSliding);
containerElement?.addEventListener('touchend', finishSliding);
// containerElement?.addEventListener('mouseleave', finishSliding)
}
}
// Make the component responsive
onMounted(() => {
const containerElement = containerRef.value;
const resizeObserver = new ResizeObserver(([entry]) => {
containerWidth.value = entry.target.getBoundingClientRect().width;
});
resizeObserver.observe(containerElement);
return () => resizeObserver.disconnect();
});
onMounted(() => {
const containerElement = containerRef.value;
// had to include this here, binding it with the container with the if hover prop doesn't work for some reason
if (props.hover) {
containerElement?.addEventListener('mousemove', startSliding); // 03
containerElement?.addEventListener('mouseleave', finishSliding); // 04
}
window.addEventListener('click', handleOnClickOutside);
// containerElement?.addEventListener('mouseleave', finishSliding)
});
onBeforeUnmount(() => {
const containerElement = containerRef.value;
containerElement?.removeEventListener('mousemove', startSliding);
containerElement?.removeEventListener('mouseleave', finishSliding);
window.removeEventListener('keydown', handleKeyDown);
window.removeEventListener('click', handleOnClickOutside);
window.removeEventListener('mousemove', handleSliding);
window.removeEventListener('touchmove', handleSliding);
window.removeEventListener('mouseup', finishSliding);
window.removeEventListener('touchend', finishSliding);
});
// Watch for changes in leftImage
watch(leftImageRef, () => {
leftImgLoaded.value = !!leftImageRef.value?.complete;
});
// Watch for changes in rightImage
watch(rightImageRef, () => {
rightImgLoaded.value = !!rightImageRef.value?.complete;
});
// since hover is the only listener set on mount, we need to rerender component if the value changes
watch(hover, () => {
forceRenderHover();
});
// Calculate container height
watch(
[() => containerWidth.value, () => leftImgLoaded.value, () => rightImgLoaded.value],
() => {
const leftImageWidthHeightRatio = leftImageRef.value!.naturalHeight / leftImageRef.value!.naturalWidth;
const rightImageWidthHeightRatio = rightImageRef.value!.naturalHeight / rightImageRef.value!.naturalWidth;
const idealWidthHeightRatio
= aspectRatio.value === 'taller' ? Math.max(leftImageWidthHeightRatio, rightImageWidthHeightRatio) : Math.min(leftImageWidthHeightRatio, rightImageWidthHeightRatio);
containerHeight.value = containerWidth.value * idealWidthHeightRatio;
},
);
</script>
<template>
<div v-if="skeleton && !allImagesLoaded" data-testid="skeleton" :style="containerStyle" v-html="skeleton"/>
<div
v-else :id="componentId" ref="containerRef" class="vci--container" tabindex="0" data-testid="vci-container"
:style="containerStyle" @click="handleOnClick" @touchstart="startSliding" @touchend="finishSliding"
@focusin="handleFocusIn" @focusout="handleFocusOut" @mousedown="startSliding" @mouseup="finishSliding"
>
<img
ref="rightImageRef" class="vci--right-image" :alt="rightImageAlt" data-testid="right-image" :src="rightImage"
:style="rightImageStyle" @load="rightImgLoaded = true"
>
<img
ref="leftImageRef" class="vci--left-image" :alt="leftImageAlt" data-testid="left-image" :src="leftImage"
:style="leftImageStyle" @load="leftImgLoaded = true"
>
<div class="vci--slider" :style="sliderStyle">
<div class="vci--slider-line" :style="lineStyle"/>
<div v-if="handle" class="vci--custom-handle" v-html="handle"/>
<div v-else class="vci--default-handle" :style="handleDefaultStyle">
<div class="vci--left-arrow" :style="leftArrowStyle"/>
<div class="vci--right-arrow" :style="rightArrowStyle"/>
</div>
<div class="vci--slider-line" :style="lineStyle"/>
</div>
<div v-if="leftImageLabel" class="vci--left-label-container" :style="leftLabelContainerStyle">
<div class="vci--left-label" data-testid="left-image-label" :style="leftLabelStyle">
{{ leftImageLabel }}
</div>
</div>
<div v-if="rightImageLabel" class="vci--right-label-container" :style="rightLabelContainerStyle">
<div class="vci--right-label" data-testid="right-image-label" :style="rightLabelStyle">
{{ rightImageLabel }}
</div>
</div>
</div>
</template>
<style scoped lang="scss" src="./index.scss">
</style>

View File

@@ -0,0 +1,93 @@
.vci--container {
box-sizing: border-box;
position: relative;
display: flex;
overflow: hidden;
width: 100%;
}
.vci--right-image {
display: flex;
position: absolute;
object-fit: cover;
height: 100%;
width: 100%;
}
.vci--left-image {
display: flex;
position: absolute;
object-fit: cover;
height: 100%;
width: 100%;
}
.vci--slider {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
.vci--slider-line {
flex: 0 1 auto;
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}
.vci--custom-handle {
box-sizing: border-box;
display: flex;
flex: 1 0 auto;
justify-content: center;
align-items: center;
height: auto;
width: auto;
}
.vci--default-handle {
box-sizing: border-box;
display: flex;
flex: 1 0 auto;
justify-content: center;
align-items: center;
border-radius: 100%;
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}
.vci--left-arrow {
height: 0px;
width: 0px;
}
.vci--right-arrow {
height: 0px;
width: 0px;
}
.vci--left-label {
position: absolute;
padding: 10px 20px;
background: rgba(0, 0, 0, 0.5);
color: white;
transition: opacity 0.1s ease-out;
}
.vci--right-label {
position: absolute;
padding: 10px 20px;
background: rgba(0, 0, 0, 0.5);
color: white;
transition: opacity 0.1s ease-out;
}
.vci--right-label-container {
position: absolute;
height: 100%;
width: 100%;
}
.vci--left-label-container {
position: absolute;
height: 100%;
width: 100%;
}

View File

@@ -0,0 +1,3 @@
import VueCompareImage from './VueCompareImage.vue';
export {VueCompareImage};

View File

@@ -1,162 +1,35 @@
<template>
<header class="header-main">
<div class="header-container">
<AFlex :vertical="false" align="center" justify="flex-start" class="header-logo-container">
<APopover placement="bottomRight" trigger="click" :arrow="false">
<template #content>
<div style="width: 500px;height: 300px">
888
</div>
</template>
<ACard :body-style="{ padding: '0px' }" :hoverable="true"
@focus="handleCardFocus"
@blur="handleCardBlur"
tabindex="0"
:style="cardStyle"
>
<AAvatar :size="50" shape="square" :src="logo"/>
</ACard>
</APopover>
<span class="header-logo-text" @click="router.push('/')">S.Album</span>
</AFlex>
<AFlex class="header-search-container" :vertical="false" align="center" justify="center">
<APopover :arrow="false" :overlayInnerStyle="{width: '28vw'}"
trigger="click">
<AInput size="large" class="header-search-input"
@focus="handleInputFocus"
@blur="handleInputBlur"
:style="{borderRadius: borderRadius, boxShadow: boxShadow}"
>
<template #suffix>
<AButton size="small" type="text" shape="circle" @click.prevent>
<template #icon>
<AAvatar size="small" shape="circle" :src="search"/>
</template>
</AButton>
</template>
</AInput>
<template #content>
<AFlex :vertical="false" align="center" justify="space-between" class="header-search-content-header">
<span>搜索历史</span>
<AButton type="text" size="small" style="color: #707072">清空搜索历史</AButton>
</AFlex>
<div class="header-search-content-body">
<ATag :style="{
padding: '5px 15px',
fontSize: '15px',
marginTop: '20px',
}" :bordered="false" closable>搜索历史1
</ATag>
</div>
</template>
</APopover>
</AFlex>
<AFlex :vertical="false" align="center" justify="flex-end" class="header-menu-container">
<AFlex :vertical="false" align="center" justify="flex-start" class="header-menu-item" gap="large">
<AButton type="text" shape="circle" size="large" class="header-menu-item-btn">
<template #icon>
<AAvatar size="default" shape="circle" :src="community"/>
</template>
</AButton>
<ABadge :dot="true" :numberStyle="{
marginTop: '10px',
marginRight: '5px',
backgroundColor: 'rgba(77,167,255,0.89)',
}">
<div class="button-wrapper">
<ADropdown :trigger="['click']">
<AButton type="text" shape="circle" size="large" class="header-menu-item-btn bouncing-button">
<template #icon>
<AAvatar size="small" shape="circle" :src="notice"/>
</template>
</AButton>
<template #overlay>
<AMenu>
<AMenuItem key="reply">
<template #icon>
<EnterOutlined/>
</template>
<span>回复我的</span>
</AMenuItem>
<AMenuItem key="like">
<template #icon>
<LikeOutlined/>
</template>
<span>收到的赞</span>
</AMenuItem>
<AMenuItem key="message">
<template #icon>
<NotificationOutlined/>
</template>
<span>系统消息</span>
</AMenuItem>
</AMenu>
</template>
</ADropdown>
</div>
</ABadge>
</AFlex>
<AFlex :vertical="false" align="center" justify="flex-start" class="header-user-container">
<ADropdown :trigger="['click']">
<div class="avatar-wrapper">
<AAvatar :size="40" class="header-user-avatar" :src="user.user.avatar"/>
</div>
<template #overlay>
<ACard style="width: 300px;height: 500px;background-color: white;"></ACard>
</template>
</ADropdown>
</AFlex>
</AFlex>
<Logo/>
<Search/>
<Menu/>
</div>
</header>
</template>
<script lang="ts" setup>
import logo from "@/assets/svgs/logo-album.svg";
import useStore from "@/store";
import notice from '@/assets/svgs/notice.svg';
import community from '@/assets/svgs/community.svg';
import search from '@/assets/svgs/search.svg';
const user = useStore().user;
const router = useRouter();
const borderRadius = ref('20px');
const boxShadow = ref('none');
const cardShadow = ref('none');
/**
* 监听输入框聚焦事件
*/
const handleInputFocus = () => {
borderRadius.value = '10px'; // 聚焦时圆角变为0
boxShadow.value = '0 0 10px rgba(0, 123, 255, 0.5)'; // 聚焦时增加阴影
};
/**
* 监听输入框失焦事件
*/
const handleInputBlur = () => {
borderRadius.value = '20px'; // 失去焦点时圆角恢复
boxShadow.value = 'none'; // 失去焦点时阴影消失
};
/**
* 监听卡片聚焦事件
*/
const handleCardFocus = () => {
cardShadow.value = '0 0 10px rgba(0, 123, 255, 0.5)'; // 卡片的阴影
};
/**
* 监听卡片失焦事件
*/
const handleCardBlur = () => {
cardShadow.value = 'none'; // 失去焦点时阴影消失
};
// 将卡片样式作为计算属性
const cardStyle = computed(() => ({
boxShadow: cardShadow.value,
}));
import Logo from "@/layout/default/Header/Logo.vue";
import Search from "@/layout/default/Header/Search.vue";
import Menu from "@/layout/default/Header/Menu.vue";
</script>
<style scoped lang="scss" src="./index.scss">
<style scoped lang="scss">
.header-main {
height: 70px;
width: 100%;
display: flex;
align-items: center;
background-color: rgba(255, 255, 255, 0.38);
backdrop-filter: blur(20px);
transition: background-color 0.3s;
z-index: 3;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
.header-container {
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
padding: 0 2%;
}
}
</style>

View File

@@ -0,0 +1,184 @@
<template>
<AFlex :vertical="false" align="center" justify="flex-start" class="header-logo-container">
<APopover placement="bottomRight" trigger="click" :arrow="false">
<template #content>
<div class="header-logo-popover">
<div class="header-logo-popover-card" @click="router.push('/main/photo/all')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar class="avatar-bounce" size="default" shape="square" :src="allPhoto"/>
<span class="header-logo-popover-text">{{ t('album.allAlbums') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/photo/recent')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="default" shape="square" :src="recentUpload"/>
<span class="header-logo-popover-text">{{ t('album.recentUploads') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/album/albums')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="default" shape="square" :src="album"/>
<span class="header-logo-popover-text">{{ t('album.albums') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/album/people')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="default" shape="square" :src="peopleAlbum"/>
<span class="header-logo-popover-text">{{ t('album.peopleAlbums') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/album/location')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="default" shape="square" :src="locationAlbum"/>
<span class="header-logo-popover-text">{{ t('album.locationAlbums') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/album/thing')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="default" shape="square" :src="thingAlbum"/>
<span class="header-logo-popover-text">{{ t('album.thingsAlbums') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/photo/share')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="default" shape="square" :src="share"/>
<span class="header-logo-popover-text">{{ t('album.share') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/photo/recycling')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="small" shape="square" :src="recyclingbin"/>
<span class="header-logo-popover-text">{{ t('album.recyclingBin') }}</span>
</AFlex>
</div>
<div class="header-logo-popover-card" @click="router.push('/main/photo/upscale')">
<AFlex :vertical="false" align="center" justify="space-between">
<AAvatar size="small" shape="square" :src="ai"/>
<span class="header-logo-popover-text">{{ t('album.upscale') }}</span>
</AFlex>
</div>
</div>
</template>
<ACard :body-style="{ padding: '0px' }" :hoverable="true"
@focus="handleCardFocus"
@blur="handleCardBlur"
tabindex="0"
:style="cardStyle">
<AAvatar :size="50" shape="square" :src="logo"/>
</ACard>
</APopover>
<span class="header-logo-text" @click="router.push('/')">S.Album</span>
</AFlex>
</template>
<script setup lang="ts">
import logo from "@/assets/svgs/logo-album.svg";
import {useI18n} from 'vue-i18n';
import allPhoto from '@/assets/svgs/all-photo.svg';
import recentUpload from '@/assets/svgs/recent-upload.svg';
import album from '@/assets/svgs/album.svg';
import peopleAlbum from '@/assets/svgs/people-album.svg';
import locationAlbum from '@/assets/svgs/location-album.svg';
import thingAlbum from '@/assets/svgs/thing-album.svg';
import recyclingbin from '@/assets/svgs/recyclingbin.svg';
import ai from '@/assets/svgs/ai.svg';
import share from '@/assets/svgs/share.svg';
const router = useRouter();
const {t} = useI18n();
const cardShadow = ref('none');
/**
* 监听卡片聚焦事件
*/
const handleCardFocus = () => {
cardShadow.value = '0 0 10px rgba(0, 123, 255, 0.5)'; // 卡片的阴影
};
/**
* 监听卡片失焦事件
*/
const handleCardBlur = () => {
cardShadow.value = 'none'; // 失去焦点时阴影消失
};
// 将卡片样式作为计算属性
const cardStyle = computed(() => ({
boxShadow: cardShadow.value,
}));
</script>
<style scoped lang="scss">
.header-logo-container {
min-width: 30%;
display: flex;
align-items: center;
.header-logo-text {
margin-left: 2%;
font-size: 1.8rem;
font-family: "Comic Sans MS", cursive;
font-weight: bold;
cursor: pointer;
}
}
.header-logo-popover {
width: 450px;
height: 200px;
display: flex;
align-items: flex-start;
justify-content: space-between;
flex-wrap: wrap;
padding: 10px;
.header-logo-popover-card {
width: 25%;
height: 15%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-radius: 15px;
border: 1px solid #ccc;
padding: 5px;
.header-logo-popover-text {
font-size: 15px;
margin-left: 10px;
font-weight: bold;
}
}
.avatar-bounce {
display: inline-block; /* 必须设置为 inline-block 或 block */
transition: transform 0.2s ease-in-out; /* 设置变换的过渡效果 */
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0); /* 初始位置 */
}
40% {
transform: translateY(-10px); /* 向上移动 */
}
60% {
transform: translateY(-5px); /* 稍微下移 */
}
}
.header-logo-popover-card:hover {
cursor: pointer;
background-color: #EEEEF6;
box-shadow: 0 0 10px rgba(0, 123, 255, 0.5);
animation: bounce 0.5s;
}
}
</style>

View File

@@ -0,0 +1,284 @@
<template>
<AFlex :vertical="false" align="center" justify="flex-end" class="header-menu-container">
<AFlex :vertical="false" align="center" justify="flex-start" class="header-menu-item" gap="large">
<!-- 社区按钮 -->
<div class="button-wrapper">
<AButton type="text" shape="circle" size="large" class="header-menu-item-btn">
<template #icon>
<AAvatar size="default" shape="circle" :src="community"/>
</template>
</AButton>
</div>
<!-- 上传按钮 -->
<div class="button-wrapper">
<AButton type="text" shape="circle" size="large" class="header-menu-item-btn">
<template #icon>
<AAvatar size="default" shape="circle" :src="upload"/>
</template>
</AButton>
</div>
<!-- 通知按钮 -->
<ABadge :dot="true" :numberStyle="{
marginTop: '10px',
marginRight: '5px',
backgroundColor: 'rgba(77,167,255,0.89)',
}">
<div class="button-wrapper">
<ADropdown :trigger="['click']">
<AButton type="text" shape="circle" size="large" class="header-menu-item-btn bouncing-button">
<template #icon>
<AAvatar size="small" shape="circle" :src="notice"/>
</template>
</AButton>
<template #overlay>
<AMenu>
<AMenuItem key="reply">
<template #icon>
<AAvatar size="small" shape="circle" :src="atme"/>
</template>
<span style="font-weight: bold">回复我的</span>
</AMenuItem>
<AMenuItem key="like">
<template #icon>
<AAvatar size="small" shape="circle" :src="like"/>
</template>
<span style="font-weight: bold">收到的赞</span>
</AMenuItem>
<AMenuItem key="message">
<template #icon>
<AAvatar size="small" shape="circle" :src="systemMessage"/>
</template>
<span style="font-weight: bold">系统消息</span>
</AMenuItem>
</AMenu>
</template>
</ADropdown>
</div>
</ABadge>
</AFlex>
<!-- 头像 -->
<AFlex :vertical="false" align="center" justify="flex-start" class="header-user-container">
<APopover :arrow="false" trigger="click" placement="bottomRight">
<div class="avatar-wrapper">
<AAvatar :size="40" class="header-user-avatar" :src="user.user.avatar"/>
</div>
<template #content>
<div class="avatar-content">
<AFlex :vertical="true" align="flex-start" justify="center">
<AFlex class="avatar-content-header" :vertical="false" align="center" justify="space-between">
<AAvatar :size="70" class="card-avatar" :src="user.user.avatar"/>
<AFlex class="avatar-content-header-info" :vertical="true" align="flex-start" justify="flex-start">
<span class="avatar-name">{{ user.user.nickname }}</span>
<AProgress :show-info="false" :percent="30"/>
<AFlex class="avatar-content-header-level" :vertical="false" align="center" justify="space-between">
<img src="/level_icon/icon/lv1.png" class="avatar-level-icon" alt="level">
<img src="/level_icon/icon/lv2.png" class="avatar-level-icon" alt="level">
</AFlex>
</AFlex>
</AFlex>
<AFlex class="avatar-content-body" :vertical="false" align="center" justify="space-around">
<ACard :hoverable="true" class="avatar-content-body-card">
</ACard>
<ACard :hoverable="true" class="avatar-content-body-card">
</ACard>
<ACard :hoverable="true" class="avatar-content-body-card">
</ACard>
</AFlex>
<ADivider/>
<div class="avatar-content-menu">
<AMenu>
<AMenuItem key="1">
<template #icon>
<AAvatar size="small" shape="circle" :src="personalCenter"/>
</template>
<span class="avatar-content-menu-item">个人中心</span>
</AMenuItem>
<AMenuItem key="2">
<template #icon>
<AAvatar size="small" shape="circle" :src="accountSetting"/>
</template>
<span class="avatar-content-menu-item">账号设置</span>
</AMenuItem>
<AMenuItem key="3">
<template #icon>
<AAvatar size="small" shape="circle" :src="logout"/>
</template>
<span class="avatar-content-menu-logout">退出登录</span>
</AMenuItem>
</AMenu>
</div>
</AFlex>
</div>
</template>
</APopover>
</AFlex>
</AFlex>
</template>
<script setup lang="ts">
import community from "@/assets/svgs/community.svg";
import upload from "@/assets/svgs/upload.svg";
import notice from "@/assets/svgs/notice.svg";
import atme from "@/assets/svgs/atme.svg";
import like from "@/assets/svgs/like.svg";
import systemMessage from "@/assets/svgs/sys-msg.svg";
import personalCenter from "@/assets/svgs/personal-center.svg";
import accountSetting from "@/assets/svgs/setting.svg";
import logout from "@/assets/svgs/logout.svg";
import useStore from "@/store";
const user = useStore().user;
</script>
<style scoped lang="scss">
.header-menu-container {
width: 30%;
display: flex;
align-items: center;
justify-content: space-between;
.header-menu-item {
width: 85%;
display: flex;
justify-content: flex-end;
.header-menu-item-btn {
display: block;
}
.button-wrapper {
position: relative;
display: inline-block;
border-radius: 50%;
overflow: hidden;
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.button-wrapper:hover {
transform: scale(1.1);
//border: 2px solid #707072;
}
@keyframes bounce {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(-10deg);
}
/* 向左倾斜 */
50% {
transform: rotate(10deg);
}
/* 向右倾斜 */
75% {
transform: rotate(-10deg);
}
/* 向左倾斜 */
100% {
transform: rotate(0deg);
}
}
.bouncing-button:hover {
animation: bounce 0.5s ease infinite; /* 鼠标悬浮时左右摇摆 */
}
}
.header-user-container {
.header-user-avatar {
display: block;
cursor: pointer;
}
.avatar-wrapper {
display: inline-block;
border-radius: 50%;
overflow: hidden;
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.avatar-wrapper:hover {
box-shadow: 0 0 10px rgba(0, 123, 255, 0.5);
transform: scale(1.1);
}
}
}
.avatar-content {
width: 250px;
height: 350px;
.avatar-content-header {
width: 100%;
.card-avatar {
width: 30%;
cursor: pointer;
box-shadow: 0 0 10px rgba(112, 112, 114, 0.82);
}
.card-avatar:hover {
box-shadow: 0 0 10px rgba(77, 167, 255, 0.89);
transform: scale(1.1);
}
.avatar-content-header-info {
width: 68%;
.avatar-name {
font-size: 14px;
font-weight: bold;
}
.avatar-content-header-level {
width: 100%;
.avatar-level-icon {
width: 30px;
}
}
}
}
.avatar-content-body {
width: 100%;
margin-top: 20px;
.avatar-content-body-card {
width: 70px;
height: 60px;
background: #EEEEF6;
}
}
.avatar-content-menu {
width: 100%;
.avatar-content-menu-item {
font-size: 14px;
font-weight: bold;
cursor: pointer;
margin-left: 10px;
}
.avatar-content-menu-logout {
font-size: 14px;
font-weight: bold;
cursor: pointer;
margin-left: 10px;
color: rgba(248, 35, 35, 0.62);
}
}
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<AFlex class="header-search-container" :vertical="false" align="center" justify="center">
<APopover :arrow="false" :overlayInnerStyle="{width: '28vw'}"
trigger="click">
<AInput size="large" class="header-search-input"
@focus="handleInputFocus"
@blur="handleInputBlur"
:style="{borderRadius: borderRadius, boxShadow: boxShadow}"
>
<template #suffix>
<AButton size="small" type="text" shape="circle" @click.prevent>
<template #icon>
<AAvatar size="small" shape="circle" :src="search"/>
</template>
</AButton>
</template>
</AInput>
<template #content>
<AFlex :vertical="false" align="center" justify="space-between" class="header-search-content-header">
<span>搜索历史</span>
<AButton type="text" size="small" style="color: #707072">清空搜索历史</AButton>
</AFlex>
<div class="header-search-content-body">
<ATag :style="{
padding: '5px 15px',
fontSize: '15px',
cursor: 'pointer',
marginTop: '20px',
}" :bordered="false" closable>搜索历史1
</ATag>
</div>
</template>
</APopover>
</AFlex>
</template>
<script setup lang="ts">
import search from "@/assets/svgs/search.svg";
const borderRadius = ref('20px');
const boxShadow = ref('none');
/**
* 监听输入框聚焦事件
*/
const handleInputFocus = () => {
borderRadius.value = '10px'; // 聚焦时圆角变为0
boxShadow.value = '0 0 10px rgba(0, 123, 255, 0.5)'; // 聚焦时增加阴影
};
/**
* 监听输入框失焦事件
*/
const handleInputBlur = () => {
borderRadius.value = '20px'; // 失去焦点时圆角恢复
boxShadow.value = 'none'; // 失去焦点时阴影消失
};
</script>
<style scoped lang="scss">
.header-search-container {
width: 30%;
.header-search-input {
border-radius: 20px;
}
.header-search-content-body {
margin-top: 10px;
}
}
</style>

View File

@@ -1,120 +0,0 @@
.header-main {
height: 70px;
width: 100%;
display: flex;
align-items: center;
background-color: rgba(255, 255, 255, 0.38);
backdrop-filter: blur(20px);
transition: background-color 0.3s;
z-index: 3;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
.header-container {
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
padding: 0 2%;
.header-logo-container {
min-width: 30%;
cursor: pointer;
display: flex;
align-items: center;
.header-logo-text {
margin-left: 2%;
font-size: 1.8rem;
font-family: "Comic Sans MS", cursive;
font-weight: bold;
}
}
.header-search-container {
width: 30%;
.header-search-input {
border-radius: 20px;
}
.header-search-content-body {
margin-top: 10px;
}
}
.header-menu-container {
width: 30%;
display: flex;
align-items: center;
justify-content: space-between;
.header-menu-item {
width: 85%;
display: flex;
justify-content: flex-end;
.header-menu-item-btn {
display: block;
}
.button-wrapper {
position: relative;
display: inline-block;
border-radius: 50%;
overflow: hidden;
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.button-wrapper:hover {
transform: scale(1.1);
//border: 2px solid #707072;
}
@keyframes bounce {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(-10deg);
}
/* 向左倾斜 */
50% {
transform: rotate(10deg);
}
/* 向右倾斜 */
75% {
transform: rotate(-10deg);
}
/* 向左倾斜 */
100% {
transform: rotate(0deg);
}
}
.bouncing-button:hover {
animation: bounce 0.5s ease infinite; /* 鼠标悬浮时左右摇摆 */
}
}
.header-user-container {
.header-user-avatar {
display: block;
cursor: pointer;
}
.avatar-wrapper {
display: inline-block;
border-radius: 50%;
overflow: hidden;
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.avatar-wrapper:hover {
//border: 2px solid #707072; /* 鼠标悬浮时的边框颜色 */
transform: scale(1.1);
}
}
}
}
}

View File

@@ -51,17 +51,23 @@
</AMenuItem>
</AMenuItemGroup>
<ADivider/>
<AMenuItem :title="t('album.recyclingBin')" key="photo/share">
<template #icon>
<AAvatar shape="square" size="small" :src="share"/>
</template>
<span class="ant-menu-item-title">{{ t('album.share') }}</span>
</AMenuItem>
<AMenuItem :title="t('album.recyclingBin')" key="photo/recycling">
<template #icon>
<AAvatar shape="square" size="small" :src="recyclingbin"/>
</template>
<span class="ant-menu-item-title">{{ t('album.recyclingBin') }}</span>
</AMenuItem>
<AMenuItem :title="t('album.recyclingBin')" key="photo/ai">
<AMenuItem :title="t('album.recyclingBin')" key="photo/upscale">
<template #icon>
<AAvatar shape="square" size="small" :src="ai"/>
</template>
<span class="ant-menu-item-title">图像增强</span>
<span class="ant-menu-item-title">{{ t('album.upscale') }}</span>
</AMenuItem>
</AMenu>
</div>
@@ -89,7 +95,7 @@ import thingAlbum from '@/assets/svgs/thing-album.svg';
import recyclingbin from '@/assets/svgs/recyclingbin.svg';
import Folder from "@/components/Folder/Folder.vue";
import ai from '@/assets/svgs/ai.svg';
import share from '@/assets/svgs/share.svg';
const {t} = useI18n();
const router = useRouter();

View File

@@ -1,6 +1,6 @@
.sidebar {
width: 220px;
height: calc(100vh - 275px);
height: calc(100vh - 271px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
overflow-y: scroll;
overflow-x: hidden;
@@ -22,6 +22,7 @@
justify-content: center;
align-items: flex-start;
border-top: 1px solid #e8e8e8;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
.sidebar-folder-text-title {

View File

@@ -9,12 +9,21 @@ const messages = {
};
const language: string = (navigator.language || 'en').toLocaleLowerCase(); // 获取浏览器的语言
function getLanguage(): string | null {
let lang: string | null = null;
const langStr: string | null = localStorage.getItem('lang');
if (langStr) {
lang = JSON.parse(langStr).lang;
}
return lang;
}
const i18n: any = createI18n({
legacy: false,
compositionOnly: false,
globalInjection: true,
silentTranslationWarn: true,
locale: language.split('-')[0] || 'zh',
locale: getLanguage() || language.split('-')[0] || 'zh', // 首先从缓存里拿,没有的话就用浏览器语言,
silentFallbackWarn: true,
missingWarn: true,
fallbackWarn: false,

View File

@@ -129,5 +129,7 @@ export default {
locationAlbums: 'Location',
thingsAlbums: 'Things',
recyclingBin: 'Recycling Bin',
upscale: 'Image Inpainting',
share: 'Quickly Share'
}
};

View File

@@ -116,6 +116,8 @@ export default {
locationAlbums: '地点',
thingsAlbums: '事物',
recyclingBin: '回收站',
upscale: '图像修复',
share: '快传'
}
};

View File

@@ -15,7 +15,25 @@ export default [
children: [
...photo,
...albums,
...recycling_bin
...recycling_bin,
{
path: '/main/photo/upscale',
name: 'upscale',
component: () => import('@/views/Upscale/Upscale.vue'),
meta: {
requiresAuth: false,
title: '图像修复'
}
},
{
path: '/main/photo/share',
name: 'share',
component: () => import('@/views/ImageShare/ImageShare.vue'),
meta: {
requiresAuth: false,
title: '快传'
}
}
]
}

View File

@@ -3,7 +3,7 @@ import login from './modules/login';
import useStore from "@/store";
import {message} from "ant-design-vue";
import notFound from "./modules/notFound.ts";
import notFound from "./modules/not_found.ts";
import landing from "./modules/landing.ts";
import mainRouter from "./modules/main_router.ts";
import i18n from "@/locales";

View File

@@ -2,24 +2,17 @@ import localforage from "localforage";
export const localforageStorageAdapter = {
set(key: string, value: any) {
localforage.setItem(key, value).then();
async set(key: string, value: any) {
await localforage.setItem(key, value);
},
get(key: string) {
let value: any;
localforage.getItem(key).then((res: any) => {
if (res === null || res === undefined || res === "") {
value = "";
} else {
value = res;
}
});
return value ? JSON.parse(value) : value;
async get(key: string) {
const res: any = await localforage.getItem(key);
return res ? JSON.parse(res) : null;
},
remove(key: any) {
localforage.removeItem(key).then();
async remove(key: any) {
await localforage.removeItem(key);
},
clear() {
localforage.clear().then();
async clear() {
await localforage.clear();
}
};

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped lang="scss" src="./index.scss">
</style>

View File

View File

@@ -2,7 +2,8 @@
<div>
<ADivider/>
<AFlex :vertical="false" align="center" justify="space-around">
<AButton href="/qrlogin" class="login-form-bottom-button" :icon="h(QrcodeOutlined)">{{ t("login.qrLogin") }}
<AButton @click="router.push('/qrlogin')" class="login-form-bottom-button" :icon="h(QrcodeOutlined)">
{{ t("login.qrLogin") }}
</AButton>
<AButton @click="userStore.openQQUrl" class="login-form-bottom-button" :icon="h(QqOutlined)"></AButton>
<AButton @click="userStore.openGiteeUrl" class="login-form-bottom-button"
@@ -23,6 +24,7 @@ import useStore from "@/store";
import gitee from "@/assets/svgs/gitee.svg";
const {t} = useI18n();
const router = useRouter();
const userStore = useStore().user;

View File

@@ -9,18 +9,18 @@
<router-view/>
</div>
</div>
<AFloatButtonGroup trigger="click" type="primary">
<AFloatButtonGroup trigger="click" type="default">
<template #icon>
<BarsOutlined/>
<AAvatar shape="square" :size="20" :src="other"/>
</template>
<AFloatButton @click="changeLanguage">
<AFloatButton @click="changeLanguage" tooltip="切换语言">
<template #icon>
<TranslationOutlined/>
<AAvatar shape="square" :size="20" :src="translation"/>
</template>
</AFloatButton>
<AFloatButton>
<AFloatButton tooltip="切换主题">
<template #icon>
<AlertOutlined/>
<AAvatar shape="square" :size="20" :src="darkMode"/>
</template>
</AFloatButton>
</AFloatButtonGroup>
@@ -34,7 +34,9 @@ import Header from "@/layout/default/Header/Header.vue";
// import {SmileOutlined} from "@ant-design/icons-vue";
import Sidebar from "@/layout/default/Sidebar/Sidebar.vue";
import {useI18n} from "vue-i18n";
import translation from '@/assets/svgs/translation.svg';
import darkMode from '@/assets/svgs/dark-mode.svg';
import other from '@/assets/svgs/other.svg';
// const websocket = useStore().websocket;
// const userInfo = useStore().user;
const lang = useStore().lang;

View File

@@ -15,19 +15,10 @@
flex-direction: row;
.main-content-container {
display: flex;
flex-direction: column;
//justify-content: center;
//align-items: center;
width: calc(100% - 15vw);
height: calc(100vh - 90px);
padding: 10px;
.main-container-card {
width: 100%;
height: 100%;
overflow: scroll;
}
width: calc(100% - 230px);
height: calc(100% - 110px);
padding: 20px;
overflow: scroll;
}

View File

@@ -2,7 +2,7 @@
<div>
<ADivider/>
<AFlex :vertical="false" align="center" justify="space-around">
<AButton href="/login" class="qrlogin-form-bottom-button" :icon="h(TabletOutlined)">
<AButton @click="router.push('/login')" class="qrlogin-form-bottom-button" :icon="h(TabletOutlined)">
{{ t("login.phoneLoginAndRegister") }}
</AButton>
<AButton @click="userStore.openQQUrl" class="qrlogin-form-bottom-button" :icon="h(QqOutlined)"></AButton>
@@ -26,7 +26,7 @@ import gitee from "@/assets/svgs/gitee.svg";
const userStore = useStore().user;
const {t} = useI18n();
const router = useRouter();
onBeforeMount(() => {
userStore.getClientId().then(() => {
userStore.getGithubRedirectUrl();

View File

@@ -0,0 +1,16 @@
<template>
<div class="upscale-container">
<AFlex :vertical="false" align="center" justify="flex-start">
<span class="upscale-title">图像修复</span>
</AFlex>
<AFlex :vertical="false" align="center" justify="flex-start">
</AFlex>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss" src="./index.scss">
</style>

View File

@@ -0,0 +1,11 @@
.upscale-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
.upscale-title {
font-size: 20px;
font-weight: bold;
}
}