🎨 update
6
components.d.ts
vendored
@@ -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']
|
||||
}
|
||||
|
@@ -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"
|
||||
|
BIN
public/realesrgan/anime_4x/group1-shard1of1.bin
Normal file
1
public/realesrgan/anime_4x/model.json
Normal file
BIN
public/realesrgan/anime_4x_plus/group1-shard1of5.bin
Normal file
BIN
public/realesrgan/anime_4x_plus/group1-shard2of5.bin
Normal file
BIN
public/realesrgan/anime_4x_plus/group1-shard3of5.bin
Normal file
BIN
public/realesrgan/anime_4x_plus/group1-shard4of5.bin
Normal file
BIN
public/realesrgan/anime_4x_plus/group1-shard5of5.bin
Normal file
1
public/realesrgan/anime_4x_plus/model.json
Normal file
BIN
public/realesrgan/general/group1-shard1of2.bin
Normal file
BIN
public/realesrgan/general/group1-shard2of2.bin
Normal file
1
public/realesrgan/general/model.json
Normal file
BIN
public/realesrgan/realx4plus/group1-shard10of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard11of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard12of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard13of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard14of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard15of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard16of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard1of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard2of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard3of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard4of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard5of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard6of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard7of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard8of16.bin
Normal file
BIN
public/realesrgan/realx4plus/group1-shard9of16.bin
Normal file
1
public/realesrgan/realx4plus/model.json
Normal file
1
src/assets/svgs/ai-icon.svg
Normal file
After Width: | Height: | Size: 7.0 KiB |
1
src/assets/svgs/arrow.svg
Normal 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
@@ -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 |
1
src/assets/svgs/category.svg
Normal 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 |
1
src/assets/svgs/dark-mode.svg
Normal 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
@@ -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 |
1
src/assets/svgs/logout.svg
Normal 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 |
1
src/assets/svgs/other.svg
Normal 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 |
1
src/assets/svgs/personal-center.svg
Normal 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 |
1
src/assets/svgs/setting.svg
Normal 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 |
1
src/assets/svgs/share.svg
Normal 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 |
1
src/assets/svgs/sys-msg.svg
Normal 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 |
1
src/assets/svgs/translation.svg
Normal file
After Width: | Height: | Size: 6.6 KiB |
1
src/assets/svgs/upload.svg
Normal 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 |
@@ -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 {
|
||||
|
446
src/components/VueCompareImage/VueCompareImage.vue
Normal 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>
|
93
src/components/VueCompareImage/index.scss
Normal 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%;
|
||||
}
|
3
src/components/VueCompareImage/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import VueCompareImage from './VueCompareImage.vue';
|
||||
|
||||
export {VueCompareImage};
|
@@ -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>
|
||||
|
184
src/layout/default/Header/Logo.vue
Normal 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>
|
284
src/layout/default/Header/Menu.vue
Normal 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>
|
72
src/layout/default/Header/Search.vue
Normal 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>
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -129,5 +129,7 @@ export default {
|
||||
locationAlbums: 'Location',
|
||||
thingsAlbums: 'Things',
|
||||
recyclingBin: 'Recycling Bin',
|
||||
upscale: 'Image Inpainting',
|
||||
share: 'Quickly Share'
|
||||
}
|
||||
};
|
||||
|
@@ -116,6 +116,8 @@ export default {
|
||||
locationAlbums: '地点',
|
||||
thingsAlbums: '事物',
|
||||
recyclingBin: '回收站',
|
||||
upscale: '图像修复',
|
||||
share: '快传'
|
||||
}
|
||||
|
||||
};
|
||||
|
@@ -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: '快传'
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
@@ -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";
|
||||
|
@@ -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();
|
||||
}
|
||||
};
|
||||
|
11
src/views/ImageShare/ImageShare.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
|
||||
</style>
|
0
src/views/ImageShare/index.scss
Normal 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;
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
16
src/views/Upscale/Upscale.vue
Normal 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>
|
11
src/views/Upscale/index.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.upscale-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.upscale-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|