Compare commits
73 Commits
c673948dd3
...
dev_vite
| Author | SHA1 | Date | |
|---|---|---|---|
| d5266937d7 | |||
| 9cd9d82077 | |||
| 9869217688 | |||
|
|
768986af54 | ||
|
|
c58a2ed093 | ||
|
|
10b632572a | ||
| 89d27ba206 | |||
| bee0a66626 | |||
|
|
3519eeffd7 | ||
|
|
21a71aeffb | ||
| 82384b8488 | |||
| 3c53212c10 | |||
|
|
4dd1eab217 | ||
|
|
70ddcb63d0 | ||
|
|
6131cd2e96 | ||
| 00bc9041ef | |||
| 436c472327 | |||
|
|
d771785cf7 | ||
| ee274e07f4 | |||
| 87f2c67ced | |||
| bc83de1da6 | |||
|
|
e3b2ca530c | ||
|
|
e3c889c58a | ||
| 49e9ba423a | |||
| d02061c407 | |||
|
|
c60458d1a0 | ||
| 5a85ff0189 | |||
|
|
5eaa77596e | ||
|
|
848e7b4692 | ||
|
|
fd140ebc56 | ||
|
|
34094c8c92 | ||
|
|
e4dc2bf729 | ||
|
|
7f226179d9 | ||
|
|
3edff6b05c | ||
|
|
6fd1212298 | ||
| e093cccb8d | |||
| b2c6c61515 | |||
| 0219b1a2f4 | |||
| 133433a260 | |||
| 90a59a3dc5 | |||
| c8a65ee2cb | |||
| 226918e941 | |||
|
|
494bfd68ca | ||
|
|
95b70792ba | ||
|
|
a0fffa5896 | ||
|
|
06eaabc742 | ||
|
|
27da4739a6 | ||
| 52576aa0a1 | |||
| 22aa7c37cd | |||
| 752b33f196 | |||
| f3b873b7ae | |||
| 006c2e3f9c | |||
|
|
1f413b36ca | ||
|
|
e7b052f100 | ||
|
|
19bb412470 | ||
|
|
a1e071f7bc | ||
|
|
5388b2df4c | ||
|
|
76e507cae3 | ||
|
|
d4e9462d39 | ||
|
|
4a11d172d2 | ||
|
|
e20092c77f | ||
|
|
5f4656c629 | ||
|
|
14eca9aff2 | ||
|
|
88f0528553 | ||
|
|
afbea289fb | ||
| eb2baa26a7 | |||
|
|
62829395ce | ||
|
|
16532ce44b | ||
|
|
27de720137 | ||
|
|
acf2029efe | ||
|
|
49398aac48 | ||
|
|
b3d9bce440 | ||
|
|
596bc75b83 |
@@ -2,6 +2,7 @@ VITE_USER_NODE_ENV = 'development_cloud'
|
|||||||
# VITE_APP_BASE_URL = 'https://aida.com.hk/test'
|
# VITE_APP_BASE_URL = 'https://aida.com.hk/test'
|
||||||
# VITE_APP_BASE_URL = 'http://18.167.251.121:10088'
|
# VITE_APP_BASE_URL = 'http://18.167.251.121:10088'
|
||||||
# VITE_APP_BASE_URL = 'https://api.aida.com.hk'
|
# VITE_APP_BASE_URL = 'https://api.aida.com.hk'
|
||||||
VITE_APP_BASE_URL = 'https://develop.api.aida.com.hk'
|
# VITE_APP_BASE_URL = 'https://develop.api.aida.com.hk'
|
||||||
|
VITE_APP_BASE_URL = 'https://www.develop-ms.api.aida.com.hk'
|
||||||
|
|
||||||
# VITE_APP_BASE_URL = 'http://localhost:22170'
|
# VITE_APP_BASE_URL = 'http://localhost:22170'
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -25,3 +25,5 @@ dist.rar
|
|||||||
.eslintrc-auto-import.json
|
.eslintrc-auto-import.json
|
||||||
components.d.ts
|
components.d.ts
|
||||||
.cursor
|
.cursor
|
||||||
|
*.zip
|
||||||
|
*.7z
|
||||||
@@ -167,8 +167,7 @@ li {
|
|||||||
padding: 0.6rem 0.5rem;
|
padding: 0.6rem 0.5rem;
|
||||||
}
|
}
|
||||||
.ant-modal-mask {
|
.ant-modal-mask {
|
||||||
background-color: #666666;
|
background-color: rgba(102, 102, 102, 0.5);
|
||||||
opacity: 0.5;
|
|
||||||
}
|
}
|
||||||
.select_block {
|
.select_block {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
@@ -1251,8 +1250,8 @@ tr > .ant-picker-cell-in-view.ant-picker-cell-range-hover-start:last-child::afte
|
|||||||
border-color: #000 !important;
|
border-color: #000 !important;
|
||||||
}
|
}
|
||||||
.ant-spin .ant-spin-dot {
|
.ant-spin .ant-spin-dot {
|
||||||
width: 1.5em;
|
width: 4.5rem;
|
||||||
height: 1.5em;
|
height: 4.5rem;
|
||||||
}
|
}
|
||||||
.ant-spin-dot-item {
|
.ant-spin-dot-item {
|
||||||
background-color: #000000 !important;
|
background-color: #000000 !important;
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1634_4311)">
|
||||||
|
<path d="M7.99968 6.6665C7.47226 6.6665 6.95669 6.8229 6.51816 7.11592C6.07962 7.40894 5.73783 7.82541 5.536 8.31268C5.33416 8.79995 5.28135 9.33613 5.38425 9.85341C5.48714 10.3707 5.74112 10.8458 6.11406 11.2188C6.487 11.5917 6.96215 11.8457 7.47943 11.9486C7.99672 12.0515 8.5329 11.9987 9.02017 11.7968C9.50743 11.595 9.92391 11.2532 10.2169 10.8147C10.5099 10.3762 10.6663 9.86059 10.6663 9.33317C10.6663 8.62593 10.3854 7.94765 9.88529 7.44755C9.3852 6.94746 8.70692 6.6665 7.99968 6.6665ZM7.99968 10.6665C7.73597 10.6665 7.47818 10.5883 7.25892 10.4418C7.03965 10.2953 6.86875 10.087 6.76784 9.84341C6.66692 9.59978 6.64052 9.33169 6.69196 9.07305C6.74341 8.81441 6.8704 8.57683 7.05687 8.39036C7.24334 8.20389 7.48091 8.0769 7.73956 8.02546C7.9982 7.97401 8.26629 8.00041 8.50992 8.10133C8.75356 8.20225 8.96179 8.37314 9.1083 8.59241C9.25481 8.81168 9.33301 9.06946 9.33301 9.33317C9.33301 9.68679 9.19253 10.0259 8.94248 10.276C8.69244 10.526 8.3533 10.6665 7.99968 10.6665Z" fill="currentColor"/>
|
||||||
<path d="M15.024 2.74801L13.252 0.976013C12.9432 0.665575 12.576 0.419466 12.1714 0.251935C11.7669 0.0844031 11.3332 -0.00122304 10.8953 1.31975e-05H3.33333C2.4496 0.00107177 1.60237 0.352601 0.97748 0.977493C0.352588 1.60239 0.00105857 2.44962 0 3.33335L0 12.6667C0.00105857 13.5504 0.352588 14.3976 0.97748 15.0225C1.60237 15.6474 2.4496 15.999 3.33333 16H12.6667C13.5504 15.999 14.3976 15.6474 15.0225 15.0225C15.6474 14.3976 15.9989 13.5504 16 12.6667V5.10468C16.0012 4.66684 15.9156 4.2331 15.7481 3.82858C15.5805 3.42405 15.3344 3.05678 15.024 2.74801ZM11.3333 1.38668V2.00001C11.3333 2.53045 11.1226 3.03915 10.7475 3.41423C10.3725 3.7893 9.86377 4.00001 9.33333 4.00001H6.66667C6.13623 4.00001 5.62753 3.7893 5.25245 3.41423C4.87738 3.03915 4.66667 2.53045 4.66667 2.00001V1.33335H10.8953C11.0429 1.33466 11.1898 1.35255 11.3333 1.38668ZM14.6667 12.6667C14.6667 13.1971 14.456 13.7058 14.0809 14.0809C13.7058 14.456 13.1971 14.6667 12.6667 14.6667H3.33333C2.8029 14.6667 2.29419 14.456 1.91912 14.0809C1.54405 13.7058 1.33333 13.1971 1.33333 12.6667V3.33335C1.33333 2.80291 1.54405 2.29421 1.91912 1.91913C2.29419 1.54406 2.8029 1.33335 3.33333 1.33335V2.00001C3.33439 2.88374 3.68592 3.73097 4.31081 4.35587C4.93571 4.98076 5.78294 5.33229 6.66667 5.33335H9.33333C10.1717 5.33074 10.9781 5.01179 11.5914 4.44026C12.2047 3.86872 12.5797 3.08674 12.6413 2.25068L14.0813 3.69068C14.455 4.0666 14.6653 4.57468 14.6667 5.10468V12.6667Z" fill="currentColor"/>
|
<path d="M15.024 2.74801L13.252 0.976013C12.9432 0.665575 12.576 0.419466 12.1714 0.251935C11.7669 0.0844031 11.3332 -0.00122304 10.8953 1.31975e-05H3.33333C2.4496 0.00107177 1.60237 0.352601 0.97748 0.977493C0.352588 1.60239 0.00105857 2.44962 0 3.33335L0 12.6667C0.00105857 13.5504 0.352588 14.3976 0.97748 15.0225C1.60237 15.6474 2.4496 15.999 3.33333 16H12.6667C13.5504 15.999 14.3976 15.6474 15.0225 15.0225C15.6474 14.3976 15.9989 13.5504 16 12.6667V5.10468C16.0012 4.66684 15.9156 4.2331 15.7481 3.82858C15.5805 3.42405 15.3344 3.05678 15.024 2.74801ZM11.3333 1.38668V2.00001C11.3333 2.53045 11.1226 3.03915 10.7475 3.41423C10.3725 3.7893 9.86377 4.00001 9.33333 4.00001H6.66667C6.13623 4.00001 5.62753 3.7893 5.25245 3.41423C4.87738 3.03915 4.66667 2.53045 4.66667 2.00001V1.33335H10.8953C11.0429 1.33466 11.1898 1.35255 11.3333 1.38668ZM14.6667 12.6667C14.6667 13.1971 14.456 13.7058 14.0809 14.0809C13.7058 14.456 13.1971 14.6667 12.6667 14.6667H3.33333C2.8029 14.6667 2.29419 14.456 1.91912 14.0809C1.54405 13.7058 1.33333 13.1971 1.33333 12.6667V3.33335C1.33333 2.80291 1.54405 2.29421 1.91912 1.91913C2.29419 1.54406 2.8029 1.33335 3.33333 1.33335V2.00001C3.33439 2.88374 3.68592 3.73097 4.31081 4.35587C4.93571 4.98076 5.78294 5.33229 6.66667 5.33335H9.33333C10.1717 5.33074 10.9781 5.01179 11.5914 4.44026C12.2047 3.86872 12.5797 3.08674 12.6413 2.25068L14.0813 3.69068C14.455 4.0666 14.6653 4.57468 14.6667 5.10468V12.6667Z" fill="currentColor"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1634_4311">
|
||||||
|
<rect width="16" height="16" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.7 KiB |
5
src/assets/icons/CTrash.svg
Normal file
5
src/assets/icons/CTrash.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12.2503 2.33333H10.442C10.3066 1.67499 9.94839 1.08345 9.42772 0.658419C8.90706 0.233386 8.25578 0.000848473 7.58366 0L6.41699 0C5.74487 0.000848473 5.0936 0.233386 4.57293 0.658419C4.05226 1.08345 3.69405 1.67499 3.55866 2.33333H1.75033C1.59562 2.33333 1.44724 2.39479 1.33785 2.50419C1.22845 2.61358 1.16699 2.76196 1.16699 2.91667C1.16699 3.07138 1.22845 3.21975 1.33785 3.32915C1.44724 3.43854 1.59562 3.5 1.75033 3.5H2.33366V11.0833C2.33459 11.8566 2.64217 12.5979 3.18895 13.1447C3.73573 13.6915 4.47706 13.9991 5.25033 14H8.75033C9.52359 13.9991 10.2649 13.6915 10.8117 13.1447C11.3585 12.5979 11.6661 11.8566 11.667 11.0833V3.5H12.2503C12.405 3.5 12.5534 3.43854 12.6628 3.32915C12.7722 3.21975 12.8337 3.07138 12.8337 2.91667C12.8337 2.76196 12.7722 2.61358 12.6628 2.50419C12.5534 2.39479 12.405 2.33333 12.2503 2.33333ZM6.41699 1.16667H7.58366C7.94549 1.16711 8.29832 1.27947 8.59377 1.48834C8.88922 1.69721 9.11282 1.99237 9.23391 2.33333H4.76674C4.88783 1.99237 5.11143 1.69721 5.40688 1.48834C5.70234 1.27947 6.05517 1.16711 6.41699 1.16667ZM10.5003 11.0833C10.5003 11.5475 10.316 11.9926 9.98776 12.3208C9.65957 12.649 9.21445 12.8333 8.75033 12.8333H5.25033C4.7862 12.8333 4.34108 12.649 4.01289 12.3208C3.6847 11.9926 3.50033 11.5475 3.50033 11.0833V3.5H10.5003V11.0833Z" fill="currentColor"/>
|
||||||
|
<path d="M5.83333 10.5002C5.98804 10.5002 6.13642 10.4387 6.24581 10.3293C6.35521 10.2199 6.41667 10.0715 6.41667 9.91683V6.41683C6.41667 6.26212 6.35521 6.11375 6.24581 6.00435C6.13642 5.89495 5.98804 5.8335 5.83333 5.8335C5.67862 5.8335 5.53025 5.89495 5.42085 6.00435C5.31146 6.11375 5.25 6.26212 5.25 6.41683V9.91683C5.25 10.0715 5.31146 10.2199 5.42085 10.3293C5.53025 10.4387 5.67862 10.5002 5.83333 10.5002Z" fill="currentColor"/>
|
||||||
|
<path d="M8.16634 10.5002C8.32105 10.5002 8.46942 10.4387 8.57882 10.3293C8.68822 10.2199 8.74967 10.0715 8.74967 9.91683V6.41683C8.74967 6.26212 8.68822 6.11375 8.57882 6.00435C8.46942 5.89495 8.32105 5.8335 8.16634 5.8335C8.01163 5.8335 7.86326 5.89495 7.75386 6.00435C7.64447 6.11375 7.58301 6.26212 7.58301 6.41683V9.91683C7.58301 10.0715 7.64447 10.2199 7.75386 10.3293C7.86326 10.4387 8.01163 10.5002 8.16634 10.5002Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
3
src/assets/icons/home.svg
Normal file
3
src/assets/icons/home.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.4872 5.28094L9.06266 0.853126C8.51506 0.306793 7.7733 0 7 0C6.2267 0 5.48494 0.306793 4.93734 0.853126L0.512757 5.28094C0.349671 5.44308 0.220373 5.636 0.132355 5.84851C0.0443375 6.06102 -0.000647733 6.2889 7.04636e-06 6.51894V12.249C7.04636e-06 12.7134 0.184381 13.1587 0.51257 13.4871C0.840758 13.8155 1.28588 14 1.75001 14H12.25C12.7141 14 13.1592 13.8155 13.4874 13.4871C13.8156 13.1587 14 12.7134 14 12.249V6.51894C14.0006 6.2889 13.9557 6.06102 13.8676 5.84851C13.7796 5.636 13.6503 5.44308 13.4872 5.28094ZM8.75 12.8326H5.25V10.5364C5.25 10.072 5.43438 9.62663 5.76256 9.29825C6.09075 8.96986 6.53587 8.78538 7 8.78538C7.46413 8.78538 7.90925 8.96986 8.23744 9.29825C8.56562 9.62663 8.75 10.072 8.75 10.5364V12.8326ZM12.8333 12.249C12.8333 12.4038 12.7719 12.5522 12.6625 12.6617C12.5531 12.7711 12.4047 12.8326 12.25 12.8326H9.91666V10.5364C9.91666 9.76241 9.60937 9.0201 9.06239 8.47279C8.51541 7.92549 7.77355 7.61801 7 7.61801C6.22645 7.61801 5.48459 7.92549 4.93761 8.47279C4.39063 9.0201 4.08334 9.76241 4.08334 10.5364V12.8326H1.75001C1.5953 12.8326 1.44692 12.7711 1.33753 12.6617C1.22813 12.5522 1.16667 12.4038 1.16667 12.249V6.51894C1.16721 6.36425 1.22862 6.216 1.33759 6.10627L5.76217 1.6802C6.09099 1.35272 6.53605 1.16887 7 1.16887C7.46394 1.16887 7.90901 1.35272 8.23783 1.6802L12.6624 6.10802C12.771 6.21732 12.8323 6.36486 12.8333 6.51894V12.249Z" fill="#585858"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
11
src/assets/icons/picture.svg
Normal file
11
src/assets/icons/picture.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1634_3952)">
|
||||||
|
<path d="M19 0H5C3.67441 0.00158786 2.40356 0.528882 1.46622 1.46622C0.528882 2.40356 0.00158786 3.67441 0 5L0 19C0.00158786 20.3256 0.528882 21.5964 1.46622 22.5338C2.40356 23.4711 3.67441 23.9984 5 24H19C20.3256 23.9984 21.5964 23.4711 22.5338 22.5338C23.4711 21.5964 23.9984 20.3256 24 19V5C23.9984 3.67441 23.4711 2.40356 22.5338 1.46622C21.5964 0.528882 20.3256 0.00158786 19 0ZM5 2H19C19.7956 2 20.5587 2.31607 21.1213 2.87868C21.6839 3.44129 22 4.20435 22 5V19C21.9983 19.4455 21.8957 19.8848 21.7 20.285L12.537 11.122C12.0727 10.6576 11.5214 10.2892 10.9147 10.0378C10.308 9.78644 9.65772 9.65707 9.001 9.65707C8.34428 9.65707 7.69399 9.78644 7.08728 10.0378C6.48056 10.2892 5.92931 10.6576 5.465 11.122L2 14.586V5C2 4.20435 2.31607 3.44129 2.87868 2.87868C3.44129 2.31607 4.20435 2 5 2ZM5 22C4.20435 22 3.44129 21.6839 2.87868 21.1213C2.31607 20.5587 2 19.7956 2 19V17.414L6.878 12.536C7.1566 12.2572 7.4874 12.0361 7.85151 11.8852C8.21561 11.7343 8.60587 11.6566 9 11.6566C9.39413 11.6566 9.78439 11.7343 10.1485 11.8852C10.5126 12.0361 10.8434 12.2572 11.122 12.536L20.285 21.7C19.8848 21.8957 19.4455 21.9983 19 22H5Z" fill="#585858"/>
|
||||||
|
<path d="M16 10.5C16.6922 10.5 17.3689 10.2947 17.9445 9.91015C18.5201 9.52556 18.9687 8.97893 19.2336 8.33939C19.4985 7.69985 19.5678 6.99612 19.4327 6.31719C19.2977 5.63825 18.9644 5.01461 18.4749 4.52513C17.9854 4.03564 17.3617 3.7023 16.6828 3.56725C16.0039 3.4322 15.3001 3.50152 14.6606 3.76642C14.0211 4.03133 13.4744 4.47993 13.0899 5.05551C12.7053 5.63108 12.5 6.30777 12.5 7C12.5 7.92826 12.8687 8.8185 13.5251 9.47487C14.1815 10.1313 15.0717 10.5 16 10.5ZM16 5.5C16.2967 5.5 16.5867 5.58798 16.8334 5.7528C17.08 5.91762 17.2723 6.15189 17.3858 6.42598C17.4994 6.70007 17.5291 7.00167 17.4712 7.29264C17.4133 7.58361 17.2704 7.85088 17.0607 8.06066C16.8509 8.27044 16.5836 8.4133 16.2926 8.47118C16.0017 8.52906 15.7001 8.49935 15.426 8.38582C15.1519 8.27229 14.9176 8.08003 14.7528 7.83336C14.588 7.58668 14.5 7.29667 14.5 7C14.5 6.60218 14.658 6.22065 14.9393 5.93934C15.2206 5.65804 15.6022 5.5 16 5.5Z" fill="#585858"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1634_3952">
|
||||||
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -172,8 +172,9 @@ input:focus{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ant-modal-mask{
|
.ant-modal-mask{
|
||||||
background-color: #666666;
|
background-color: rgba(102,102,102,0.5);
|
||||||
opacity: .5;
|
// background-color: #666666;
|
||||||
|
// opacity: .5;
|
||||||
}
|
}
|
||||||
.select_block{
|
.select_block{
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
|
|||||||
@@ -572,10 +572,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const disableEndDate = (current: Dayjs) => {
|
const disableEndDate = (current: Dayjs) => {
|
||||||
if (isEditMode.value) {
|
|
||||||
const specificTime = dayjs(formState.currentPeriodEnd)
|
|
||||||
return current && current < dayjs(formState.currentPeriodEnd * 1000).startOf("day")
|
|
||||||
}
|
|
||||||
return disabledDate(current)
|
return disabledDate(current)
|
||||||
}
|
}
|
||||||
const range = (start: number, end: number) => {
|
const range = (start: number, end: number) => {
|
||||||
|
|||||||
3615
src/lang/cn.ts
3615
src/lang/cn.ts
File diff suppressed because it is too large
Load Diff
136
src/lang/en.ts
136
src/lang/en.ts
@@ -24,6 +24,7 @@ export default {
|
|||||||
SubscribeNow: 'Subscribe now',
|
SubscribeNow: 'Subscribe now',
|
||||||
TaskList: 'Task List',
|
TaskList: 'Task List',
|
||||||
ViewOrders: 'View Orders',
|
ViewOrders: 'View Orders',
|
||||||
|
PersonalCenter: 'Personal Center',
|
||||||
BecomeSeller: 'Become a Seller',
|
BecomeSeller: 'Become a Seller',
|
||||||
SellerDashboard: 'Seller Dashboard',
|
SellerDashboard: 'Seller Dashboard',
|
||||||
toolsToProduct: 'To product image',
|
toolsToProduct: 'To product image',
|
||||||
@@ -1784,7 +1785,7 @@ export default {
|
|||||||
step3Info: 'Details are pre-filled from AiDA. Review and complete any missing information.',
|
step3Info: 'Details are pre-filled from AiDA. Review and complete any missing information.',
|
||||||
step4Title: 'Listing Live',
|
step4Title: 'Listing Live',
|
||||||
step4Info: 'Publish and your design goes live on the marketplace.',
|
step4Info: 'Publish and your design goes live on the marketplace.',
|
||||||
showAgain: 'Don’t show me again',
|
showAgain: 'Don’t show this again for 7 days',
|
||||||
GetStarted: 'Get Started',
|
GetStarted: 'Get Started',
|
||||||
},
|
},
|
||||||
SellerListEdit:{
|
SellerListEdit:{
|
||||||
@@ -1793,9 +1794,9 @@ export default {
|
|||||||
sketch:'Sketch',
|
sketch:'Sketch',
|
||||||
mainProductImage:'Main Product Image',
|
mainProductImage:'Main Product Image',
|
||||||
cover:'Cover',
|
cover:'Cover',
|
||||||
productImageDesc:'Choose from product image',
|
productImageDesc:'Choose from product media',
|
||||||
cropDesc:'Crop from main product image or sketch',
|
cropDesc:'Crop from main product image or sketch',
|
||||||
productImageMainTitle:'Product Image ',
|
productImageMainTitle:'Product Media ',
|
||||||
productImageSubTitle:'(from design collection)',
|
productImageSubTitle:'(from design collection)',
|
||||||
apparelSketchTitle:'Apparel Sketch ',
|
apparelSketchTitle:'Apparel Sketch ',
|
||||||
apparelSketchSubTitle:'(from design collection)',
|
apparelSketchSubTitle:'(from design collection)',
|
||||||
@@ -1809,9 +1810,138 @@ export default {
|
|||||||
learnMore:'Learn more',
|
learnMore:'Learn more',
|
||||||
requiredFieldTips:'Please fill in {field}',
|
requiredFieldTips:'Please fill in {field}',
|
||||||
requiredFieldTipsWithPage:'Listing {index}: Please fill in {field}',
|
requiredFieldTipsWithPage:'Listing {index}: Please fill in {field}',
|
||||||
|
priceMinMessage: 'Price cannot be less than HK$4',
|
||||||
draftSaved: 'Draft Saved',
|
draftSaved: 'Draft Saved',
|
||||||
draftDesc: 'Your listing has been saved as a draft. \nYou can continue editing or publish it later from My Listings.',
|
draftDesc: 'Your listing has been saved as a draft. \nYou can continue editing or publish it later from My Listings.',
|
||||||
listingLive:'Listing Live',
|
listingLive:'Listing Live',
|
||||||
publishDesc:'Your listing is now live on the marketplace.\nBuyers can discover and purchase your design.'
|
publishDesc:'Your listing is now live on the marketplace.\nBuyers can discover and purchase your design.'
|
||||||
|
},
|
||||||
|
Seller: {
|
||||||
|
brandProfile: "Brand Profile",
|
||||||
|
myListings: "My Listings",
|
||||||
|
myOrders: "My Orders",
|
||||||
|
settings: "Settings",
|
||||||
|
// Brand Profile
|
||||||
|
brandProfileBgNullTip: "Your brand banner has not been set up yet.",
|
||||||
|
changeBrandBanner: "Change Brand Banner",
|
||||||
|
edit: "Edit",
|
||||||
|
cancel: "Cancel",
|
||||||
|
confirm: "Confirm",
|
||||||
|
saveChange: "Save Change",
|
||||||
|
brandProfileEditTip: "Changes will be reflected on your Stylish Parade brand page.",
|
||||||
|
storeName: "Store Name",
|
||||||
|
storeNameDesc: "Enter your store name",
|
||||||
|
ownerName: "Owner’s Full Name",
|
||||||
|
ownerNameDesc: "Enter store owner's full name",
|
||||||
|
email: "Email",
|
||||||
|
emailDesc: "Enter email",
|
||||||
|
mobile: "Phone Number",
|
||||||
|
mobileDesc: "Enter phone number",
|
||||||
|
link: "Link {index}",
|
||||||
|
links: "Portfoilo/Social Media Links",
|
||||||
|
storeDescription: "Store Description",
|
||||||
|
storeDescriptionDesc: "Briefly describe your design style and store features...",
|
||||||
|
storeDescriptionErr: "Please enter store description",
|
||||||
|
cropBrandBanner: "Crop Brand Banner",
|
||||||
|
cropAvatar: "Crop Avatar",
|
||||||
|
cropFrom: "Crop from:",
|
||||||
|
sketch: "Sketch",
|
||||||
|
mainProductImage: "Main Product Image",
|
||||||
|
cropPreview: "Crop Preview",
|
||||||
|
imageClipCoverTip: "Align crown to top, mid-thigh to bottom for best results.",
|
||||||
|
imageClipMainProductImageTip: "Align crown to top, foot base to bottom for best results.",
|
||||||
|
imageClipSketchTip: "Align crown to top, foot base to bottom for best results.",
|
||||||
|
imageClipApparelTip: "Trim whitespace and center your apparel sketch.",
|
||||||
|
// 我的订单
|
||||||
|
totalRevenue: "Total Revenue",
|
||||||
|
totalPurchases: "Total Purchases",
|
||||||
|
totalViews: "Total Views",
|
||||||
|
allInvoice: "All Invoice",
|
||||||
|
myOrdersTip: "A summary of all completed transactions.",
|
||||||
|
myOrdersSearchPlaceholder: "Search by item name or order ID",
|
||||||
|
orderId: "Order ID",
|
||||||
|
item: "Item",
|
||||||
|
price: "Price",
|
||||||
|
buyerUsername: "Buyer Username",
|
||||||
|
date: "Date",
|
||||||
|
dateTimeFormat: 'SM D, YYYY<br/> h:mm A',
|
||||||
|
// 设置
|
||||||
|
notifications: "Notifications",
|
||||||
|
notificationsTitle: "New order notification",
|
||||||
|
notificationsTip: "Receive an inbox message when a new order is placed.",
|
||||||
|
payout: "Payout",
|
||||||
|
payoutTitle: "Payment Providers",
|
||||||
|
payoutTip: "Select how you want to receive payments.",
|
||||||
|
unbound: "Unbound",
|
||||||
|
manage: "Manage",
|
||||||
|
bindNow: "Bind Now",
|
||||||
|
paymentCurrency: "Payment Currency",
|
||||||
|
HKD: "HKD - Hong Kong Dollar",
|
||||||
|
fixed: "Fixed",
|
||||||
|
dataPrivacy: "Data & Privacy",
|
||||||
|
dataPrivacyTitle: "Copyright licence",
|
||||||
|
dataPrivacyTip1: "A licence certificate is automatically included with every purchase download. View the default licensing terms applied to your listings.",
|
||||||
|
dataPrivacyTip2: "BThis licence is issued by Code-Create and is legally binding upon purchase. It certifies the buyer's right to use the purchased design asset in accordance with the terms below.",
|
||||||
|
dataPrivacyTip3: "For custom licensing arrangements, <span onclick='{click}()'>contact us</span>.",
|
||||||
|
downloadToView: "Download to View",
|
||||||
|
stopSelling: "Stop Selling",
|
||||||
|
stopSellingTitle: "Deactivate seller account",
|
||||||
|
stopSellingTip: "Permanently deactivate your seller account. All listings and invoice records will be deleted. You may re-register as a seller in the future, but your previous sales data cannot be recovered.",
|
||||||
|
deactivate: "Deactivate",
|
||||||
|
newListing: "New Listing",
|
||||||
|
draftMessage: 'Product moved to drafts and stats reset.',
|
||||||
|
publishMessage: 'Item is now live on the Marketplace.',
|
||||||
|
ActiveListings: 'Active Listings',
|
||||||
|
Praka: 'Praka',
|
||||||
|
Drafts: 'Drafts',
|
||||||
|
Cancel: 'Cancel',
|
||||||
|
Delete: 'Delete',
|
||||||
|
DeleteConfirm: 'Delete this listing?',
|
||||||
|
DeleteDesc: 'Your listing and its details will be permanently removed.',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Draft: 'Draft',
|
||||||
|
Publish: 'Publish',
|
||||||
|
sketchesSelected: 'Sketches selected',
|
||||||
|
Next: 'Next',
|
||||||
|
All: 'All',
|
||||||
|
SeriesDesign: 'Series Design',
|
||||||
|
SingleDesign: 'Single Design',
|
||||||
|
sketchs: 'sketches',
|
||||||
|
MyListings: 'My Listings',
|
||||||
|
MyListingsInfo: 'Active listings and unpublished inventory',
|
||||||
|
SelectCollection: 'Select Collection',
|
||||||
|
SelectSketch: 'Select Sketch',
|
||||||
|
EditListingDetails: 'Edit Listing Details',
|
||||||
|
VideoWarning:'The first selected item is the main product image. Videos cannot be used.',
|
||||||
|
selectSketchMaxNum: 'Select up to 9 sketches',
|
||||||
|
},
|
||||||
|
ApplySeller: {
|
||||||
|
applySellerTitle: 'Apply to Become a Seller',
|
||||||
|
applySellerDesc: 'Join the Stylish Parade and start selling your design work',
|
||||||
|
formTitle: 'Brand Information',
|
||||||
|
formTip: 'Share a few details to set up your seller profile',
|
||||||
|
termsTitle: 'Seller Terms',
|
||||||
|
termsTip: 'Please carefully read and agree to the following terms',
|
||||||
|
agreementTitle: 'AiDA Seller Agreement',
|
||||||
|
agreementTip: 'Please read and agree to the following agreement',
|
||||||
|
agreementItem1: "Provide accurate and truthful personal and store information",
|
||||||
|
agreementItem2: "Only sell original designs or content with proper licensing",
|
||||||
|
agreementItem3: "Maintain high quality standards for all products",
|
||||||
|
agreementItem4: "Respond to customer inquiries within 48 hours",
|
||||||
|
agreementItem5: "Ship orders within promised timeframes",
|
||||||
|
agreementItem6: "Comply with AiDA's terms of service and community guidelines",
|
||||||
|
agreementItem7: "Pay applicable platform fees and transaction charges",
|
||||||
|
agreementItem8: "Handle customer disputes professionally and fairly",
|
||||||
|
agreementAgreement: "I have read and agree to the Seller Agreement, understanding my responsibilities and obligations as a seller on the AiDA platform.",
|
||||||
|
submitApplication: "Submit Application",
|
||||||
|
applicationSubmitted: "Application Submitted",
|
||||||
|
applicationSubmittedTip: "Approval is expected shortly. Upon receipt, please ensure your payout account is linked under <span>Seller Dashboard > Settings</span> prior to listing any items.",
|
||||||
|
auditStatus1_title: "Step 1: Submit Application",
|
||||||
|
auditStatus1_tip: "Fill out the seller information form and agree to our terms",
|
||||||
|
auditStatus2_title: "Step 2: Review & Verification",
|
||||||
|
auditStatus2_tip: "Our team will review your application (typically 1-3 business days)",
|
||||||
|
auditStatus3_title: "Step 3: Start Selling",
|
||||||
|
auditStatus3_tip: "Once approved, access your seller dashboard and start listing products",
|
||||||
|
backToHomepage: "Back to Homepage",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ type SellerBreadcrumbItem = {
|
|||||||
type SellerBreadcrumbList = SellerRouteMetaValue<SellerBreadcrumbItem[]>
|
type SellerBreadcrumbList = SellerRouteMetaValue<SellerBreadcrumbItem[]>
|
||||||
|
|
||||||
const myListingsBreadcrumb: SellerBreadcrumbItem = {
|
const myListingsBreadcrumb: SellerBreadcrumbItem = {
|
||||||
title: "My Listings",
|
titleKey: "Seller.MyListings",
|
||||||
to: { name: "myListingsIndex" }
|
to: { name: "myListingsIndex" }
|
||||||
}
|
}
|
||||||
const selectCollectionBreadcrumb: SellerBreadcrumbItem = {
|
const selectCollectionBreadcrumb: SellerBreadcrumbItem = {
|
||||||
title: "Select Collection",
|
titleKey: "Seller.SelectCollection",
|
||||||
to: { name: "myListingsSelect" }
|
to: { name: "myListingsSelect" }
|
||||||
}
|
}
|
||||||
const selectSketchBreadcrumb: SellerBreadcrumbItem = {
|
const selectSketchBreadcrumb: SellerBreadcrumbItem = {
|
||||||
title: "Select Sketch",
|
titleKey: "Seller.SelectSketch",
|
||||||
to: (route) => {
|
to: (route) => {
|
||||||
const collectionId =
|
const collectionId =
|
||||||
route.params.collectionId ||
|
route.params.collectionId ||
|
||||||
@@ -33,7 +33,7 @@ const selectSketchBreadcrumb: SellerBreadcrumbItem = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const editListingBreadcrumb: SellerBreadcrumbItem = {
|
const editListingBreadcrumb: SellerBreadcrumbItem = {
|
||||||
title: "Edit Listing Details"
|
titleKey: "Seller.EditListingDetails"
|
||||||
}
|
}
|
||||||
const statusBreadcrumb: SellerBreadcrumbItem = {
|
const statusBreadcrumb: SellerBreadcrumbItem = {
|
||||||
titleKey: (route) =>
|
titleKey: (route) =>
|
||||||
@@ -224,8 +224,8 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "becomeSeller",
|
name: "becomeSeller",
|
||||||
meta: {
|
meta: {
|
||||||
enter: "all",
|
enter: "all",
|
||||||
sellerHeaderTitle: "Apply to Become a Seller",
|
sellerHeaderTitle: "ApplySeller.applySellerTitle",
|
||||||
sellerHeaderTip: "Join the Stylish Parade and start selling your design work"
|
sellerHeaderTip: "ApplySeller.applySellerDesc"
|
||||||
},
|
},
|
||||||
component: () => import("@/views/SellerDashboard/BecomeSeller/index.vue")
|
component: () => import("@/views/SellerDashboard/BecomeSeller/index.vue")
|
||||||
},
|
},
|
||||||
@@ -235,6 +235,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
meta: { enter: "all" },
|
meta: { enter: "all" },
|
||||||
component: () => import("@/views/SellerDashboard/index.vue"),
|
component: () => import("@/views/SellerDashboard/index.vue"),
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
meta: { enter: "all" },
|
||||||
|
redirect: "/home/seller/brandProfile"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "brandProfile",
|
path: "brandProfile",
|
||||||
name: "brandProfile",
|
name: "brandProfile",
|
||||||
@@ -257,8 +262,8 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "myListingsIndex",
|
name: "myListingsIndex",
|
||||||
meta: {
|
meta: {
|
||||||
enter: "all",
|
enter: "all",
|
||||||
sellerHeaderTitle: "My Listings",
|
sellerHeaderTitleKey: "Seller.MyListings",
|
||||||
sellerHeaderTip: "Active listings and unpublished inventory.",
|
sellerHeaderTipKey: "Seller.MyListingsInfo",
|
||||||
sellerBreadcrumbs: [myListingsBreadcrumb]
|
sellerBreadcrumbs: [myListingsBreadcrumb]
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
@@ -269,7 +274,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "myListingsSelect",
|
name: "myListingsSelect",
|
||||||
meta: {
|
meta: {
|
||||||
enter: "all",
|
enter: "all",
|
||||||
sellerHeaderTitle: "Select Collection",
|
sellerHeaderTitleKey: "Seller.SelectCollection",
|
||||||
sellerBreadcrumbs: [
|
sellerBreadcrumbs: [
|
||||||
myListingsBreadcrumb,
|
myListingsBreadcrumb,
|
||||||
selectCollectionBreadcrumb
|
selectCollectionBreadcrumb
|
||||||
@@ -283,8 +288,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "myListingsSelectItem",
|
name: "myListingsSelectItem",
|
||||||
meta: {
|
meta: {
|
||||||
enter: "all",
|
enter: "all",
|
||||||
cache: true,
|
sellerHeaderTitleKey: "Seller.SelectSketch",
|
||||||
sellerHeaderTitle: "Select Collection",
|
|
||||||
sellerBreadcrumbs: [
|
sellerBreadcrumbs: [
|
||||||
myListingsBreadcrumb,
|
myListingsBreadcrumb,
|
||||||
selectCollectionBreadcrumb,
|
selectCollectionBreadcrumb,
|
||||||
@@ -299,7 +303,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "EditDetail",
|
name: "EditDetail",
|
||||||
meta: {
|
meta: {
|
||||||
enter: "all",
|
enter: "all",
|
||||||
sellerHeaderTitle: "Edit Listing Details",
|
sellerHeaderTitleKey: "Seller.EditListingDetails",
|
||||||
sellerBreadcrumbs: editListingBreadcrumbs
|
sellerBreadcrumbs: editListingBreadcrumbs
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
@@ -310,7 +314,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "Status",
|
name: "Status",
|
||||||
meta: {
|
meta: {
|
||||||
enter: "all",
|
enter: "all",
|
||||||
sellerHeaderTitle: "Edit Listing Details",
|
sellerHeaderTitleKey: "Seller.EditListingDetails",
|
||||||
sellerBreadcrumbs: listingStatusBreadcrumbs
|
sellerBreadcrumbs: listingStatusBreadcrumbs
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
|
|||||||
@@ -12,19 +12,21 @@ interface DesignerInfo {
|
|||||||
ownerName: string,
|
ownerName: string,
|
||||||
email: string,
|
email: string,
|
||||||
mobile: string,
|
mobile: string,
|
||||||
socialLinks: string,
|
socialLinks: string[] | string,
|
||||||
description: string,
|
description: string,
|
||||||
}
|
}
|
||||||
interface Seller {
|
interface Seller {
|
||||||
isSeller: boolean,
|
isSeller: boolean,
|
||||||
applyStatus: number,
|
applyStatus: number | null,
|
||||||
designerInfo: DesignerInfo,
|
designerInfo: DesignerInfo,
|
||||||
|
firstEnter: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const seller: Module<Seller, RootState> = {
|
const seller: Module<Seller, RootState> = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
isSeller: false,
|
isSeller: false,
|
||||||
|
firstEnter: false,
|
||||||
applyStatus: null,
|
applyStatus: null,
|
||||||
designerInfo: {
|
designerInfo: {
|
||||||
shopName: "--",
|
shopName: "--",
|
||||||
@@ -38,6 +40,9 @@ const seller: Module<Seller, RootState> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
set_firstEnter(state: Seller, value: boolean) {
|
||||||
|
state.firstEnter = value
|
||||||
|
},
|
||||||
set_isSeller(state: Seller, value: boolean) {
|
set_isSeller(state: Seller, value: boolean) {
|
||||||
state.isSeller = value
|
state.isSeller = value
|
||||||
},
|
},
|
||||||
@@ -52,8 +57,24 @@ const seller: Module<Seller, RootState> = {
|
|||||||
...state.designerInfo,
|
...state.designerInfo,
|
||||||
...value,
|
...value,
|
||||||
}
|
}
|
||||||
if (value.socialLinks) {
|
if (typeof value.socialLinks === "string") {
|
||||||
state.designerInfo.socialLinks = JSON.parse(value.socialLinks)
|
state.designerInfo.socialLinks = JSON.parse(value.socialLinks)
|
||||||
|
} else if (Array.isArray(value.socialLinks)) {
|
||||||
|
state.designerInfo.socialLinks = value.socialLinks
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear_state(state: Seller) {
|
||||||
|
state.isSeller = false
|
||||||
|
state.applyStatus = null
|
||||||
|
state.designerInfo = {
|
||||||
|
shopName: "--",
|
||||||
|
avatar: "",
|
||||||
|
brandBanner: "",
|
||||||
|
ownerName: "--",
|
||||||
|
email: "--",
|
||||||
|
mobile: "--",
|
||||||
|
socialLinks: ["--"],
|
||||||
|
description: "--"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -61,7 +82,8 @@ const seller: Module<Seller, RootState> = {
|
|||||||
actions: {
|
actions: {
|
||||||
get_isSeller({ commit }) {
|
get_isSeller({ commit }) {
|
||||||
Https.axiosGet(Https.httpUrls.checkSellerDesigner).then(rv => {
|
Https.axiosGet(Https.httpUrls.checkSellerDesigner).then(rv => {
|
||||||
commit('set_isSeller', !!rv)
|
commit('set_isSeller', !!rv.hasQualification)
|
||||||
|
commit('set_firstEnter', !rv.firstEnter)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async get_designerInfo({ commit }) {
|
async get_designerInfo({ commit }) {
|
||||||
|
|||||||
@@ -470,6 +470,7 @@ export const Https = {
|
|||||||
checkSellerDesigner: '/seller/designer/check', // 检查卖家是否为设计师
|
checkSellerDesigner: '/seller/designer/check', // 检查卖家是否为设计师
|
||||||
getSellerApplyStatus: '/seller/designer/apply/status', // 获取卖家申请状态
|
getSellerApplyStatus: '/seller/designer/apply/status', // 获取卖家申请状态
|
||||||
submitSellerApply: '/seller/designer/apply', // 提交卖家申请
|
submitSellerApply: '/seller/designer/apply', // 提交卖家申请
|
||||||
|
deleteSellerDesigner: '/seller/designer/delete', // 删除设计师
|
||||||
getDesignerInfo: '/seller/designer/info', // 获取设计师信息
|
getDesignerInfo: '/seller/designer/info', // 获取设计师信息
|
||||||
updateDesignerInfo: '/seller/designer/update', // 更新设计师信息
|
updateDesignerInfo: '/seller/designer/update', // 更新设计师信息
|
||||||
getSellerOrderSummary: '/seller/order/summary', // 获取卖家订单数据总览
|
getSellerOrderSummary: '/seller/order/summary', // 获取卖家订单数据总览
|
||||||
|
|||||||
@@ -272,12 +272,12 @@ const navTypeList = (t)=>{
|
|||||||
// },
|
// },
|
||||||
|
|
||||||
|
|
||||||
// {
|
{
|
||||||
// icon:'fi fi-rr-puzzle-alt',
|
icon:'fi fi-rr-puzzle-alt',
|
||||||
// value:'deReconstruction',
|
value:'deReconstruction',
|
||||||
// label:t('Header.toolsDeReconstruction'),
|
label:t('Header.toolsDeReconstruction'),
|
||||||
// router:'tools=deReconstruction'
|
router:'tools=deReconstruction'
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
icon:'fi fi-ss-box-open',
|
icon:'fi fi-ss-box-open',
|
||||||
value:'toProduct',
|
value:'toProduct',
|
||||||
@@ -294,18 +294,18 @@ const navTypeList = (t)=>{
|
|||||||
label:t('Header.toolsToTransferPose'),
|
label:t('Header.toolsToTransferPose'),
|
||||||
router:'tools=poseTransfer'
|
router:'tools=poseTransfer'
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// icon:'fi fi-rr-cubes',
|
icon:'fi fi-rr-cubes',
|
||||||
// value:'patternMaking3D',
|
value:'patternMaking3D',
|
||||||
// label:t('Header.toolsPatternMaking'),
|
label:t('Header.toolsPatternMaking'),
|
||||||
// router:'tools=patternMaking3D'
|
router:'tools=patternMaking3D'
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// icon:'fi fi-rr-pen-swirl',
|
icon:'fi fi-rr-pen-swirl',
|
||||||
// value:'canvasUpload',
|
value:'canvasUpload',
|
||||||
// label:t('Header.toolsCanvas'),
|
label:t('Header.toolsCanvas'),
|
||||||
// router:'tools=canvasUpload'
|
router:'tools=canvasUpload'
|
||||||
// },
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
library:{
|
library:{
|
||||||
|
|||||||
@@ -709,3 +709,37 @@ export {
|
|||||||
sketchToMask,
|
sketchToMask,
|
||||||
isValidUrl
|
isValidUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 时间格式化-自定义格式
|
||||||
|
* @param value 时间对象|时间戳|时间字符串
|
||||||
|
* @param format 格式化字符串,默认值为 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
* @returns 格式化后的时间字符串
|
||||||
|
*/
|
||||||
|
export function FormatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
|
||||||
|
const d = new Date(value);
|
||||||
|
if (!d || isNaN(d.getTime())) return 'Invalid Date';
|
||||||
|
const pad = (n) => String(n).padStart(2, '0');
|
||||||
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
const tokens = {
|
||||||
|
YYYY: d.getFullYear(),
|
||||||
|
YY: String(d.getFullYear()).slice(-2),
|
||||||
|
MM: pad(d.getMonth() + 1),
|
||||||
|
M: d.getMonth() + 1,
|
||||||
|
SM: months[d.getMonth()],
|
||||||
|
DD: pad(d.getDate()),
|
||||||
|
D: d.getDate(),
|
||||||
|
HH: pad(d.getHours()),
|
||||||
|
H: d.getHours(),
|
||||||
|
hh: pad(d.getHours() % 12 || 12),
|
||||||
|
h: d.getHours() % 12 || 12,
|
||||||
|
mm: pad(d.getMinutes()),
|
||||||
|
m: d.getMinutes(),
|
||||||
|
ss: pad(d.getSeconds()),
|
||||||
|
s: d.getSeconds(),
|
||||||
|
A: d.getHours() < 12 ? 'AM' : 'PM',
|
||||||
|
a: d.getHours() < 12 ? 'am' : 'pm'
|
||||||
|
}
|
||||||
|
const reg = new RegExp(Object.keys(tokens).join('|'), 'g')
|
||||||
|
return format.replace(reg, match => tokens[match]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -318,7 +318,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="homeMain_user">
|
<div class="homeMain_user">
|
||||||
<div class="homeMain_user_icon" @click="openAccount">
|
<div class="homeMain_user_icon">
|
||||||
<img :src="userDetail.avatar" alt="" />
|
<img :src="userDetail.avatar" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="homeMain_user_detail">
|
<div class="homeMain_user_detail">
|
||||||
@@ -373,6 +373,10 @@
|
|||||||
<i class="fi fi-rs-notebook"></i>
|
<i class="fi fi-rs-notebook"></i>
|
||||||
<span class="select_item_des">{{ $t('Header.ViewOrders') }}</span>
|
<span class="select_item_des">{{ $t('Header.ViewOrders') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="select_item" @click="openAccount">
|
||||||
|
<span class="icon"><svg-icon name="home" /></span>
|
||||||
|
<span class="select_item_des">{{ $t('Header.PersonalCenter') }}</span>
|
||||||
|
</div>
|
||||||
<div class="select_item" @click="onBecomeSeller" v-if="!isSeller">
|
<div class="select_item" @click="onBecomeSeller" v-if="!isSeller">
|
||||||
<span class="icon"><svg-icon name="seller-sellerIndex" /></span>
|
<span class="icon"><svg-icon name="seller-sellerIndex" /></span>
|
||||||
<span class="select_item_des">{{ $t('Header.BecomeSeller') }}</span>
|
<span class="select_item_des">{{ $t('Header.BecomeSeller') }}</span>
|
||||||
@@ -380,7 +384,7 @@
|
|||||||
<div class="select_item" @click="onSellerDashboard" v-else>
|
<div class="select_item" @click="onSellerDashboard" v-else>
|
||||||
<span class="icon"><svg-icon name="seller-sellerIndex" /></span>
|
<span class="icon"><svg-icon name="seller-sellerIndex" /></span>
|
||||||
<span class="select_item_des">{{ $t('Header.SellerDashboard') }}</span>
|
<span class="select_item_des">{{ $t('Header.SellerDashboard') }}</span>
|
||||||
<a-badge :dot="true"></a-badge>
|
<a-badge v-if="firstEnterSeller" :dot="true"></a-badge>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
class="select_item"
|
class="select_item"
|
||||||
@@ -1076,10 +1080,14 @@ export default defineComponent({
|
|||||||
const isSeller = computed(() => {
|
const isSeller = computed(() => {
|
||||||
return store.state.seller.isSeller
|
return store.state.seller.isSeller
|
||||||
})
|
})
|
||||||
|
const firstEnterSeller = computed(() => {
|
||||||
|
return store.state.seller.firstEnter
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
store,
|
store,
|
||||||
userDetail,
|
userDetail,
|
||||||
isSeller,
|
isSeller,
|
||||||
|
firstEnterSeller,
|
||||||
t,
|
t,
|
||||||
...toRefs(homeMainData),
|
...toRefs(homeMainData),
|
||||||
...toRefs(historyData),
|
...toRefs(historyData),
|
||||||
@@ -1264,6 +1272,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onSellerDashboard(){
|
onSellerDashboard(){
|
||||||
this.$router.push({ name: 'brandProfile' })
|
this.$router.push({ name: 'brandProfile' })
|
||||||
|
this.store.commit('seller/set_firstEnter', false)
|
||||||
},
|
},
|
||||||
//教程
|
//教程
|
||||||
getTutorial() {
|
getTutorial() {
|
||||||
|
|||||||
@@ -2,56 +2,58 @@
|
|||||||
<div class="seller-apply">
|
<div class="seller-apply">
|
||||||
<div class="session">
|
<div class="session">
|
||||||
<div class="content mini-scrollbar">
|
<div class="content mini-scrollbar">
|
||||||
<div class="title">Brand Information</div>
|
<div class="title">{{ $t("ApplySeller.formTitle") }}</div>
|
||||||
<div class="tip">Share a few details to set up your seller profile</div>
|
<div class="tip">{{ $t("ApplySeller.formTip") }}</div>
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<a-form :model="formData" :rules="formRules" layout="vertical" ref="formRef">
|
<a-form :model="formData" :rules="formRules" layout="vertical" ref="formRef">
|
||||||
<a-form-item label="Store Name" name="storeName">
|
<a-form-item :label="$t('Seller.storeName')" name="storeName">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="formData.storeName"
|
v-model:value="formData.storeName"
|
||||||
placeholder="Enter the store name"
|
:placeholder="$t('Seller.storeNameDesc')"
|
||||||
:maxlength="80"
|
:maxlength="80"
|
||||||
/>
|
/>
|
||||||
<span class="tip-length">{{ formData.storeName.length }}/80</span>
|
<span class="tip-length">{{ formData.storeName.length }}/80</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Owner’s Full Name" name="fullName">
|
<a-form-item :label="$t('Seller.ownerName')" name="fullName">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="formData.fullName"
|
v-model:value="formData.fullName"
|
||||||
placeholder="Enter store owner's full name"
|
:placeholder="$t('Seller.ownerNameDesc')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<a-form-item label="Email" name="email">
|
<a-form-item :label="$t('Seller.email')" name="email">
|
||||||
<a-input
|
<a-input
|
||||||
type="email"
|
type="email"
|
||||||
v-model:value="formData.email"
|
v-model:value="formData.email"
|
||||||
placeholder="Enter email"
|
:placeholder="$t('Seller.emailDesc')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Phone Number" name="phoneNumber">
|
<a-form-item :label="$t('Seller.mobile')" name="mobile">
|
||||||
<a-input
|
<a-input
|
||||||
type="tel"
|
type="tel"
|
||||||
v-model:value="formData.phoneNumber"
|
v-model:value="formData.mobile"
|
||||||
placeholder="Enter phone number"
|
:placeholder="$t('Seller.mobileDesc')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
<a-form-item label="Store Description" name="description">
|
<a-form-item :label="$t('Seller.storeDescription')" name="description">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="formData.description"
|
v-model:value="formData.description"
|
||||||
placeholder="Briefly describe your design style and store features..."
|
:placeholder="$t('Seller.storeDescriptionDesc')"
|
||||||
:maxlength="500"
|
:maxlength="500"
|
||||||
/>
|
/>
|
||||||
<span class="tip-length">{{ formData.description.length }}/500</span>
|
<span class="tip-length">{{ formData.description.length }}/500</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Portfoilo/Social Media Links">
|
<a-form-item :label="$t('Seller.links')">
|
||||||
<a-input
|
<a-input
|
||||||
placeholder="https://"
|
placeholder="https://"
|
||||||
v-for="(v, i) in formData.links"
|
v-for="(v, i) in formData.links"
|
||||||
:key="i"
|
:key="i"
|
||||||
v-model:value="formData.links[i]"
|
v-model:value="formData.links[i]"
|
||||||
>
|
>
|
||||||
<template #prefix>Link {{ i + 1 }}</template>
|
<template #prefix>{{
|
||||||
|
$t("Seller.link", { index: i + 1 })
|
||||||
|
}}</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input
|
||||||
placeholder="https://"
|
placeholder="https://"
|
||||||
@@ -71,33 +73,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="session">
|
<div class="session">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">Brand Information</div>
|
<div class="title">{{ $t("ApplySeller.termsTitle") }}</div>
|
||||||
<div class="tip">Share a few details to set up your seller profile</div>
|
<div class="tip">{{ $t("ApplySeller.termsTip") }}</div>
|
||||||
<div class="agreement">
|
<div class="agreement">
|
||||||
<div class="title">AiDA Seller Agreement</div>
|
<div class="title">{{ $t("ApplySeller.agreementTitle") }}</div>
|
||||||
<div class="tip">
|
<div class="tip">{{ $t("ApplySeller.agreementTip") }}</div>
|
||||||
By checking the box below, you agree to comply with the following terms:
|
|
||||||
</div>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Provide accurate and truthful personal and store information</li>
|
<li v-for="(v, i) in 8" :key="i">
|
||||||
<li>Only sell original designs or content with proper licensing</li>
|
{{ $t(`ApplySeller.agreementItem${i + 1}`) }}
|
||||||
<li>Maintain high quality standards for all products</li>
|
</li>
|
||||||
<li>Respond to customer inquiries within 48 hours</li>
|
|
||||||
<li>Ship orders within promised timeframes</li>
|
|
||||||
<li>Comply with AiDA's terms of service and community guidelines</li>
|
|
||||||
<li>Pay applicable platform fees and transaction charges</li>
|
|
||||||
<li>Handle customer disputes professionally and fairly</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a-checkbox class="agree-agreement" v-model:checked="isAgreement">
|
<a-checkbox class="agree-agreement" v-model:checked="isAgreement">
|
||||||
I have read and agree to the Seller Agreement, understanding my responsibilities
|
{{ $t("ApplySeller.agreementAgreement") }}
|
||||||
and obligations as a seller on the AiDA platform.
|
|
||||||
</a-checkbox>
|
</a-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button class="cancel" @click="onCancel">Cancel</button>
|
<button class="cancel" @click="onCancel">{{ $t("Seller.cancel") }}</button>
|
||||||
<button class="submit" :disabled="!isAgreement" @click="onSubmit">
|
<button class="submit" :disabled="!isAgreement" @click="onSubmit">
|
||||||
Submit Application
|
{{ $t("ApplySeller.submitApplication") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,24 +100,26 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive } from "vue"
|
import { ref, reactive } from "vue"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
const { t } = useI18n()
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
import { Https } from "@/tool/https"
|
import { Https } from "@/tool/https"
|
||||||
const emit = defineEmits(["submit"])
|
const emit = defineEmits(["submit"])
|
||||||
const formRules = {
|
const formRules = {
|
||||||
storeName: [{ required: true, message: "Enter the store name" }],
|
storeName: [{ required: true, message: t("Seller.storeNameDesc") }],
|
||||||
fullName: [{ required: true, message: "Enter store owner's full name" }],
|
fullName: [{ required: true, message: t("Seller.ownerNameDesc") }],
|
||||||
email: [{ required: true, message: "Enter email" }],
|
email: [{ required: true, message: t("Seller.emailDesc") }],
|
||||||
phoneNumber: [{ required: true, message: "Enter phone number" }],
|
mobile: [{ required: true, message: t("Seller.mobileDesc") }],
|
||||||
description: [{ required: true, message: "Enter store description" }]
|
description: [{ required: true, message: t("Seller.storeDescriptionErr") }]
|
||||||
}
|
}
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
storeName: "",
|
storeName: "",
|
||||||
fullName: "",
|
fullName: "",
|
||||||
email: "",
|
email: "",
|
||||||
phoneNumber: "",
|
mobile: "",
|
||||||
description: "",
|
description: "",
|
||||||
links: ["", ""]
|
links: ["", ""]
|
||||||
})
|
})
|
||||||
@@ -140,7 +136,6 @@
|
|||||||
formRef.value
|
formRef.value
|
||||||
.validate()
|
.validate()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(formData)
|
|
||||||
const data = {
|
const data = {
|
||||||
// userId: 0,
|
// userId: 0,
|
||||||
shopName: formData.storeName,
|
shopName: formData.storeName,
|
||||||
@@ -148,7 +143,7 @@
|
|||||||
// brandBanner: "",
|
// brandBanner: "",
|
||||||
ownerName: formData.fullName,
|
ownerName: formData.fullName,
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
mobile: formData.phoneNumber,
|
mobile: formData.mobile,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
socialLinks: JSON.stringify(formData.links.filter((v) => v))
|
socialLinks: JSON.stringify(formData.links.filter((v) => v))
|
||||||
}
|
}
|
||||||
@@ -173,12 +168,18 @@
|
|||||||
> .session {
|
> .session {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0.1rem;
|
||||||
> .content {
|
> .content {
|
||||||
max-height: 100%;
|
padding: 0.4rem 1.5rem;
|
||||||
padding: 2.4rem;
|
border: 2rem solid transparent;
|
||||||
border: 1px solid #b0b0b0;
|
border-right-width: 0.4rem;
|
||||||
|
border-left-width: 0.4rem;
|
||||||
border-radius: 2.4rem;
|
border-radius: 2.4rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 0 0 0.1rem #b0b0b0;
|
||||||
|
// margin: 0.2rem;
|
||||||
|
|
||||||
> .title {
|
> .title {
|
||||||
font-size: 2.2rem;
|
font-size: 2.2rem;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="seller-review">
|
<div class="seller-review">
|
||||||
<img class="success" src="@/assets/images/seller/success-1.png" />
|
<img class="success" src="@/assets/images/seller/success-1.png" />
|
||||||
<div class="title">Application Submitted</div>
|
<div class="title">{{ $t("ApplySeller.applicationSubmitted") }}</div>
|
||||||
<div class="tip">
|
<div
|
||||||
Our team will review your application and get back to you within 1–3 business days.
|
class="tip"
|
||||||
You'll receive a notification in your email once a decision has been made.
|
v-html="$t('ApplySeller.applicationSubmittedTip', { click: 'onPersonalCenter' })"
|
||||||
</div>
|
></div>
|
||||||
<div class="step-list">
|
<div class="step-list">
|
||||||
<div v-for="v in list" :key="v.title" class="step-item">
|
<div v-for="v in list" :key="v.title" class="step-item">
|
||||||
<img v-show="!v.active" src="@/assets/images/seller/success-0.png" />
|
<img v-show="!v.active" src="@/assets/images/seller/success-0.png" />
|
||||||
@@ -16,7 +16,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="home-btn" @click="onBackToHome">Back to Homepage</button>
|
<button class="home-btn" @click="onBackToHome">
|
||||||
|
{{ $t("ApplySeller.backToHomepage") }}
|
||||||
|
</button>
|
||||||
|
<div class="tip">ID: {{ userId }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -25,35 +28,41 @@
|
|||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
import { useStore } from "vuex"
|
import { useStore } from "vuex"
|
||||||
import { ApplyStatus } from "@/store/seller/index.d"
|
import { ApplyStatus } from "@/store/seller/index.d"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
const userId = computed(() => store.state.UserHabit.userDetail.userId)
|
||||||
const applyStatus = computed(() => store.state.seller.applyStatus)
|
const applyStatus = computed(() => store.state.seller.applyStatus)
|
||||||
const list = computed(() => [
|
const list = computed(() => [
|
||||||
{
|
{
|
||||||
title: "Step 1: Submit Application",
|
title: t("ApplySeller.auditStatus1_title"),
|
||||||
tip: "Fill out the seller information form and agree to our terms",
|
tip: t("ApplySeller.auditStatus1_tip"),
|
||||||
active: [ApplyStatus.Pending, ApplyStatus.Approved].includes(applyStatus.value)
|
active: [ApplyStatus.Pending, ApplyStatus.Approved].includes(applyStatus.value)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Step 2: Review & Verification",
|
title: t("ApplySeller.auditStatus2_title"),
|
||||||
tip: "Our team will review your application (typically 1-3 business days)",
|
tip: t("ApplySeller.auditStatus2_tip"),
|
||||||
active: applyStatus.value === ApplyStatus.Approved
|
active: applyStatus.value === ApplyStatus.Approved
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Step 3: Start Selling",
|
title: t("ApplySeller.auditStatus3_title"),
|
||||||
tip: "Once approved, access your seller dashboard and start listing products ",
|
tip: t("ApplySeller.auditStatus3_tip"),
|
||||||
active: applyStatus.value === ApplyStatus.Approved
|
active: applyStatus.value === ApplyStatus.Approved
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const onBackToHome = () => {
|
const onBackToHome = () => {
|
||||||
router.push({ name: "home" })
|
router.push({ name: "home" })
|
||||||
}
|
}
|
||||||
|
window.onPersonalCenter = () => {
|
||||||
|
router.push("/home/account")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.seller-review {
|
.seller-review {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 60rem;
|
|
||||||
height: 90%;
|
height: 90%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -73,15 +82,20 @@
|
|||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
> .tip {
|
> .tip {
|
||||||
font-family: pingfang_medium;
|
width: 66rem;
|
||||||
|
font-family: pingfang_regular;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
line-height: 170%;
|
line-height: 170%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #585858;
|
color: #585858;
|
||||||
|
&:deep(span) {
|
||||||
|
color: #585858;
|
||||||
|
font-family: pingfang_heavy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
> .step-list {
|
> .step-list {
|
||||||
|
width: 60rem;
|
||||||
margin: 2.6rem 0;
|
margin: 2.6rem 0;
|
||||||
width: 100%;
|
|
||||||
padding: 1.6rem;
|
padding: 1.6rem;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<div class="brand-info">
|
<div class="brand-info">
|
||||||
<a-form :model="formData" :rules="isEdit ? formRules : {}" layout="vertical" ref="formRef">
|
<a-form :model="formData" :rules="isEdit ? formRules : {}" layout="vertical" ref="formRef">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<a-form-item label="Store Name" name="shopName">
|
<a-form-item :label="$t('Seller.storeName')" name="shopName">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="formData.shopName"
|
v-model:value="formData.shopName"
|
||||||
placeholder="Enter the store name"
|
:placeholder="$t('Seller.storeNameDesc')"
|
||||||
:maxlength="80"
|
:maxlength="80"
|
||||||
:readonly="!isEdit"
|
:readonly="!isEdit"
|
||||||
/>
|
/>
|
||||||
@@ -13,34 +13,34 @@
|
|||||||
>{{ formData.shopName.length }}/80</span
|
>{{ formData.shopName.length }}/80</span
|
||||||
>
|
>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Owner’s Full Name" name="ownerName">
|
<a-form-item :label="$t('Seller.ownerName')" name="ownerName">
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="formData.ownerName"
|
v-model:value="formData.ownerName"
|
||||||
placeholder="Enter store owner's full name"
|
:placeholder="$t('Seller.ownerNameDesc')"
|
||||||
:readonly="!isEdit"
|
:readonly="!isEdit"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<a-form-item label="Email" name="email">
|
<a-form-item :label="$t('Seller.email')" name="email">
|
||||||
<a-input
|
<a-input
|
||||||
type="email"
|
type="email"
|
||||||
v-model:value="formData.email"
|
v-model:value="formData.email"
|
||||||
placeholder="Enter email"
|
:placeholder="$t('Seller.emailDesc')"
|
||||||
:readonly="!isEdit"
|
:readonly="!isEdit"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Phone Number" name="mobile">
|
<a-form-item :label="$t('Seller.mobile')" name="mobile">
|
||||||
<a-input
|
<a-input
|
||||||
type="tel"
|
type="tel"
|
||||||
v-model:value="formData.mobile"
|
v-model:value="formData.mobile"
|
||||||
placeholder="Enter phone number"
|
:placeholder="$t('Seller.mobileDesc')"
|
||||||
:readonly="!isEdit"
|
:readonly="!isEdit"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<a-form-item label="Portfoilo/Social Media Links">
|
<a-form-item :label="$t('Seller.links')">
|
||||||
<a-input
|
<a-input
|
||||||
placeholder="https://"
|
placeholder="https://"
|
||||||
v-for="(v, i) in formData.socialLinks"
|
v-for="(v, i) in formData.socialLinks"
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
v-model:value="formData.socialLinks[i]"
|
v-model:value="formData.socialLinks[i]"
|
||||||
:readonly="!isEdit"
|
:readonly="!isEdit"
|
||||||
>
|
>
|
||||||
<template #prefix>Link {{ i + 1 }}</template>
|
<template #prefix>{{ $t("Seller.link", { index: i + 1 }) }}</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
<a-input
|
<a-input
|
||||||
placeholder="https://"
|
placeholder="https://"
|
||||||
@@ -63,10 +63,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Store Description" name="description">
|
<a-form-item :label="$t('Seller.storeDescription')" name="description">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="formData.description"
|
v-model:value="formData.description"
|
||||||
placeholder="Briefly describe your design style and store features..."
|
:placeholder="$t('Seller.storeDescriptionDesc')"
|
||||||
:maxlength="500"
|
:maxlength="500"
|
||||||
:readonly="!isEdit"
|
:readonly="!isEdit"
|
||||||
/>
|
/>
|
||||||
@@ -83,6 +83,9 @@
|
|||||||
import { ref, reactive, watch } from "vue"
|
import { ref, reactive, watch } from "vue"
|
||||||
import { useRoute, useRouter } from "vue-router"
|
import { useRoute, useRouter } from "vue-router"
|
||||||
import { useStore } from "vuex"
|
import { useStore } from "vuex"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const designerInfo = computed(() => store.state.seller.designerInfo)
|
const designerInfo = computed(() => store.state.seller.designerInfo)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -94,11 +97,11 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const formRules = {
|
const formRules = {
|
||||||
shopName: [{ required: true, message: "Enter the store name" }],
|
shopName: [{ required: true, message: t("Seller.storeNameDesc") }],
|
||||||
ownerName: [{ required: true, message: "Enter store owner's full name" }],
|
ownerName: [{ required: true, message: t("Seller.ownerNameDesc") }],
|
||||||
email: [{ required: true, message: "Enter email" }],
|
email: [{ required: true, message: t("Seller.emailDesc") }],
|
||||||
mobile: [{ required: true, message: "Enter phone number" }],
|
mobile: [{ required: true, message: t("Seller.mobileDesc") }],
|
||||||
description: [{ required: true, message: "Enter store description" }]
|
description: [{ required: true, message: t("Seller.storeDescriptionErr") }]
|
||||||
}
|
}
|
||||||
|
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
|
|||||||
@@ -10,34 +10,35 @@
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
wrapClassName="#app"
|
wrapClassName="#app"
|
||||||
:keyboard="false"
|
:keyboard="false"
|
||||||
|
:destroyOnClose="true"
|
||||||
>
|
>
|
||||||
<div class="image-clip-dialog-box">
|
<div class="image-clip-dialog-box">
|
||||||
<div class="header" :class="{ 'is-product': data.isProduct }">
|
<div class="header" :class="{ 'is-product': data.isProduct }">
|
||||||
<div class="title">{{ data.title }}</div>
|
<div class="title">{{ data.title }}</div>
|
||||||
<div class="right flex">
|
<div class="right flex">
|
||||||
<div v-if="coverOrigin.length > 1" class="origin-container flex align-center">
|
<div v-if="coverOrigin.length > 1" class="origin-container flex align-center">
|
||||||
<span>Crop from: </span>
|
<span>{{ $t("Seller.cropFrom") }}</span>
|
||||||
<div class="origin-select flex align-center">
|
<div class="origin-select flex align-center">
|
||||||
<div
|
<div
|
||||||
class="origin-item sketch"
|
class="origin-item sketch"
|
||||||
:class="{ selected: currentOrigin === 'sketch' }"
|
:class="{ selected: currentOrigin === 'sketch' }"
|
||||||
@click="handleChangeOrigin('sketch')"
|
@click="handleChangeOrigin('sketch')"
|
||||||
>
|
>
|
||||||
Sketch
|
{{ $t("Seller.sketch") }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="origin-item product"
|
class="origin-item product"
|
||||||
:class="{ selected: currentOrigin === 'mainProducImage' }"
|
:class="{ selected: currentOrigin === 'mainProductImage' }"
|
||||||
@click="handleChangeOrigin('mainProductImage')"
|
@click="handleChangeOrigin('mainProductImage')"
|
||||||
>
|
>
|
||||||
Main product image
|
{{ $t("Seller.mainProductImage") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="submit" v-if="!data.isPreview" @click="onSubmit">
|
<div class="submit" v-if="!data.isPreview" @click="onSubmit">
|
||||||
<svg-icon name="seller-dui" size="24" />
|
<svg-icon name="seller-dui" size="24" />
|
||||||
</div>
|
</div>
|
||||||
<button @click="onCancel">Cancel</button>
|
<button @click="onCancel">{{ $t("Seller.cancel") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" :class="{ 'is-product': data.isProduct }">
|
<div class="content" :class="{ 'is-product': data.isProduct }">
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
>
|
>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<span class="icon"><svg-icon name="seller-preview" size="24" /></span>
|
<span class="icon"><svg-icon name="seller-preview" size="24" /></span>
|
||||||
<span class="label">Crop Preview</span>
|
<span class="label">{{ $t("Seller.cropPreview") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-image">
|
<div class="preview-image">
|
||||||
<img :src="data.preview_url" />
|
<img :src="data.preview_url" />
|
||||||
@@ -79,254 +80,267 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed } from "vue"
|
import { ref, reactive, computed } from "vue"
|
||||||
import ImageClip from "./image-clip.vue"
|
import ImageClip from "./image-clip.vue"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ""
|
default: ""
|
||||||
},
|
},
|
||||||
isProduct: {
|
isProduct: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const tips = computed(() => {
|
const tips = computed(() => {
|
||||||
if (props.type === "cover") {
|
if (props.type === "cover") {
|
||||||
return "Align crown to top, mid-thigh to bottom for best results."
|
return t("Seller.imageClipCoverTip")
|
||||||
}
|
}
|
||||||
if (props.type === "mainProductImage") {
|
if (props.type === "mainProductImage") {
|
||||||
return "Align crown to top, foot base to bottom for best results."
|
return t("Seller.imageClipMainProductImageTip")
|
||||||
}
|
}
|
||||||
if (props.type === "sketch") {
|
if (props.type === "sketch") {
|
||||||
return "Align crown to top, foot base to bottom for best results."
|
return t("Seller.imageClipSketchTip")
|
||||||
}
|
}
|
||||||
if (props.type === "apparel") {
|
if (props.type === "apparel") {
|
||||||
return "Trim whitespace and center your apparel sketch."
|
return t("Seller.imageClipApparelTip")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
url: "",
|
url: "",
|
||||||
title: "Crop Image",
|
title: "Crop Image",
|
||||||
preview_url: "",
|
preview_url: "",
|
||||||
ratio: [1, 1],
|
ratio: [1, 1],
|
||||||
isPreview: true,
|
isPreview: true,
|
||||||
callback: null,
|
callback: null,
|
||||||
isProduct: false // 是否商品编辑
|
isProduct: false // 是否商品编辑
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentOrigin = ref("sketch")
|
const currentOrigin = ref("sketch")
|
||||||
const coverOrigin = ref([])
|
const coverOrigin = ref([])
|
||||||
const handleChangeOrigin = (type) => {
|
const handleChangeOrigin = (type) => {
|
||||||
currentOrigin.value = type
|
const targetOrigin = coverOrigin.value.find((el) => el.type === type)
|
||||||
data.url = coverOrigin.value.filter((el) => el.type === type)[0].url
|
if (!targetOrigin) return
|
||||||
}
|
|
||||||
|
currentOrigin.value = type
|
||||||
const show = ref(false)
|
data.url = targetOrigin.url
|
||||||
const open = (url, callback, options, origin) => {
|
}
|
||||||
if (!props.isProduct) {
|
|
||||||
if (!url || !callback) return
|
const show = ref(false)
|
||||||
}
|
const open = (url, callback, options, origin) => {
|
||||||
data.url = url
|
if (!props.isProduct) {
|
||||||
data.callback = callback
|
if (!url || !callback) return
|
||||||
data.ratio = options.ratio || [1, 1]
|
}
|
||||||
data.isPreview = true
|
|
||||||
data.preview_url = ""
|
coverOrigin.value = []
|
||||||
data.title = options.title || "Crop Image"
|
data.url = null
|
||||||
if (options) {
|
currentOrigin.value = "sketch"
|
||||||
if (options.hasOwnProperty("isPreview")) data.isPreview = options.isPreview
|
|
||||||
data.isProduct = options.isProduct
|
data.url = url
|
||||||
}
|
data.callback = callback
|
||||||
if (origin?.length) {
|
data.ratio = options.ratio || [1, 1]
|
||||||
coverOrigin.value = origin
|
data.isPreview = true
|
||||||
data.url = origin[0].url
|
data.preview_url = ""
|
||||||
}
|
data.title = options.title || "Crop Image"
|
||||||
show.value = true
|
if (options) {
|
||||||
}
|
if (options.hasOwnProperty("isPreview")) data.isPreview = options.isPreview
|
||||||
const onCancel = () => {
|
data.isProduct = options.isProduct
|
||||||
show.value = false
|
}
|
||||||
}
|
if (origin?.length) {
|
||||||
const imageClipRef = ref(null)
|
coverOrigin.value = origin
|
||||||
const onSubmit = () => {
|
const defaultOrigin = origin.find((el) => el.type === options?.coverFrom) || origin[0]
|
||||||
imageClipRef.value.getCropBlob().then((blob) => {
|
currentOrigin.value = defaultOrigin.type
|
||||||
if (data.callback) data.callback(blobToFile(blob, "image.png"))
|
data.url = defaultOrigin.url
|
||||||
onCancel()
|
}
|
||||||
|
show.value = true
|
||||||
|
}
|
||||||
|
const onCancel = () => {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
const imageClipRef = ref(null)
|
||||||
|
const onSubmit = () => {
|
||||||
|
imageClipRef.value.getCropBlob().then((blob) => {
|
||||||
|
if (data.callback) data.callback(blobToFile(blob, "image.png"), currentOrigin.value)
|
||||||
|
onCancel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 将blob转换为file对象
|
||||||
|
const blobToFile = (blob, fileName) => {
|
||||||
|
return new File([blob], fileName, { type: blob.type })
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
})
|
})
|
||||||
}
|
|
||||||
// 将blob转换为file对象
|
|
||||||
const blobToFile = (blob, fileName) => {
|
|
||||||
return new File([blob], fileName, { type: blob.type })
|
|
||||||
}
|
|
||||||
defineExpose({
|
|
||||||
open
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.image-clip-dialog-box {
|
.image-clip-dialog-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
.submit {
|
|
||||||
width: 4rem;
|
|
||||||
height: 4rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #262626;
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
> .header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: column;
|
||||||
margin-bottom: 5rem;
|
.submit {
|
||||||
&.is-product {
|
width: 4rem;
|
||||||
margin-bottom: 2.4rem;
|
height: 4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #262626;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
> .title {
|
> .header {
|
||||||
font-family: pingfang_heavy;
|
display: flex;
|
||||||
font-size: 2.4rem;
|
justify-content: space-between;
|
||||||
color: #595959;
|
margin-bottom: 5rem;
|
||||||
|
&.is-product {
|
||||||
|
margin-bottom: 2.4rem;
|
||||||
|
}
|
||||||
|
> .title {
|
||||||
|
font-family: pingfang_heavy;
|
||||||
|
font-size: 2.4rem;
|
||||||
|
color: #595959;
|
||||||
|
}
|
||||||
|
> .right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2rem;
|
||||||
|
> button {
|
||||||
|
width: 10rem;
|
||||||
|
height: 4.8rem;
|
||||||
|
border-radius: 4rem;
|
||||||
|
border: none;
|
||||||
|
background: #e4e5eb;
|
||||||
|
font-family: pingfang_heavy;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.origin-container {
|
||||||
|
font-weight: 400;
|
||||||
|
color: #000;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
.origin-select {
|
||||||
|
margin-left: 1.2rem;
|
||||||
|
height: 4.8rem;
|
||||||
|
border: 1px solid #c7c7c7;
|
||||||
|
border-radius: 3rem;
|
||||||
|
column-gap: 1.2rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
.origin-item {
|
||||||
|
height: 3.2rem;
|
||||||
|
line-height: 3.2rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
&.selected {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
&.sketch {
|
||||||
|
padding: 0 1.9rem;
|
||||||
|
}
|
||||||
|
&.product {
|
||||||
|
padding: 0 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
> .right {
|
> .content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 2rem;
|
|
||||||
> button {
|
|
||||||
width: 10rem;
|
|
||||||
height: 4.8rem;
|
|
||||||
border-radius: 4rem;
|
|
||||||
border: none;
|
|
||||||
background: #e4e5eb;
|
|
||||||
font-family: pingfang_heavy;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.origin-container {
|
|
||||||
font-weight: 400;
|
|
||||||
color: #000;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
.origin-select {
|
|
||||||
margin-left: 1.2rem;
|
|
||||||
height: 4.8rem;
|
|
||||||
border: 1px solid #c7c7c7;
|
|
||||||
border-radius: 3rem;
|
|
||||||
column-gap: 1.2rem;
|
|
||||||
padding: 0.8rem;
|
|
||||||
.origin-item {
|
|
||||||
height: 3.2rem;
|
|
||||||
line-height: 3.2rem;
|
|
||||||
border-radius: 2rem;
|
|
||||||
cursor: pointer;
|
|
||||||
&.selected {
|
|
||||||
background-color: #000;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
&.sketch {
|
|
||||||
padding: 0 1.9rem;
|
|
||||||
}
|
|
||||||
&.product {
|
|
||||||
padding: 0 2.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .content {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.crop-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
.tips {
|
|
||||||
text-align: center;
|
|
||||||
color: #585858;
|
|
||||||
font-size: 1.4rem;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.is-product {
|
|
||||||
column-gap: 18.6rem;
|
|
||||||
.crop-wrapper {
|
.crop-wrapper {
|
||||||
width: initial;
|
width: 100%;
|
||||||
}
|
padding-top: 1.2rem;
|
||||||
}
|
.tips {
|
||||||
> .image-clip {
|
text-align: center;
|
||||||
flex: 1;
|
color: #585858;
|
||||||
&.is-product {
|
font-size: 1.4rem;
|
||||||
width: initial;
|
font-weight: 400;
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .preview {
|
|
||||||
margin-left: 6rem;
|
|
||||||
width: 28rem;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 2.4rem;
|
|
||||||
min-height: 0;
|
|
||||||
|
|
||||||
> .title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 1.2rem;
|
|
||||||
> .label {
|
|
||||||
font-family: pingfang_heavy;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> .preview-image {
|
&.is-product {
|
||||||
width: 100%;
|
column-gap: 18.6rem;
|
||||||
|
.crop-wrapper {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .image-clip {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
&.is-product {
|
||||||
display: flex;
|
width: initial;
|
||||||
align-items: center;
|
flex: none;
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
> .preview-image > img {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
> .submit {
|
|
||||||
margin-top: auto;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
&.is-product {
|
|
||||||
margin-left: 0;
|
|
||||||
> .preview-image > img {
|
|
||||||
width: 20.8rem;
|
|
||||||
height: 36.7rem;
|
|
||||||
box-shadow: 4px 4px 16px 0px #0000000f;
|
|
||||||
border: 1px solid #ededed;
|
|
||||||
}
|
}
|
||||||
&.is-cover {
|
}
|
||||||
> .preview-image > img {
|
> .preview {
|
||||||
width: 29.7rem;
|
margin-left: 6rem;
|
||||||
height: 37.5rem;
|
width: 28rem;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2.4rem;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
> .title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1.2rem;
|
||||||
|
> .label {
|
||||||
|
font-family: pingfang_heavy;
|
||||||
|
font-size: 1.6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.is-apparel {
|
> .preview-image {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
> .preview-image > img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
> .submit {
|
||||||
|
margin-top: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
&.is-product {
|
||||||
|
margin-left: 0;
|
||||||
> .preview-image > img {
|
> .preview-image > img {
|
||||||
width: 100%;
|
width: 20.8rem;
|
||||||
height: auto;
|
height: 36.7rem;
|
||||||
max-height: 100%;
|
box-shadow: 4px 4px 16px 0px #0000000f;
|
||||||
|
border: 1px solid #ededed;
|
||||||
|
}
|
||||||
|
&.is-cover {
|
||||||
|
> .preview-image > img {
|
||||||
|
width: 29.7rem;
|
||||||
|
height: 37.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.is-apparel {
|
||||||
|
> .preview-image > img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,8 +9,11 @@
|
|||||||
:fixedNumber="ratio"
|
:fixedNumber="ratio"
|
||||||
:fixed="isProduct ? type !== 'apparel' : true"
|
:fixed="isProduct ? type !== 'apparel' : true"
|
||||||
movable
|
movable
|
||||||
|
:info="false"
|
||||||
@realTime="onChange"
|
@realTime="onChange"
|
||||||
outputType="png"
|
outputType="png"
|
||||||
|
:full="true"
|
||||||
|
centerBox
|
||||||
></VueCropper>
|
></VueCropper>
|
||||||
</div>
|
</div>
|
||||||
<div class="clip_opterate">
|
<div class="clip_opterate">
|
||||||
@@ -95,24 +98,26 @@
|
|||||||
return label
|
return label
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const centerLabelTop = "8px"
|
||||||
|
|
||||||
const cropLabelMap = {
|
const cropLabelMap = {
|
||||||
cover: [
|
cover: [
|
||||||
{ text: "crown", top: "2.67%", className: "label-h" },
|
{ text: "crown", top: "2.67%", className: "label-h" },
|
||||||
{ text: "hip line", top: "63.47%", className: "label-h" },
|
{ text: "hip line", top: "63.47%", className: "label-h" },
|
||||||
{ text: "mid-thigh", top: "92.8%", className: "label-h" },
|
{ text: "mid-thigh", top: "92.8%", className: "label-h" },
|
||||||
{ text: "center", top: "0", className: "label-v" }
|
{ text: "center", top: centerLabelTop, className: "label-v" }
|
||||||
],
|
],
|
||||||
mainProductImage: [
|
mainProductImage: [
|
||||||
{ text: "crown", top: "2.67%", className: "label-h" },
|
{ text: "crown", top: "2.67%", className: "label-h" },
|
||||||
{ text: "footbase", top: "97.6%", className: "label-h" },
|
{ text: "footbase", top: "97.6%", className: "label-h" },
|
||||||
{ text: "center", top: "0", className: "label-v" }
|
{ text: "center", top: centerLabelTop, className: "label-v" }
|
||||||
],
|
],
|
||||||
sketch: [
|
sketch: [
|
||||||
{ text: "crown", top: "2.67%", className: "label-h" },
|
{ text: "crown", top: "2.67%", className: "label-h" },
|
||||||
{ text: "footbase", top: "97.6%", className: "label-h" },
|
{ text: "footbase", top: "97.6%", className: "label-h" },
|
||||||
{ text: "center", top: "0", className: "label-v" }
|
{ text: "center", top: centerLabelTop, className: "label-v" }
|
||||||
],
|
],
|
||||||
apparel: [{ text: "center", top: "0", className: "label-v" }]
|
apparel: [{ text: "center", top: centerLabelTop, className: "label-v" }]
|
||||||
}
|
}
|
||||||
|
|
||||||
const injectCropLabel = () => {
|
const injectCropLabel = () => {
|
||||||
@@ -202,24 +207,12 @@
|
|||||||
:deep(.cropper-box) {
|
:deep(.cropper-box) {
|
||||||
.cropper-box-canvas {
|
.cropper-box-canvas {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
|
||||||
// img {
|
|
||||||
// height: 100%;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.is-cover {
|
&.is-cover {
|
||||||
:deep(.vue-cropper) {
|
:deep(.vue-cropper) {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
// :deep(.cropper-box-canvas) {
|
|
||||||
// width: 31.1rem !important;
|
|
||||||
// left: 50% !important;
|
|
||||||
// transform: translateX(-50%) !important;
|
|
||||||
// img {
|
|
||||||
// display: none;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,13 +264,15 @@
|
|||||||
.image-clip-body {
|
.image-clip-body {
|
||||||
width: 45.7rem;
|
width: 45.7rem;
|
||||||
height: 45.7rem;
|
height: 45.7rem;
|
||||||
:deep(.cropper-modal) {
|
// :deep(.cropper-modal) {
|
||||||
background: transparent;
|
// background: transparent;
|
||||||
}
|
// }
|
||||||
:deep(.vue-cropper .cropper-view-box) {
|
:deep(.vue-cropper .cropper-view-box) {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: visible !important;
|
// overflow: visible !important;
|
||||||
/* 原有的蓝色边框(outline)由组件控制,这里不干涉 */
|
img {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.vue-cropper .cropper-view-box::after) {
|
:deep(.vue-cropper .cropper-view-box::after) {
|
||||||
@@ -287,11 +282,16 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid rgba(75, 165, 255, 0.85);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 9; /* 位于图片之上,但在控制点之下 */
|
z-index: 9; /* 位于图片之上,但在控制点之下 */
|
||||||
background-image: none;
|
background-image: none;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
:deep(.vue-cropper .crop-point) {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-crop-type="cover"] {
|
&[data-crop-type="cover"] {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<img v-if="banner" :src="banner" />
|
<img v-if="banner" :src="banner" />
|
||||||
<div v-else class="null">
|
<div v-else class="null">
|
||||||
<span class="icon"><svg-icon name="seller-picture" size="60" /></span>
|
<span class="icon"><svg-icon name="seller-picture" size="60" /></span>
|
||||||
<span class="tip">Your brand banner has not been set up yet.</span>
|
<span class="tip">{{ $t("Seller.brandProfileBgNullTip") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="onChangeBanner">Change Brand Banner</button>
|
<button @click="onChangeBanner">{{ $t("Seller.changeBrandBanner") }}</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 头像 -->
|
<!-- 头像 -->
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
@@ -25,19 +25,19 @@
|
|||||||
<div class="and-profile-footer">
|
<div class="and-profile-footer">
|
||||||
<template v-if="isEdit">
|
<template v-if="isEdit">
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button class="cancel" @click="onCancel()">Cancel</button>
|
<button class="cancel" @click="onCancel()">{{ $t("Seller.cancel") }}</button>
|
||||||
<button class="submit" @click="onSubmit()">Save Change</button>
|
<button class="submit" @click="onSubmit()">{{ $t("Seller.saveChange") }}</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="tip">Changes will be reflected on your Stylish Parade brand page.</p>
|
<p class="tip">{{ $t("Seller.brandProfileEditTip") }}</p>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button class="edit" @click="onEdit">Edit</button>
|
<button class="edit" @click="onEdit">{{ $t("Seller.edit") }}</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="tip"> </p>
|
<p class="tip"> </p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<image-clip-dialog ref="imageClipDialogRef" />
|
<image-clip-dialog ref="imageClipDialogRef" centerBox />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -46,6 +46,8 @@
|
|||||||
import BrandInfo from "./brand-info.vue"
|
import BrandInfo from "./brand-info.vue"
|
||||||
import ImageClipDialog from "./image-clip-dialog.vue"
|
import ImageClipDialog from "./image-clip-dialog.vue"
|
||||||
import { useStore } from "vuex"
|
import { useStore } from "vuex"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
const { t } = useI18n()
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
store.dispatch("seller/get_designerInfo")
|
store.dispatch("seller/get_designerInfo")
|
||||||
const designerInfo = computed(() => store.state.seller.designerInfo)
|
const designerInfo = computed(() => store.state.seller.designerInfo)
|
||||||
@@ -90,7 +92,7 @@
|
|||||||
onSubmit({ brandBanner: res })
|
onSubmit({ brandBanner: res })
|
||||||
store.commit("set_loading", false)
|
store.commit("set_loading", false)
|
||||||
},
|
},
|
||||||
{ ratio: [40, 7], isPreview: false, title: "Crop Brand Banner" }
|
{ ratio: [40, 7], isPreview: false, title: t("Seller.cropBrandBanner") }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -104,7 +106,7 @@
|
|||||||
onSubmit({ avatar: res })
|
onSubmit({ avatar: res })
|
||||||
store.commit("set_loading", false)
|
store.commit("set_loading", false)
|
||||||
},
|
},
|
||||||
{ ratio: [1, 1], isPreview: true, title: "Crop Avatar" }
|
{ ratio: [1, 1], isPreview: true, title: t("Seller.cropAvatar") }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -121,7 +123,7 @@
|
|||||||
...res
|
...res
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
data.socialLinks = JSON.stringify(data.socialLinks)
|
data.socialLinks = JSON.stringify(data.socialLinks.filter((v) => v))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
data.socialLinks = JSON.stringify([])
|
data.socialLinks = JSON.stringify([])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,25 +30,67 @@ This directory owns the seller listing edit/create detail page.
|
|||||||
Detail API images are mapped by `category`:
|
Detail API images are mapped by `category`:
|
||||||
|
|
||||||
- `cover` -> `currentListing.cover`
|
- `cover` -> `currentListing.cover`
|
||||||
|
- `cover_from` -> `currentListing.coverFrom`; `imageUrl` stores the source image URL, not the literal source key. Resolve it against `sketch` and `mainProductImage` when hydrating. Keep backward compatibility for old rows whose `imageUrl` is `sketch` or `mainProductImage`.
|
||||||
- `sketch` -> `currentListing.sketch`
|
- `sketch` -> `currentListing.sketch`
|
||||||
- `mainProductImage` -> `currentListing.mainProductImage`
|
- `mainProductImage` or `main_product` -> `currentListing.mainProductImage`
|
||||||
- `main_product` or `product` -> `currentListing.prodImageList`
|
- `product` -> non-video entries in `currentListing.prodImageList`
|
||||||
|
- `firstFrame`, `gif`, and `video` -> one video entry in `currentListing.prodImageList` when the three rows share the same `sortOrder`
|
||||||
- `apparel` -> `currentListing.sketchList`
|
- `apparel` -> `currentListing.sketchList`
|
||||||
|
|
||||||
When saving, preserve the backend's expected image categories. Confirm backend naming before changing `main_product`, `product`, or `mainProductImage`.
|
When saving, preserve the backend's expected image categories. Confirm backend naming before changing `main_product`, `product`, `firstFrame`, `gif`, `video`, or `mainProductImage`.
|
||||||
|
|
||||||
|
中文补充:
|
||||||
|
|
||||||
|
- 详情接口返回的 `images` 要按 `category` 回填到页面状态,不要只按数组顺序猜类型。
|
||||||
|
- `cover_from` 不是封面图本身,而是记录封面裁剪来源。它的 `imageUrl` 传来源图片链接,回显时用这个 URL 和 `sketch`、`mainProductImage` 比较,恢复裁剪弹窗里的来源选择。
|
||||||
|
- `main_product` 表示页面右上方的主产品图 URL;普通 `product` 只表示产品图列表里的非视频图片。
|
||||||
|
- 视频不要保存成 `product`。视频必须拆成 `firstFrame`、`gif`、`video` 三类,并在回显时按相同 `sortOrder` 合并成一个视频项。
|
||||||
|
|
||||||
## Product Image Rules
|
## Product Image Rules
|
||||||
|
|
||||||
- The `main` badge represents the first selected product image, not the most recently selected one.
|
- The `main` badge represents the first selected product image, not the most recently selected one.
|
||||||
- `firstSelectedIndex` is stored on each `ListingItem` and passed to `ProductImageList.vue`.
|
- `firstSelectedIndex` is stored on each `ListingItem` and passed to `ProductImageList.vue`.
|
||||||
|
- Hydrating detail data must only set `mainProductImage` from explicit `main_product` or `mainProductImage` rows. Never infer it from selected `product` rows.
|
||||||
- Selecting a product image should only set `mainProductImage` when no main image is currently tracked by that listing's `firstSelectedIndex`.
|
- Selecting a product image should only set `mainProductImage` when no main image is currently tracked by that listing's `firstSelectedIndex`.
|
||||||
- Unselecting the current main product image clears `mainProductImage` and resets `firstSelectedIndex`.
|
- Unselecting the current main product image clears `mainProductImage` and resets `firstSelectedIndex`.
|
||||||
|
- Videos can be selected and saved, but they cannot become `mainProductImage`, must not set `firstSelectedIndex`, and must not display the `main` badge.
|
||||||
|
|
||||||
|
中文补充:
|
||||||
|
|
||||||
|
- `main` 标识只给图片,不给视频。
|
||||||
|
- 数据回显时,只有接口明确返回 `main_product` / `mainProductImage` 才能设置主图。不要因为某个 `product` 是已选中状态,就自动把它当成 `mainProductImage` 或显示 `main`。
|
||||||
|
- 第一次选择视频时可以弹 warning,但视频本身仍然要保持选中;只是不要把它写入 `mainProductImage`,也不要更新 `firstSelectedIndex`。
|
||||||
|
- 如果先选中视频,再选中图片,图片仍然可以成为第一个主图。
|
||||||
|
- 如果取消的是当前主图图片,需要清空 `mainProductImage` 和 `firstSelectedIndex`;取消普通图片或视频不应影响主图。
|
||||||
|
|
||||||
|
## Save Image Ordering
|
||||||
|
|
||||||
|
- Every saved image row must include `sortOrder`.
|
||||||
|
- `sortOrder` is scoped per category; each category starts its own sequence.
|
||||||
|
- For `product` rows, save the image currently used as `mainProductImage` first, selected non-main images next, and unselected images last.
|
||||||
|
- Save video media as three rows with categories `firstFrame`, `gif`, and `video`. The three rows from the same video item must share the same `sortOrder`.
|
||||||
|
- When hydrating detail data, group `firstFrame`, `gif`, and `video` rows by matching `sortOrder` and restore the combined video item, including its selected state. Accept `isSelected`, old typo `isSeleted`, and `selected` from the API.
|
||||||
|
|
||||||
|
中文补充:
|
||||||
|
|
||||||
|
- `sortOrder` 是按 category 分开排的,不同 category 之间不要共用一个全局序号。
|
||||||
|
- `product` 的排序规则是:当前作为 `mainProductImage` 的图片第一,其他已选图片其次,未选图片最后。
|
||||||
|
- 同一个视频拆出的 `firstFrame`、`gif`、`video` 三条数据必须使用同一个 `sortOrder`。例如第一个视频三条都是 `sortOrder: 1`,第二个视频三条都是 `sortOrder: 2`。
|
||||||
|
- 回显视频时,用相同 `sortOrder` 找回一组 `firstFrame/gif/video`。三条里任意一条带选中标记,都应恢复为这个视频已选中。
|
||||||
|
- 选中字段要兼容 `isSelected`、历史拼写 `isSeleted`、以及 `selected`。
|
||||||
|
|
||||||
## Crop Flow
|
## Crop Flow
|
||||||
|
|
||||||
- `TopImageSection.vue` and `ApparelSketchList.vue` emit `crop`.
|
- `TopImageSection.vue` and `ApparelSketchList.vue` emit `crop`.
|
||||||
- `index.vue` handles `handleClickCrop`, opens `ImageClipDialog`, uploads with `uploadFile`, then writes the returned URL into the correct field/list item.
|
- `index.vue` handles `handleClickCrop`, opens `ImageClipDialog`, uploads with `uploadFile`, then writes the returned URL into the correct field/list item.
|
||||||
- Keep cover crop ratio at `[4, 5]`; other crop types use `[9, 16]`.
|
- Keep cover crop ratio at `[4, 5]`; other crop types use `[9, 16]`.
|
||||||
|
- Cover crop can be based on `sketch` or `mainProductImage`. Store the chosen source in `coverFrom`, save it via `cover_from.imageUrl` as the source image URL, and pass it back into `ImageClipDialog` so reopening cover crop restores the selected source.
|
||||||
|
|
||||||
|
中文补充:
|
||||||
|
|
||||||
|
- cover 裁剪弹窗会在 `sketch` 和 `mainProductImage` 之间切来源。用户保存 cover 时,父组件需要同时保存裁剪后的 cover URL 和本次使用的来源。
|
||||||
|
- 下次重新打开 cover 裁剪时,应该按 `coverFrom` 恢复来源选择,并用对应的原图作为裁剪图,不要直接拿已裁好的 cover 图再次裁。
|
||||||
|
- 如果只有一个来源图可用,就按可用来源打开;如果两个来源都可用,要显示来源切换。
|
||||||
|
|
||||||
## Form Flow
|
## Form Flow
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import { Https } from "@/tool/https"
|
import { Https } from "@/tool/https"
|
||||||
|
import type { ListingImageCategory, SketchDetailResponse } from "./types"
|
||||||
|
|
||||||
// 编辑时根据ID获取信息
|
// 编辑时根据ID获取信息
|
||||||
export const fetchListingDetailById = (id) => {
|
export const fetchListingDetailById = (id) => {
|
||||||
return Https.axiosGet("/seller/listing/detail", { params: { id } })
|
return Https.axiosGet("/seller/listing/detail", { params: { id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SketchIDs {
|
type SketchIDs = Array<number | string>
|
||||||
designItemIds: Array
|
|
||||||
}
|
|
||||||
interface DetailReturns {
|
|
||||||
clothes: string[]
|
|
||||||
designItemId: number
|
|
||||||
toProductImageUrls: string[]
|
|
||||||
}
|
|
||||||
// 获取designItemId对应的产品图
|
// 获取designItemId对应的产品图
|
||||||
export const fetchSketchDetail = (data: SketchIDs): Array<DetailReturns> => {
|
export const fetchSketchDetail = (data: SketchIDs): Promise<SketchDetailResponse[]> => {
|
||||||
let params = "?"
|
let params = "?"
|
||||||
data.forEach((id, index) => {
|
data.forEach((id, index) => {
|
||||||
if (index === data.length - 1) {
|
if (index === data.length - 1) {
|
||||||
@@ -27,23 +21,26 @@ export const fetchSketchDetail = (data: SketchIDs): Array<DetailReturns> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ImageObj {
|
interface ImageObj {
|
||||||
id: number // 图片id,有值会更新,没有会自动新增
|
id?: number // 图片id,有值会更新,没有会自动新增
|
||||||
category: "cover" | "main_product" | "product" | "sketch" | "apparel" // 图片类型
|
category: ListingImageCategory // 图片类型
|
||||||
|
imageUrl?: string | null
|
||||||
|
isSelected?: number
|
||||||
|
sortOrder?: number
|
||||||
}
|
}
|
||||||
interface DetailData {
|
interface DetailData {
|
||||||
id: number | string // 商品Id
|
id: number | string // 商品Id
|
||||||
title: string // 商品名
|
title: string // 商品名
|
||||||
description: string // 商品描述
|
description: string // 商品描述
|
||||||
price: number // 价格
|
price: number | string // 价格
|
||||||
stock?: number // 库存
|
stock?: number // 库存
|
||||||
viewCount?: number // 浏览量
|
viewCount?: number // 浏览量
|
||||||
status: 0 | 1 | 2 // 0草稿 1发布 2删除
|
status: 0 | 1 | 2 // 0草稿 1发布 2删除
|
||||||
images: ImageObj[]
|
images: ImageObj[]
|
||||||
designFor: "male" | "female"
|
designFor: "male" | "female"
|
||||||
productCategory: "outwear" | "trousers" | "blouse" | "dress" | "skirt" | "accessories"
|
productCategory: string[] | null
|
||||||
}
|
}
|
||||||
// 保存/更新表单
|
// 保存/更新表单
|
||||||
export const fetchUpdateListing = (data: DetailData) => {
|
export const fetchUpdateListing = (data: DetailData[]) => {
|
||||||
return Https.axiosPost("/seller/listing/batch", data)
|
return Https.axiosPost("/seller/listing/batch", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,8 @@
|
|||||||
|
|
||||||
.img-src {
|
.img-src {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crop-tool {
|
.crop-tool {
|
||||||
|
|||||||
@@ -12,8 +12,10 @@
|
|||||||
v-for="(item, index) in imageList"
|
v-for="(item, index) in imageList"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="product-image-item flex flex-center"
|
class="product-image-item flex flex-center"
|
||||||
:class="{ selected: item.selected }"
|
:class="{ selected: item.selected, video: item.isVideo }"
|
||||||
@click="emit('select', index)"
|
@click="emit('select', index)"
|
||||||
|
@mouseenter="handleMouseEnter(index, item)"
|
||||||
|
@mouseleave="handleMouseLeave(index, item)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
v-if="item.selected"
|
v-if="item.selected"
|
||||||
@@ -21,8 +23,11 @@
|
|||||||
class="checked"
|
class="checked"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<img class="img-src" :src="item.url" alt="" />
|
<img class="img-src" :src="getDisplayUrl(item, index)" alt="" />
|
||||||
<div v-if="item.selected && index === firstSelectedIndex" class="main-pic">
|
<div v-if="item.isVideo && durationMap[index]" class="video-duration">
|
||||||
|
{{ durationMap[index] }}
|
||||||
|
</div>
|
||||||
|
<div v-if="item.selected && index === firstSelectedIndex && !item.isVideo" class="main-pic">
|
||||||
main
|
main
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,8 +35,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import type { ListingItem } from "../types"
|
import type { ListingItem, ProductMediaItem } from "../types"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
imageList: ListingItem["prodImageList"]
|
imageList: ListingItem["prodImageList"]
|
||||||
@@ -43,6 +48,70 @@
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const selectedCount = computed(() => props.imageList.filter((item) => item.selected).length)
|
const selectedCount = computed(() => props.imageList.filter((item) => item.selected).length)
|
||||||
|
const hoveredVideoIndex = ref<number | null>(null)
|
||||||
|
const durationMap = ref<Record<number, string>>({})
|
||||||
|
const videoSourceKey = computed(() =>
|
||||||
|
props.imageList
|
||||||
|
.map((item) => `${item.videoUrl || ""}::${item.url || ""}`)
|
||||||
|
.join("|")
|
||||||
|
)
|
||||||
|
|
||||||
|
const getDisplayUrl = (item: ProductMediaItem, index: number) => {
|
||||||
|
if (item.isVideo && hoveredVideoIndex.value === index && item.gifUrl) {
|
||||||
|
return item.gifUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.url
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseEnter = (index: number, item: ProductMediaItem) => {
|
||||||
|
if (!item.isVideo || !item.gifUrl) return
|
||||||
|
hoveredVideoIndex.value = index
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = (index: number, item: ProductMediaItem) => {
|
||||||
|
if (!item.isVideo) return
|
||||||
|
if (hoveredVideoIndex.value === index) hoveredVideoIndex.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatVideoDuration = (duration: number) => {
|
||||||
|
if (!Number.isFinite(duration) || duration <= 0) return ""
|
||||||
|
|
||||||
|
const totalSeconds = Math.round(duration)
|
||||||
|
const minutes = Math.floor(totalSeconds / 60)
|
||||||
|
const seconds = totalSeconds % 60
|
||||||
|
|
||||||
|
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadVideoDurations = () => {
|
||||||
|
durationMap.value = {}
|
||||||
|
props.imageList.forEach((item, index) => {
|
||||||
|
if (!item.isVideo || !item.videoUrl) return
|
||||||
|
|
||||||
|
const video = document.createElement("video")
|
||||||
|
video.preload = "metadata"
|
||||||
|
video.src = item.videoUrl
|
||||||
|
video.onloadedmetadata = () => {
|
||||||
|
durationMap.value = {
|
||||||
|
...durationMap.value,
|
||||||
|
[index]: formatVideoDuration(video.duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
video.onerror = () => {
|
||||||
|
video.src = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
videoSourceKey,
|
||||||
|
() => {
|
||||||
|
hoveredVideoIndex.value = null
|
||||||
|
loadVideoDurations()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@@ -130,7 +199,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.img-src {
|
.img-src {
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-duration {
|
||||||
|
position: absolute;
|
||||||
|
right: 0.8rem;
|
||||||
|
bottom: 0.8rem;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0 0.8rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
border-radius: 1.2rem;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-pic {
|
.main-pic {
|
||||||
|
|||||||
@@ -16,6 +16,13 @@
|
|||||||
>
|
>
|
||||||
<SvgIcon name="CCrop" color="#fff" size="12" />
|
<SvgIcon name="CCrop" color="#fff" size="12" />
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="images[type] && type !== 'sketch'"
|
||||||
|
class="delete-tool flex flex-center"
|
||||||
|
@click="emit('remove-image', type)"
|
||||||
|
>
|
||||||
|
<SvgIcon name="CTrash" color="#ffffff" size="14" />
|
||||||
|
</div>
|
||||||
<img
|
<img
|
||||||
v-if="images[type]"
|
v-if="images[type]"
|
||||||
:src="images[type] || ''"
|
:src="images[type] || ''"
|
||||||
@@ -35,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="trigger-img placeholder"></div>
|
<SvgIcon name="picture" size="24" />
|
||||||
<div class="trigger-tips">
|
<div class="trigger-tips">
|
||||||
{{ $t("SellerListEdit.productImageDesc") }}
|
{{ $t("SellerListEdit.productImageDesc") }}
|
||||||
</div>
|
</div>
|
||||||
@@ -54,6 +61,7 @@
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "crop", data: string | null, type: TopImageType): void
|
(e: "crop", data: string | null, type: TopImageType): void
|
||||||
|
(e: "remove-image", type: TopImageType): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const topImageList: TopImageType[] = ["sketch", "mainProductImage", "cover"]
|
const topImageList: TopImageType[] = ["sketch", "mainProductImage", "cover"]
|
||||||
@@ -117,11 +125,23 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete-tool {
|
||||||
|
position: absolute;
|
||||||
|
top: 3.4rem;
|
||||||
|
right: 0.8rem;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #9a9a9a;
|
||||||
|
// z-index: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.sketch-img {
|
.sketch-img {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
height: 100%;
|
||||||
&.sketch {
|
&.sketch {
|
||||||
height: initial;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,13 +156,6 @@
|
|||||||
row-gap: 1.2rem;
|
row-gap: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
width: 2.4rem;
|
|
||||||
height: 2.4rem;
|
|
||||||
border-radius: 0.6rem;
|
|
||||||
background: linear-gradient(135deg, #efefef 0%, #cdcdcd 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-tips {
|
.trigger-tips {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -150,6 +163,11 @@
|
|||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.mainProductImage {
|
||||||
|
.trigger-tips {
|
||||||
|
margin: 0 -2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</seller-header>
|
</seller-header>
|
||||||
<div class="edit-detail-content flex">
|
<div class="edit-detail-content flex" ref="editDetailContentRef">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<TopImageSection :images="previewImageMap" @crop="handleClickCrop" />
|
<TopImageSection :images="previewImageMap" @crop="handleClickCrop" @remove-image="handleRemoveImage" />
|
||||||
<ProductImageList
|
<ProductImageList
|
||||||
:image-list="prodImgList"
|
:image-list="prodImgList"
|
||||||
:first-selected-index="currentListing.firstSelectedIndex"
|
:first-selected-index="currentListing.firstSelectedIndex"
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
|
<div class="right-content" ref="rightContentRef">
|
||||||
<ListingForm
|
<ListingForm
|
||||||
:product-name="currentListing.productName"
|
:product-name="currentListing.productName"
|
||||||
:price="currentListing.price"
|
:price="currentListing.price"
|
||||||
@@ -40,11 +41,12 @@
|
|||||||
:gender-options="genderOptions"
|
:gender-options="genderOptions"
|
||||||
:category-options="categoryOptions"
|
:category-options="categoryOptions"
|
||||||
@update:product-name="currentListing.productName = $event"
|
@update:product-name="currentListing.productName = $event"
|
||||||
@update:price="currentListing.price = $event"
|
@update:price="handleUpdatePrice"
|
||||||
@update:desc="currentListing.desc = $event"
|
@update:desc="currentListing.desc = $event"
|
||||||
@update:gender="handleUpdateGender"
|
@update:gender="handleUpdateGender"
|
||||||
@update:category="currentListing.category = $event"
|
@update:category="currentListing.category = $event"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div class="page-control flex align-center" v-if="selectList.length > 1">
|
<div class="page-control flex align-center" v-if="selectList.length > 1">
|
||||||
<a-pagination
|
<a-pagination
|
||||||
v-model:current="currentPage"
|
v-model:current="currentPage"
|
||||||
@@ -81,6 +83,8 @@
|
|||||||
import ProductImageList from "./components/ProductImageList.vue"
|
import ProductImageList from "./components/ProductImageList.vue"
|
||||||
import TopImageSection from "./components/TopImageSection.vue"
|
import TopImageSection from "./components/TopImageSection.vue"
|
||||||
import { useStore } from "vuex"
|
import { useStore } from "vuex"
|
||||||
|
import { debounce } from "lodash-es"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchSketchDetail,
|
fetchSketchDetail,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
@@ -88,10 +92,17 @@
|
|||||||
fetchUpdateListing
|
fetchUpdateListing
|
||||||
} from "./api"
|
} from "./api"
|
||||||
import type {
|
import type {
|
||||||
|
CoverSourceType,
|
||||||
|
CropType,
|
||||||
|
TopImageType,
|
||||||
ListingDetailImage,
|
ListingDetailImage,
|
||||||
ListingDetailResponse,
|
ListingDetailResponse,
|
||||||
|
ListingImageCategory,
|
||||||
ListingItem,
|
ListingItem,
|
||||||
|
ProductMediaItem,
|
||||||
RadioOption,
|
RadioOption,
|
||||||
|
SketchDetailResponse,
|
||||||
|
SketchDetailVideo,
|
||||||
StatusType
|
StatusType
|
||||||
} from "./types"
|
} from "./types"
|
||||||
|
|
||||||
@@ -99,6 +110,8 @@
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const imageClipDialogRef = ref<InstanceType<typeof ImageClipDialog> | null>(null)
|
const imageClipDialogRef = ref<InstanceType<typeof ImageClipDialog> | null>(null)
|
||||||
|
const editDetailContentRef = ref<HTMLElement | null>(null)
|
||||||
|
const rightContentRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "EditDetail"
|
name: "EditDetail"
|
||||||
@@ -121,18 +134,14 @@
|
|||||||
desc: "",
|
desc: "",
|
||||||
gender: "FEMALE",
|
gender: "FEMALE",
|
||||||
category: null,
|
category: null,
|
||||||
|
coverFrom: "sketch",
|
||||||
firstSelectedIndex: null,
|
firstSelectedIndex: null,
|
||||||
prodImageList: [],
|
prodImageList: [],
|
||||||
sketchList: []
|
sketchList: []
|
||||||
})
|
})
|
||||||
|
|
||||||
const genderOptions = computed(() => {
|
const genderOptions = computed(() => {
|
||||||
return (
|
return STORE.state.UserHabit?.sex.value
|
||||||
STORE.state.UserHabit?.sex.value.map((el) => ({
|
|
||||||
...el,
|
|
||||||
name: el.key.toLowerCase()
|
|
||||||
})) || []
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const fallbackCategoryOptions: Record<string, RadioOption[]> = {
|
const fallbackCategoryOptions: Record<string, RadioOption[]> = {
|
||||||
@@ -162,6 +171,16 @@
|
|||||||
currentListing.value.category = null
|
currentListing.value.category = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleUpdatePrice = (value: string) => {
|
||||||
|
const num = Number(value)
|
||||||
|
if (value && !Number.isNaN(num) && num < 4) {
|
||||||
|
currentListing.value.price = "4"
|
||||||
|
message.warning(t("SellerListEdit.priceMinMessage"))
|
||||||
|
} else {
|
||||||
|
currentListing.value.price = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const previewImageMap = computed(() => ({
|
const previewImageMap = computed(() => ({
|
||||||
sketch: currentListing.value.sketch,
|
sketch: currentListing.value.sketch,
|
||||||
mainProductImage: currentListing.value.mainProductImage,
|
mainProductImage: currentListing.value.mainProductImage,
|
||||||
@@ -172,14 +191,50 @@
|
|||||||
return [...images].sort((prev, next) => (prev.sortOrder ?? 0) - (next.sortOrder ?? 0))
|
return [...images].sort((prev, next) => (prev.sortOrder ?? 0) - (next.sortOrder ?? 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getImageSelected = (value: ListingDetailImage["isSelected"]) =>
|
const getImageSelected = (value: ListingDetailImage["isSelected"]) => {
|
||||||
value === true || value === 1 || value === "1"
|
if (value === true || value === 1 || value === "1") return true
|
||||||
|
if (typeof value === "string") return value.toLowerCase() === "true"
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDetailImageSelected = (image: ListingDetailImage) =>
|
||||||
|
getImageSelected(image.isSelected) ||
|
||||||
|
getImageSelected(image.isSeleted) ||
|
||||||
|
getImageSelected(image.selected)
|
||||||
|
|
||||||
|
const normalizeCoverSource = (value: unknown): CoverSourceType =>
|
||||||
|
value === "mainProductImage" ? "mainProductImage" : "sketch"
|
||||||
|
|
||||||
|
const isCoverSource = (value: unknown): value is CoverSourceType =>
|
||||||
|
value === "sketch" || value === "mainProductImage"
|
||||||
|
|
||||||
|
const resolveCoverSourceFromImageUrl = (
|
||||||
|
imageUrl: string,
|
||||||
|
listing: ListingItem
|
||||||
|
): CoverSourceType => {
|
||||||
|
if (imageUrl === listing.mainProductImage) return "mainProductImage"
|
||||||
|
if (imageUrl === listing.sketch) return "sketch"
|
||||||
|
|
||||||
|
return normalizeCoverSource(imageUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoImageCategories = ["firstFrame", "gif", "video"] as const
|
||||||
|
type VideoImageCategory = (typeof videoImageCategories)[number]
|
||||||
|
|
||||||
|
const isVideoImageCategory = (
|
||||||
|
category: ListingDetailImage["category"]
|
||||||
|
): category is VideoImageCategory =>
|
||||||
|
videoImageCategories.includes(category as VideoImageCategory)
|
||||||
|
|
||||||
const normalizeDetailGender = (value: ListingDetailResponse["designFor"]) => {
|
const normalizeDetailGender = (value: ListingDetailResponse["designFor"]) => {
|
||||||
const gender = String(value || "").toUpperCase()
|
const gender = String(value || "").toUpperCase()
|
||||||
return gender === "MALE" || gender === "FEMALE" ? gender : "FEMALE"
|
return gender === "MALE" || gender === "FEMALE" ? gender : "FEMALE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getListingDesignFor = (gender: ListingItem["gender"]): "male" | "female" =>
|
||||||
|
gender === "MALE" ? "male" : "female"
|
||||||
|
|
||||||
const normalizeDetailCategory = (
|
const normalizeDetailCategory = (
|
||||||
value: ListingDetailResponse["productCategory"]
|
value: ListingDetailResponse["productCategory"]
|
||||||
): ListingItem["category"] => {
|
): ListingItem["category"] => {
|
||||||
@@ -193,6 +248,17 @@
|
|||||||
|
|
||||||
const createListingItemFromDetail = (detail: ListingDetailResponse): ListingItem => {
|
const createListingItemFromDetail = (detail: ListingDetailResponse): ListingItem => {
|
||||||
const listing = createListingItem()
|
const listing = createListingItem()
|
||||||
|
let coverFromImageUrl = ""
|
||||||
|
const videoGroupMap = new Map<
|
||||||
|
number,
|
||||||
|
{
|
||||||
|
sortOrder: number
|
||||||
|
firstFrameUrl?: string
|
||||||
|
gifUrl?: string
|
||||||
|
videoUrl?: string
|
||||||
|
selected: boolean
|
||||||
|
}
|
||||||
|
>()
|
||||||
|
|
||||||
listing.productName = detail.title || ""
|
listing.productName = detail.title || ""
|
||||||
listing.price =
|
listing.price =
|
||||||
@@ -202,6 +268,11 @@
|
|||||||
listing.category = normalizeDetailCategory(detail.productCategory)
|
listing.category = normalizeDetailCategory(detail.productCategory)
|
||||||
|
|
||||||
getSortedDetailImages(detail.images || []).forEach((image) => {
|
getSortedDetailImages(detail.images || []).forEach((image) => {
|
||||||
|
if (image.category === "cover_from") {
|
||||||
|
coverFromImageUrl = image.imageUrl || ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const imageUrl = image.imageUrl || ""
|
const imageUrl = image.imageUrl || ""
|
||||||
if (!imageUrl) return
|
if (!imageUrl) return
|
||||||
|
|
||||||
@@ -215,7 +286,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image.category === "mainProductImage") {
|
if (image.category === "mainProductImage" || image.category === "main_product") {
|
||||||
listing.mainProductImage = imageUrl
|
listing.mainProductImage = imageUrl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -223,23 +294,50 @@
|
|||||||
if (image.category === "product") {
|
if (image.category === "product") {
|
||||||
listing.prodImageList.push({
|
listing.prodImageList.push({
|
||||||
url: imageUrl,
|
url: imageUrl,
|
||||||
selected: getImageSelected(image.isSelected)
|
selected: getDetailImageSelected(image)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isVideoImageCategory(image.category)) {
|
||||||
|
if (image.sortOrder === null || typeof image.sortOrder === "undefined") return
|
||||||
|
|
||||||
|
const sortOrder = Number(image.sortOrder)
|
||||||
|
if (!Number.isFinite(sortOrder)) return
|
||||||
|
|
||||||
|
const group = videoGroupMap.get(sortOrder) || {
|
||||||
|
sortOrder,
|
||||||
|
selected: false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.category === "firstFrame") group.firstFrameUrl = imageUrl
|
||||||
|
if (image.category === "gif") group.gifUrl = imageUrl
|
||||||
|
if (image.category === "video") group.videoUrl = imageUrl
|
||||||
|
group.selected = group.selected || getDetailImageSelected(image)
|
||||||
|
videoGroupMap.set(sortOrder, group)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (image.category === "apparel") {
|
if (image.category === "apparel") {
|
||||||
listing.sketchList.push({ url: imageUrl })
|
listing.sketchList.push({ url: imageUrl })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!listing.mainProductImage) {
|
Array.from(videoGroupMap.values())
|
||||||
listing.mainProductImage =
|
.sort((prev, next) => prev.sortOrder - next.sortOrder)
|
||||||
listing.prodImageList.find((item) => item.selected)?.url || ""
|
.forEach((video) => {
|
||||||
|
const videoItem = createProductVideoItem(video, video.selected)
|
||||||
|
if (videoItem) listing.prodImageList.push(videoItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (coverFromImageUrl) {
|
||||||
|
listing.coverFrom = resolveCoverSourceFromImageUrl(coverFromImageUrl, listing)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedIndex = listing.prodImageList.findIndex((item) => item.selected)
|
const mainProductIndex = listing.prodImageList.findIndex(
|
||||||
listing.firstSelectedIndex = selectedIndex === -1 ? null : selectedIndex
|
(item) => !item.isVideo && item.url === listing.mainProductImage
|
||||||
|
)
|
||||||
|
listing.firstSelectedIndex = mainProductIndex === -1 ? null : mainProductIndex
|
||||||
|
|
||||||
listing.productImage = listing.prodImageList.map((item) => item.url)
|
listing.productImage = listing.prodImageList.map((item) => item.url)
|
||||||
listing.apparelSketch = listing.sketchList
|
listing.apparelSketch = listing.sketchList
|
||||||
@@ -249,6 +347,26 @@
|
|||||||
return listing
|
return listing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createProductVideoItem = (
|
||||||
|
video: SketchDetailVideo,
|
||||||
|
selected = false
|
||||||
|
): ProductMediaItem | null => {
|
||||||
|
const firstFrameUrl = video?.firstFrameUrl || ""
|
||||||
|
const gifUrl = video?.gifUrl || ""
|
||||||
|
const videoUrl = video?.videoUrl || ""
|
||||||
|
|
||||||
|
if (!firstFrameUrl || !videoUrl) return null
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: firstFrameUrl,
|
||||||
|
firstFrameUrl,
|
||||||
|
gifUrl,
|
||||||
|
videoUrl,
|
||||||
|
isVideo: true,
|
||||||
|
selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleSelectProdImg = (index: number) => {
|
const handleSelectProdImg = (index: number) => {
|
||||||
const listing = currentListing.value
|
const listing = currentListing.value
|
||||||
const target = prodImgList.value[index]
|
const target = prodImgList.value[index]
|
||||||
@@ -257,6 +375,10 @@
|
|||||||
target.selected = willSelect
|
target.selected = willSelect
|
||||||
|
|
||||||
if (willSelect && listing.firstSelectedIndex === null) {
|
if (willSelect && listing.firstSelectedIndex === null) {
|
||||||
|
if (target.isVideo) {
|
||||||
|
message.warning(t("Seller.VideoWarning"))
|
||||||
|
return
|
||||||
|
}
|
||||||
listing.mainProductImage = target.url
|
listing.mainProductImage = target.url
|
||||||
listing.firstSelectedIndex = index
|
listing.firstSelectedIndex = index
|
||||||
return
|
return
|
||||||
@@ -268,25 +390,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cropType = ref("")
|
const getCoverOriginList = (item: ListingItem) => {
|
||||||
const handleClickCrop = (data: any, type: string, paramThree: any = []) => {
|
const origin: Array<{ type: CoverSourceType; url: string }> = []
|
||||||
|
if (item.sketch) {
|
||||||
|
origin.push({ type: "sketch", url: item.sketch })
|
||||||
|
}
|
||||||
|
if (item.mainProductImage) {
|
||||||
|
origin.push({ type: "mainProductImage", url: item.mainProductImage })
|
||||||
|
}
|
||||||
|
|
||||||
|
return origin
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemoveImage = (type: TopImageType) => {
|
||||||
|
const listing = currentListing.value
|
||||||
|
if (type === "mainProductImage") {
|
||||||
|
listing.prodImageList.forEach((item) => {
|
||||||
|
if (item.url === listing.mainProductImage && !item.isVideo) {
|
||||||
|
item.selected = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
listing.mainProductImage = ""
|
||||||
|
listing.firstSelectedIndex = null
|
||||||
|
} else if (type === "cover") {
|
||||||
|
listing.cover = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cropType = ref<CropType | "">("")
|
||||||
|
const handleClickCrop = (
|
||||||
|
data: string | null,
|
||||||
|
type: CropType,
|
||||||
|
paramThree: number | unknown[] = []
|
||||||
|
) => {
|
||||||
// 处理来自TopImageSection的调用: (data, type, list)
|
// 处理来自TopImageSection的调用: (data, type, list)
|
||||||
// 处理来自ApparelSketchList的调用: (data, type, index)
|
// 处理来自ApparelSketchList的调用: (data, type, index)
|
||||||
const index = typeof paramThree === "number" ? paramThree : undefined
|
const index = typeof paramThree === "number" ? paramThree : undefined
|
||||||
const list = Array.isArray(paramThree) ? paramThree : []
|
|
||||||
|
|
||||||
// console.log(data, type)
|
// console.log(data, type)
|
||||||
// console.log(selectList.value[currentIndex.value])
|
// console.log(selectList.value[currentIndex.value])
|
||||||
let origin = []
|
|
||||||
const currentItem = selectList.value[currentIndex.value]
|
const currentItem = selectList.value[currentIndex.value]
|
||||||
if (currentItem.sketch) {
|
const origin = type === "cover" ? getCoverOriginList(currentItem) : []
|
||||||
origin.push({ type: "sketch", url: currentItem.sketch })
|
const titleList: Record<CropType, string> = {
|
||||||
}
|
|
||||||
if (currentItem.mainProductImage) {
|
|
||||||
origin.push({ type: "mainProductImage", url: currentItem.mainProductImage })
|
|
||||||
}
|
|
||||||
if (type !== "cover") origin = []
|
|
||||||
const titleList = {
|
|
||||||
sketch: "Crop Sketch",
|
sketch: "Crop Sketch",
|
||||||
mainProductImage: "Crop Main Product Image",
|
mainProductImage: "Crop Main Product Image",
|
||||||
cover: "Crop Cover",
|
cover: "Crop Cover",
|
||||||
@@ -296,17 +441,28 @@
|
|||||||
cropType.value = type
|
cropType.value = type
|
||||||
imageClipDialogRef.value.open(
|
imageClipDialogRef.value.open(
|
||||||
data,
|
data,
|
||||||
(file) => {
|
(file: File, coverFrom?: CoverSourceType) => {
|
||||||
// console.log(file)
|
// console.log(file)
|
||||||
uploadFile(file).then((res) => {
|
uploadFile(file).then((res) => {
|
||||||
if (type === "apparel" && typeof index !== "undefined") {
|
if (type === "apparel" && typeof index !== "undefined") {
|
||||||
selectList.value[currentIndex.value].sketchList[index].url = res
|
selectList.value[currentIndex.value].sketchList[index].url = res
|
||||||
|
} else if (type === "cover") {
|
||||||
|
selectList.value[currentIndex.value].cover = res
|
||||||
|
if (isCoverSource(coverFrom)) {
|
||||||
|
selectList.value[currentIndex.value].coverFrom = coverFrom
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectList.value[currentIndex.value][type] = res
|
selectList.value[currentIndex.value][type] = res
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ ratio, isPreview: true, title: titleList[type], isProduct: true },
|
{
|
||||||
|
ratio,
|
||||||
|
isPreview: true,
|
||||||
|
title: titleList[type],
|
||||||
|
isProduct: true,
|
||||||
|
coverFrom: currentItem.coverFrom
|
||||||
|
},
|
||||||
origin
|
origin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -315,10 +471,9 @@
|
|||||||
value !== null && value !== undefined && String(value).trim() !== ""
|
value !== null && value !== undefined && String(value).trim() !== ""
|
||||||
|
|
||||||
const getMissingRequiredField = (item: ListingItem) => {
|
const getMissingRequiredField = (item: ListingItem) => {
|
||||||
const cover = item.cover || item.mainProductImage || item.sketch
|
|
||||||
const requiredFields = [
|
const requiredFields = [
|
||||||
{ value: item.sketch, label: t("SellerListEdit.sketch") },
|
{ value: item.sketch, label: t("SellerListEdit.sketch") },
|
||||||
{ value: cover, label: t("SellerListEdit.cover") },
|
{ value: item.cover, label: t("SellerListEdit.cover") },
|
||||||
{ value: item.productName, label: t("SellerListEdit.productName") },
|
{ value: item.productName, label: t("SellerListEdit.productName") },
|
||||||
{ value: item.price, label: t("SellerListEdit.price") },
|
{ value: item.price, label: t("SellerListEdit.price") },
|
||||||
{ value: item.desc, label: t("SellerListEdit.productDescription") },
|
{ value: item.desc, label: t("SellerListEdit.productDescription") },
|
||||||
@@ -358,93 +513,160 @@
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SaveListingImage = {
|
||||||
|
category: ListingImageCategory
|
||||||
|
imageUrl: string | null
|
||||||
|
isSelected: number
|
||||||
|
sortOrder: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMainProductMedia = (item: ListingItem, media: ProductMediaItem, index: number) =>
|
||||||
|
!media.isVideo &&
|
||||||
|
(item.firstSelectedIndex === index ||
|
||||||
|
(Boolean(item.mainProductImage) && item.mainProductImage === media.url))
|
||||||
|
|
||||||
|
const getSortedProductMedia = (item: ListingItem) => {
|
||||||
|
return item.prodImageList
|
||||||
|
.map((media, index) => ({ media, index }))
|
||||||
|
.filter(({ media }) => !media.isVideo)
|
||||||
|
.sort((prev, next) => {
|
||||||
|
const prevRank = isMainProductMedia(item, prev.media, prev.index)
|
||||||
|
? 0
|
||||||
|
: prev.media.selected
|
||||||
|
? 1
|
||||||
|
: 2
|
||||||
|
const nextRank = isMainProductMedia(item, next.media, next.index)
|
||||||
|
? 0
|
||||||
|
: next.media.selected
|
||||||
|
? 1
|
||||||
|
: 2
|
||||||
|
|
||||||
|
return prevRank - nextRank || prev.index - next.index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildListingImages = (item: ListingItem) => {
|
||||||
|
const images: SaveListingImage[] = []
|
||||||
|
const sortOrderMap: Partial<Record<ListingImageCategory, number>> = {}
|
||||||
|
const getNextSortOrder = (category: ListingImageCategory) => {
|
||||||
|
const sortOrder = (sortOrderMap[category] || 0) + 1
|
||||||
|
sortOrderMap[category] = sortOrder
|
||||||
|
return sortOrder
|
||||||
|
}
|
||||||
|
const pushImage = (
|
||||||
|
category: ListingImageCategory,
|
||||||
|
imageUrl: string | null,
|
||||||
|
isSelected = 1,
|
||||||
|
sortOrder = getNextSortOrder(category)
|
||||||
|
) => {
|
||||||
|
images.push({
|
||||||
|
category,
|
||||||
|
imageUrl,
|
||||||
|
isSelected,
|
||||||
|
sortOrder
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const getCoverFromImageUrl = () => {
|
||||||
|
if (item.coverFrom === "mainProductImage") return item.mainProductImage || null
|
||||||
|
return item.sketch
|
||||||
|
}
|
||||||
|
|
||||||
|
pushImage("sketch", item.sketch)
|
||||||
|
pushImage("cover", item.cover)
|
||||||
|
pushImage("cover_from", getCoverFromImageUrl())
|
||||||
|
|
||||||
|
if (item.mainProductImage) {
|
||||||
|
pushImage("main_product", item.mainProductImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSortedProductMedia(item).forEach(({ media }) => {
|
||||||
|
pushImage("product", media.url, Number(!!media.selected))
|
||||||
|
})
|
||||||
|
|
||||||
|
item.sketchList.forEach((sketch) => {
|
||||||
|
pushImage("apparel", sketch.url)
|
||||||
|
})
|
||||||
|
|
||||||
|
let videoSortOrder = 0
|
||||||
|
item.prodImageList
|
||||||
|
.filter((media) => media.isVideo)
|
||||||
|
.forEach((media) => {
|
||||||
|
videoSortOrder += 1
|
||||||
|
const isSelected = Number(!!media.selected)
|
||||||
|
|
||||||
|
pushImage(
|
||||||
|
"firstFrame",
|
||||||
|
media.firstFrameUrl || media.url,
|
||||||
|
isSelected,
|
||||||
|
videoSortOrder
|
||||||
|
)
|
||||||
|
pushImage("gif", media.gifUrl || "", isSelected, videoSortOrder)
|
||||||
|
pushImage("video", media.videoUrl || "", isSelected, videoSortOrder)
|
||||||
|
})
|
||||||
|
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
|
||||||
const handleSaveForm = async (type: StatusType) => {
|
const handleSaveForm = async (type: StatusType) => {
|
||||||
const paramsList = []
|
const paramsList = selectList.value.map((item: ListingItem) => {
|
||||||
selectList.value.forEach((item: ListingItem) => {
|
return {
|
||||||
const params = {
|
|
||||||
id: itemId.value,
|
id: itemId.value,
|
||||||
title: item.productName,
|
title: item.productName,
|
||||||
description: item.desc,
|
description: item.desc,
|
||||||
price: item.price,
|
price: item.price,
|
||||||
status: type === "draft" ? 0 : 1,
|
status: type === "draft" ? 0 : 1,
|
||||||
images: [],
|
images: buildListingImages(item),
|
||||||
designFor: (item.gender || "FEMALE").toLowerCase(),
|
designFor: getListingDesignFor(item.gender),
|
||||||
productCategory: item.category
|
productCategory: item.category
|
||||||
}
|
}
|
||||||
|
|
||||||
;["sketch", "cover"].forEach((el) => {
|
|
||||||
params.images.push({
|
|
||||||
category: el,
|
|
||||||
imageUrl: item[el],
|
|
||||||
isSelected: 1
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if (item.mainProductImage) {
|
|
||||||
params.images.push({
|
|
||||||
category: "main_product",
|
|
||||||
imageUrl: item.mainProductImage,
|
|
||||||
isSeleted: 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
item.prodImageList.forEach((item) => {
|
|
||||||
params.images.push({
|
|
||||||
category: "product",
|
|
||||||
imageUrl: item.url,
|
|
||||||
isSelected: Number(!!item.selected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
item.sketchList.forEach((item) => {
|
|
||||||
params.images.push({
|
|
||||||
category: "apparel",
|
|
||||||
imageUrl: item.url,
|
|
||||||
isSelected: 1
|
|
||||||
})
|
|
||||||
})
|
|
||||||
paramsList.push(params)
|
|
||||||
})
|
})
|
||||||
await fetchUpdateListing(paramsList)
|
await fetchUpdateListing(paramsList)
|
||||||
}
|
}
|
||||||
const handleClickMenu = async (status: StatusType) => {
|
const handleClickMenu = debounce(async (status: StatusType) => {
|
||||||
if (status === "draft" && !selectList.value[currentIndex.value].cover) {
|
// async (status: StatusType) => {
|
||||||
message.error("请先完成封面制作")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!validatePublishRequired()) return
|
if (!validatePublishRequired()) return
|
||||||
|
|
||||||
await handleSaveForm(status)
|
await handleSaveForm(status)
|
||||||
|
|
||||||
|
// 从 sessionStorage 获取参数
|
||||||
|
const paramsStr = sessionStorage.getItem("listingEditParams")
|
||||||
|
const params = paramsStr ? JSON.parse(paramsStr) : {}
|
||||||
|
|
||||||
if (status === "draft") {
|
if (status === "draft") {
|
||||||
// save draft logic
|
|
||||||
// console.log("Saving draft...", currentListing.value)
|
|
||||||
ROUTER.push({
|
ROUTER.push({
|
||||||
name: "Status",
|
name: "Status",
|
||||||
params: { status: "draft" },
|
params: { status: "draft" },
|
||||||
state: {
|
state: {
|
||||||
type: history.state?.type,
|
type: params.type,
|
||||||
collectionId: history.state?.collectionId
|
collectionId: params.collectionId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (status === "publish") {
|
} else if (status === "publish") {
|
||||||
// publish logic
|
|
||||||
// console.log("Publishing...", currentListing.value)
|
|
||||||
ROUTER.push({
|
ROUTER.push({
|
||||||
name: "Status",
|
name: "Status",
|
||||||
params: { status: "publish" },
|
params: { status: "publish" },
|
||||||
state: {
|
state: {
|
||||||
type: history.state?.type,
|
type: params.type,
|
||||||
collectionId: history.state?.collectionId
|
collectionId: params.collectionId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
const handleFetchItemDetial = (list) => {
|
const handleFetchItemDetial = (list) => {
|
||||||
fetchSketchDetail(list).then((res) => {
|
fetchSketchDetail(list).then((res: SketchDetailResponse[]) => {
|
||||||
res.forEach((item, index) => {
|
res.forEach((item, index) => {
|
||||||
if (!selectList.value[index]) return
|
if (!selectList.value[index]) return
|
||||||
selectList.value[index].sketchList = item.clothes.map((el) => ({ url: el }))
|
selectList.value[index].sketchList = (item.clothes || []).map((el) => ({ url: el }))
|
||||||
selectList.value[index].prodImageList = item.toProductImageUrls.map((el) => ({
|
const imageItems = (item.toProductImageUrls || []).map((el) => ({
|
||||||
url: el
|
url: el,
|
||||||
|
selected: false
|
||||||
}))
|
}))
|
||||||
|
const videoItems = (item.videos || [])
|
||||||
|
.map((video) => createProductVideoItem(video))
|
||||||
|
.filter((video): video is ProductMediaItem => Boolean(video))
|
||||||
|
selectList.value[index].prodImageList = [...imageItems, ...videoItems]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -458,13 +680,28 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetScrollPositions = () => {
|
||||||
|
if (editDetailContentRef.value) {
|
||||||
|
editDetailContentRef.value.scrollTop = 0
|
||||||
|
}
|
||||||
|
if (rightContentRef.value) {
|
||||||
|
rightContentRef.value.scrollTop = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const data = history.state
|
// 重置滚动条位置
|
||||||
|
resetScrollPositions()
|
||||||
|
|
||||||
|
// 从 sessionStorage 获取参数
|
||||||
|
const paramsStr = sessionStorage.getItem("listingEditParams")
|
||||||
|
const data = paramsStr ? JSON.parse(paramsStr) : {}
|
||||||
|
|
||||||
if (data?.type === "edit") {
|
if (data?.type === "edit") {
|
||||||
itemId.value = history.state?.id
|
itemId.value = data?.id
|
||||||
handleGetDetailById()
|
handleGetDetailById()
|
||||||
} else {
|
} else {
|
||||||
const designItemIds = history.state?.designItemIds || []
|
const designItemIds = data?.designItemIds || []
|
||||||
|
|
||||||
if (!designItemIds.length) return
|
if (!designItemIds.length) return
|
||||||
|
|
||||||
@@ -483,6 +720,11 @@
|
|||||||
.c-svg {
|
.c-svg {
|
||||||
width: initial;
|
width: initial;
|
||||||
height: initial;
|
height: initial;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: inherit;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-detail-wrapper {
|
.edit-detail-wrapper {
|
||||||
@@ -512,6 +754,11 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: #000;
|
background: #000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
|
.c-svg svg {
|
||||||
|
color: #fff;
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// &.publish:hover {
|
// &.publish:hover {
|
||||||
@@ -538,7 +785,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
justify-content: space-between;
|
justify-content: space-around;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -548,10 +795,26 @@
|
|||||||
.right {
|
.right {
|
||||||
width: 55.2rem;
|
width: 55.2rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
.right-content {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.page-control {
|
.page-control {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 4rem;
|
margin-top: 2rem;
|
||||||
|
padding: 2rem 0;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,27 @@ export type RadioOption = {
|
|||||||
|
|
||||||
export type TopImageType = "sketch" | "mainProductImage" | "cover"
|
export type TopImageType = "sketch" | "mainProductImage" | "cover"
|
||||||
export type CropType = TopImageType | "apparel"
|
export type CropType = TopImageType | "apparel"
|
||||||
|
export type CoverSourceType = "sketch" | "mainProductImage"
|
||||||
|
export type ListingImageCategory =
|
||||||
|
| "cover"
|
||||||
|
| "cover_from"
|
||||||
|
| "main_product"
|
||||||
|
| "mainProductImage"
|
||||||
|
| "product"
|
||||||
|
| "sketch"
|
||||||
|
| "apparel"
|
||||||
|
| "firstFrame"
|
||||||
|
| "gif"
|
||||||
|
| "video"
|
||||||
|
|
||||||
|
export type ProductMediaItem = {
|
||||||
|
url: string
|
||||||
|
selected?: boolean
|
||||||
|
isVideo?: boolean
|
||||||
|
videoUrl?: string
|
||||||
|
gifUrl?: string
|
||||||
|
firstFrameUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type ListingItem = {
|
export type ListingItem = {
|
||||||
designItemId: number | string | null
|
designItemId: number | string | null
|
||||||
@@ -20,18 +41,18 @@ export type ListingItem = {
|
|||||||
desc: string
|
desc: string
|
||||||
gender: string
|
gender: string
|
||||||
category: string[] | null
|
category: string[] | null
|
||||||
|
coverFrom: CoverSourceType
|
||||||
firstSelectedIndex: number | null
|
firstSelectedIndex: number | null
|
||||||
prodImageList: Array<{
|
prodImageList: ProductMediaItem[]
|
||||||
url: string
|
|
||||||
selected?: boolean
|
|
||||||
}>
|
|
||||||
sketchList: Array<{ url: string | null }>
|
sketchList: Array<{ url: string | null }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ListingDetailImage = {
|
export type ListingDetailImage = {
|
||||||
category?: string | null
|
category?: ListingImageCategory | string | null
|
||||||
imageUrl?: string | null
|
imageUrl?: string | null
|
||||||
isSelected?: boolean | number | string | null
|
isSelected?: boolean | number | string | null
|
||||||
|
isSeleted?: boolean | number | string | null
|
||||||
|
selected?: boolean | number | string | null
|
||||||
sortOrder?: number | null
|
sortOrder?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,4 +66,17 @@ export type ListingDetailResponse = {
|
|||||||
images?: ListingDetailImage[] | null
|
images?: ListingDetailImage[] | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SketchDetailVideo = {
|
||||||
|
firstFrameUrl?: string | null
|
||||||
|
gifUrl?: string | null
|
||||||
|
videoUrl?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SketchDetailResponse = {
|
||||||
|
clothes?: string[] | null
|
||||||
|
designItemId?: number | string | null
|
||||||
|
toProductImageUrls?: string[] | null
|
||||||
|
videos?: SketchDetailVideo[] | null
|
||||||
|
}
|
||||||
|
|
||||||
export type StatusType = "draft" | "publish"
|
export type StatusType = "draft" | "publish"
|
||||||
|
|||||||
@@ -55,26 +55,28 @@ defineExpose({getCreateList})
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="historyList">
|
<div class="historyList">
|
||||||
<div class="list">
|
<div class="listBox">
|
||||||
<div v-for="(item,index) in list" :key="index" class="item" @click="selectCollectionItem(item)">
|
<div class="list">
|
||||||
<div class="imgList">
|
<div v-for="(item,index) in list" :key="index" class="item" @click="selectCollectionItem(item)">
|
||||||
<div v-if="item.userLikeGroupVO?.groupDetails?.length > 0" v-for="(img,index) in item.userLikeGroupVO?.groupDetails.slice(0, 4)" :key="index" class="img">
|
<div class="imgList">
|
||||||
<img :src="img.url" alt="">
|
<div v-if="item.userLikeGroupVO?.groupDetails?.length > 0" v-for="(img,index) in item.userLikeGroupVO?.groupDetails.slice(0, 4)" :key="index" class="img">
|
||||||
|
<img :src="img.url" alt="">
|
||||||
|
</div>
|
||||||
|
<div v-else class="img null">
|
||||||
|
<img src="@/assets/images/seller/selectCollectionNullStatus.png" alt="">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="img null">
|
<div class="detail">
|
||||||
<img src="@/assets/images/seller/selectCollectionNullStatus.png" alt="">
|
<div class="name">{{item.name}}</div>
|
||||||
|
<div class="bottom">
|
||||||
|
<div>{{item.userLikeGroupVO?.groupDetails?.length || 0}} {{ $t('Seller.sketchs') }}</div>
|
||||||
|
<div>{{setPubDate(item.updateTime,t)}}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="type" :class="item.type">
|
||||||
<div class="detail">
|
{{item.process == "SERIES_DESIGN"?"Series":"Single"}}
|
||||||
<div class="name">{{item.name}}</div>
|
|
||||||
<div class="bottom">
|
|
||||||
<div>{{item.userLikeGroupVO?.groupDetails?.length || 0}} sketchs</div>
|
|
||||||
<div>{{setPubDate(item.updateTime,t)}}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="type" :class="item.type">
|
|
||||||
{{item.process == "SERIES_DESIGN"?"Series":"Single"}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
@@ -94,105 +96,110 @@ defineExpose({getCreateList})
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
> .list{
|
> .listBox{
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
row-gap: 2.4rem; /* 垂直间距 3px */
|
|
||||||
column-gap: 3.2rem; /* 横向间距 2px */
|
|
||||||
align-content: start;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
&::-webkit-scrollbar{
|
&::-webkit-scrollbar{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
> .item{
|
> .list{
|
||||||
width: 42.6rem;
|
display: grid;
|
||||||
height: auto;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
// height: 27.6rem;
|
row-gap: 2.4rem; /* 垂直间距 3px */
|
||||||
border: 1.5px solid #C7C7C7;
|
column-gap: 3.2rem; /* 横向间距 2px */
|
||||||
position: relative;
|
align-content: start;
|
||||||
overflow: hidden;
|
&::-webkit-scrollbar{
|
||||||
border-radius: 2rem;
|
display: none;
|
||||||
cursor: pointer;
|
|
||||||
transition: all .3s;
|
|
||||||
> .imgList{
|
|
||||||
background-color: #eaeaea;
|
|
||||||
height: 19.2rem;
|
|
||||||
padding: 1rem 1.3rem;
|
|
||||||
display: flex;
|
|
||||||
gap: .4rem;
|
|
||||||
justify-content: center;
|
|
||||||
> .img{
|
|
||||||
width: 9.7rem;
|
|
||||||
height: 17.2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: #fff;
|
|
||||||
&.null{
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
> img{
|
|
||||||
object-fit: contain;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
> .detail{
|
> .item{
|
||||||
padding: 2rem 2.4rem;
|
width: 42.6rem;
|
||||||
> .name{
|
height: auto;
|
||||||
font-family: pingfang_heavy;
|
// height: 27.6rem;
|
||||||
font-weight: 400;
|
border: 1.5px solid #C7C7C7;
|
||||||
font-size: 1.797rem;
|
position: relative;
|
||||||
line-height: 1;
|
overflow: hidden;
|
||||||
}
|
|
||||||
> .bottom{
|
|
||||||
display: flex;
|
|
||||||
margin-top: .8rem;
|
|
||||||
justify-content: space-between;
|
|
||||||
> div{
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-family: pingfang_regular;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
line-height: 1;
|
|
||||||
color: #585858;
|
|
||||||
}
|
|
||||||
> div:nth-child(2){
|
|
||||||
color: #979797;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .type{
|
|
||||||
width: 7.6rem;
|
|
||||||
height: 3rem;
|
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
border: 1px solid #727070;
|
cursor: pointer;
|
||||||
background: #7E7C7C;
|
transition: all .3s;
|
||||||
font-weight: 500;
|
> .imgList{
|
||||||
font-size: 1.4rem;
|
background-color: #eaeaea;
|
||||||
line-height: 150%;
|
height: 19.2rem;
|
||||||
color: #fff;
|
padding: 1rem 1.3rem;
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: .5rem;
|
gap: .4rem;
|
||||||
right: .5rem;
|
justify-content: center;
|
||||||
display: flex;
|
> .img{
|
||||||
justify-content: center;
|
width: 9.7rem;
|
||||||
align-items: center;
|
height: 17.2rem;
|
||||||
&.Series{
|
border-radius: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #fff;
|
||||||
|
&.null{
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
> img{
|
||||||
|
object-fit: contain;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .detail{
|
||||||
|
padding: 2rem 2.4rem;
|
||||||
|
> .name{
|
||||||
|
font-family: pingfang_heavy;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.797rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
> .bottom{
|
||||||
|
display: flex;
|
||||||
|
margin-top: .8rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
> div{
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-family: pingfang_regular;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: #585858;
|
||||||
|
}
|
||||||
|
> div:nth-child(2){
|
||||||
|
color: #979797;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .type{
|
||||||
|
width: 7.6rem;
|
||||||
|
height: 3rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
border: 1px solid #727070;
|
||||||
|
background: #7E7C7C;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 150%;
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: .5rem;
|
||||||
|
right: .5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
&.Series{
|
||||||
|
|
||||||
|
}
|
||||||
|
&.Single{
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #727070;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.Single{
|
&:hover{
|
||||||
background-color: #ffffff;
|
border: 1.5px solid #000;
|
||||||
color: #727070;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover{
|
|
||||||
border: 1.5px solid #000;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .pagination{
|
> .pagination{
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useRouter } from "vue-router"
|
|||||||
//])
|
//])
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
let getCollectionListData = reactive({
|
let getCollectionListData = reactive({
|
||||||
process: [],
|
process: ['SERIES_DESIGN','SINGLE_DESIGN'],
|
||||||
projectName: '',
|
projectName: '',
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -21,11 +21,7 @@ const handleSearch = () => {
|
|||||||
historyListRef.value.getCreateList()
|
historyListRef.value.getCreateList()
|
||||||
}
|
}
|
||||||
const setProcess = (type:any) => {
|
const setProcess = (type:any) => {
|
||||||
if(type){
|
getCollectionListData.process = type
|
||||||
getCollectionListData.process = [type]
|
|
||||||
}else{
|
|
||||||
getCollectionListData.process = []
|
|
||||||
}
|
|
||||||
historyListRef.value.getCreateList()
|
historyListRef.value.getCreateList()
|
||||||
}
|
}
|
||||||
const selectCollectionItem = (item:any) => {
|
const selectCollectionItem = (item:any) => {
|
||||||
@@ -44,9 +40,9 @@ defineExpose({})
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div :class="{active:!getCollectionListData.process?.[0]}" @click="setProcess('')">All</div>
|
<div :class="{active:getCollectionListData.process?.includes('SERIES_DESIGN') && getCollectionListData.process?.includes('SINGLE_DESIGN')}" @click="setProcess(['SERIES_DESIGN','SINGLE_DESIGN'])">{{$t('Seller.All')}}</div>
|
||||||
<div :class="{active:getCollectionListData.process[0] == 'SERIES_DESIGN'}" @click="setProcess('SERIES_DESIGN')">Series Design</div>
|
<div :class="{active:getCollectionListData.process[0] == 'SERIES_DESIGN' && getCollectionListData.process.length == 1}" @click="setProcess(['SERIES_DESIGN'])">{{$t('Seller.SeriesDesign')}}</div>
|
||||||
<div :class="{active:getCollectionListData.process[0] == 'SINGLE_DESIGN'}" @click="setProcess('SINGLE_DESIGN')">Single Design</div>
|
<div :class="{active:getCollectionListData.process[0] == 'SINGLE_DESIGN' && getCollectionListData.process.length == 1}" @click="setProcess(['SINGLE_DESIGN'])">{{$t('Seller.SingleDesign')}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="search_input flex flex-align-center">
|
<div class="search_input flex flex-align-center">
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import selectMenu from '@/component/modules/selectMenu.vue'
|
|||||||
import { Https } from '@/tool/https'
|
import { Https } from '@/tool/https'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
// 定义组件名称
|
// 定义组件名称
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'myListingsSelectItem'
|
name: 'myListingsSelectItem'
|
||||||
})
|
})
|
||||||
|
const { t } = useI18n()
|
||||||
//const props = defineProps({
|
//const props = defineProps({
|
||||||
//})
|
//})
|
||||||
//const emit = defineEmits([
|
//const emit = defineEmits([
|
||||||
@@ -22,15 +25,15 @@ const route = useRoute()
|
|||||||
const domSize = ref('Small')
|
const domSize = ref('Small')
|
||||||
const domSizeList = ref([
|
const domSizeList = ref([
|
||||||
{
|
{
|
||||||
label:'Small',
|
label:t('Header.Small'),
|
||||||
value:'Small',
|
value:'Small',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'Medium',
|
label:t('Header.Medium'),
|
||||||
value:'Medium',
|
value:'Medium',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'Large',
|
label:t('Header.Large'),
|
||||||
value:'Large',
|
value:'Large',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -43,6 +46,10 @@ const chooseItem = (item:any)=>{
|
|||||||
if(chooseList.value.findIndex((i:any)=>i.designItemId == item.designItemId) != -1){
|
if(chooseList.value.findIndex((i:any)=>i.designItemId == item.designItemId) != -1){
|
||||||
chooseList.value.splice(chooseList.value.findIndex((i:any)=>i.designItemId == item.designItemId),1)
|
chooseList.value.splice(chooseList.value.findIndex((i:any)=>i.designItemId == item.designItemId),1)
|
||||||
}else{
|
}else{
|
||||||
|
if(chooseList.value.length >= 9){
|
||||||
|
message.info(t('Seller.selectSketchMaxNum'))
|
||||||
|
return
|
||||||
|
}
|
||||||
chooseList.value.push(item)
|
chooseList.value.push(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,13 +57,16 @@ const chooseItem = (item:any)=>{
|
|||||||
const next = ()=>{
|
const next = ()=>{
|
||||||
if(chooseList.value.length == 0)return
|
if(chooseList.value.length == 0)return
|
||||||
let designItemIds = chooseList.value.map((item:any)=>({designOutfitUrl:item.designOutfitUrl,designItemId:item.designItemId}))
|
let designItemIds = chooseList.value.map((item:any)=>({designOutfitUrl:item.designOutfitUrl,designItemId:item.designItemId}))
|
||||||
|
|
||||||
|
// 使用 sessionStorage 传递参数
|
||||||
|
sessionStorage.setItem('listingEditParams', JSON.stringify({
|
||||||
|
designItemIds,
|
||||||
|
type:'create',
|
||||||
|
collectionId: route.params.collectionId
|
||||||
|
}))
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
path:'/home/seller/myListings/edit',
|
path:'/home/seller/myListings/edit'
|
||||||
state: {
|
|
||||||
designItemIds,
|
|
||||||
type:'create',
|
|
||||||
collectionId: route.params.collectionId
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +155,11 @@ onMounted(()=>{
|
|||||||
// 开始监听
|
// 开始监听
|
||||||
if(resizeObserver)resizeObserver.observe(listingsBoxRef.value)
|
if(resizeObserver)resizeObserver.observe(listingsBoxRef.value)
|
||||||
})
|
})
|
||||||
|
chooseList.value = []
|
||||||
getCollectionDetail()
|
getCollectionDetail()
|
||||||
})
|
})
|
||||||
|
onActivated(()=>{
|
||||||
|
})
|
||||||
onUnmounted(()=>{
|
onUnmounted(()=>{
|
||||||
})
|
})
|
||||||
defineExpose({})
|
defineExpose({})
|
||||||
@@ -158,10 +171,10 @@ const {} = toRefs(data);
|
|||||||
<template #right>
|
<template #right>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="chooseNum">
|
<div class="chooseNum">
|
||||||
{{ chooseList.length }} sketches selected
|
{{ chooseList.length }} / 9 {{ t('Seller.sketchesSelected') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="button" @click="next">
|
<div class="button" @click="next">
|
||||||
<span>Next</span>
|
<span>{{ $t('Seller.Next') }}</span>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<i class="fi fi-rr-arrow-small-right"></i>
|
<i class="fi fi-rr-arrow-small-right"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,8 +185,8 @@ const {} = toRefs(data);
|
|||||||
<div class="content" ref="listingsBoxRef">
|
<div class="content" ref="listingsBoxRef">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<i class="fi fi-rs-comments"></i>
|
<i class="fi fi-rs-comments"></i>
|
||||||
<span>Active Listings</span>
|
<span>{{ $t('Seller.Praka') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="generalModel_state">
|
<div class="generalModel_state">
|
||||||
@@ -312,6 +325,7 @@ const {} = toRefs(data);
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
|
min-width: 90%;
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -366,7 +380,8 @@ const {} = toRefs(data);
|
|||||||
}
|
}
|
||||||
> img{
|
> img{
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
&.active{
|
&.active{
|
||||||
border: 1.5px solid #000;
|
border: 1.5px solid #000;
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import deleteDrafts from './deleteDrafts.vue'
|
|||||||
import { Https } from '@/tool/https'
|
import { Https } from '@/tool/https'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
//const props = defineProps({
|
//const props = defineProps({
|
||||||
//})
|
//})
|
||||||
//const emit = defineEmits([
|
//const emit = defineEmits([
|
||||||
//])
|
//])
|
||||||
|
const { t:$t } = useI18n()
|
||||||
let data = reactive({
|
let data = reactive({
|
||||||
showDrafts: false,
|
showDrafts: false,
|
||||||
})
|
})
|
||||||
@@ -50,15 +52,15 @@ const config = ref({
|
|||||||
const domSize = ref('Small')
|
const domSize = ref('Small')
|
||||||
const domSizeList = ref([
|
const domSizeList = ref([
|
||||||
{
|
{
|
||||||
label:'Small',
|
label:$t('Header.Small'),
|
||||||
value:'Small',
|
value:'Small',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'Medium',
|
label:$t('Header.Medium'),
|
||||||
value:'Medium',
|
value:'Medium',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label:'Large',
|
label:$t('Header.Large'),
|
||||||
value:'Large',
|
value:'Large',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -155,7 +157,7 @@ const draftListing = async (item: any)=>{
|
|||||||
list2.value.unshift(item)
|
list2.value.unshift(item)
|
||||||
list.value = list.value.filter((v: any)=>v.id != item.id)
|
list.value = list.value.filter((v: any)=>v.id != item.id)
|
||||||
})
|
})
|
||||||
message.success('Product moved to drafts and stats reset.')
|
message.success($t('Seller.draftMessage'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const publishListing = async (item: any)=>{
|
const publishListing = async (item: any)=>{
|
||||||
@@ -163,16 +165,20 @@ const publishListing = async (item: any)=>{
|
|||||||
list.value.unshift(item)
|
list.value.unshift(item)
|
||||||
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
list2.value = list2.value.filter((v: any)=>v.id != item.id)
|
||||||
})
|
})
|
||||||
message.success('Item is now live on the Marketplace.')
|
message.success($t('Seller.publishMessage'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const editListing = (item: any)=>{
|
const editListing = (item: any) => {
|
||||||
|
sessionStorage.setItem('listingEditParams', JSON.stringify({
|
||||||
|
id:item.id,
|
||||||
|
type:'edit'
|
||||||
|
}))
|
||||||
router.push({
|
router.push({
|
||||||
path:'/home/seller/myListings/edit',
|
path:'/home/seller/myListings/edit',
|
||||||
state: {
|
// state: {
|
||||||
id:item.id,
|
// id:item.id,
|
||||||
type:'edit'
|
// type:'edit'
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +243,7 @@ const { showDrafts } = toRefs(data);
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<i class="fi fi-rs-comments"></i>
|
<i class="fi fi-rs-comments"></i>
|
||||||
<span>Active Listings</span>
|
<span>{{ $t('Seller.ActiveListings') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="generalModel_state">
|
<div class="generalModel_state">
|
||||||
@@ -302,7 +308,7 @@ const { showDrafts } = toRefs(data);
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<i class="fi fi-rs-comments"></i>
|
<i class="fi fi-rs-comments"></i>
|
||||||
<span>Drafts</span>
|
<span>{{ $t('Seller.Drafts') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VueDraggable
|
<VueDraggable
|
||||||
@@ -398,6 +404,7 @@ const { showDrafts } = toRefs(data);
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
min-width: 90%;
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,15 +36,15 @@ const {} = toRefs(data);
|
|||||||
<div class="maskBtn">
|
<div class="maskBtn">
|
||||||
<div @click="$emit('editListing',item)">
|
<div @click="$emit('editListing',item)">
|
||||||
<svgIcon name="seller-edit" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
<svgIcon name="seller-edit" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
||||||
<div>Edit</div>
|
<div>{{ $t('Seller.Edit') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="type == 'listings'" @click="$emit('draftListing',item)">
|
<div v-if="type == 'listings'" @click="$emit('draftListing',item)">
|
||||||
<svgIcon name="seller-draft" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
<svgIcon name="seller-draft" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
||||||
<div>Draft</div>
|
<div>{{ $t('Seller.Draft') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="type == 'drafts'" @click="$emit('publishListing',item)">
|
<div v-else-if="type == 'drafts'" @click="$emit('publishListing',item)">
|
||||||
<svgIcon name="seller-share" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
<svgIcon name="seller-share" :size="domSize == 'Small'?32:domSize == 'Medium'?40:48" />
|
||||||
<div>Publish</div>
|
<div>{{ $t('Seller.Publish') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,6 +132,7 @@ const {} = toRefs(data);
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
> .maskBtn{
|
> .maskBtn{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -167,10 +168,16 @@ const {} = toRefs(data);
|
|||||||
--rightColor: #979797;
|
--rightColor: #979797;
|
||||||
.left{
|
.left{
|
||||||
gap: var(--detailRightItemGap);
|
gap: var(--detailRightItemGap);
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
.name{
|
.name{
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: var(--detailLeftNameSize);
|
font-size: var(--detailLeftNameSize);
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.price{
|
.price{
|
||||||
font-family: pingfang_regular;
|
font-family: pingfang_regular;
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ const { showAgain } = toRefs(data);
|
|||||||
<i class="fi fi-rr-trash"></i>
|
<i class="fi fi-rr-trash"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="titleText">
|
<div class="titleText">
|
||||||
<h1>Delete this listing?</h1>
|
<h1>{{ $t('Seller.DeleteConfirm') }}</h1>
|
||||||
<p>Your listing and its details will be permanently removed.</p>
|
<p>{{ $t('Seller.DeleteDesc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="deleteContent">
|
<div class="deleteContent">
|
||||||
@@ -85,12 +85,12 @@ const { showAgain } = toRefs(data);
|
|||||||
</div>
|
</div>
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div class="name">{{ item?.title }}</div>
|
<div class="name">{{ item?.title }}</div>
|
||||||
<div class="price">HK${{ item?.price }} · Draft</div>
|
<div class="price">HK${{ item?.price }} · {{ $t('Seller.Drafts') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btnBox">
|
<div class="btnBox">
|
||||||
<div class="btn" @click.stop="cleardata()">Cancel</div>
|
<div class="btn" @click.stop="cleardata()">{{ $t('Seller.Cancel') }}</div>
|
||||||
<div class="btn" @click.stop="deleteDrafts()">Delete</div>
|
<div class="btn" @click.stop="deleteDrafts()">{{ $t('Seller.Delete') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ const {} = toRefs(data);
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="myListings-seller">
|
<div class="myListings-seller">
|
||||||
<seller-header>
|
<seller-header :displayBack="false">
|
||||||
<template #right>
|
<template #right>
|
||||||
<div class="button" @click="newListing">
|
<div class="button" @click="newListing">
|
||||||
<span>New Listing</span>
|
<span>{{ $t('Seller.newListing') }}</span>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<i class="fi fi-br-plus"></i>
|
<i class="fi fi-br-plus"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="filter-box">
|
<div class="filter-box">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="title">All Invoice</div>
|
<div class="title">{{ t("Seller.allInvoice") }}</div>
|
||||||
<div class="tip">A summary of all completed transactions.</div>
|
<div class="tip">{{ t("Seller.myOrdersTip") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="input">
|
<div class="input">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-model="nameOrId"
|
v-model="nameOrId"
|
||||||
placeholder="Search by item name or order ID"
|
:placeholder="t('Seller.myOrdersSearchPlaceholder')"
|
||||||
@keydown.enter.prevent="getList(true)"
|
@keydown.enter.prevent="getList(true)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,11 +30,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="order-id">Order ID</div>
|
<div class="order-id">{{ t("Seller.orderId") }}</div>
|
||||||
<div class="item">Item</div>
|
<div class="item">{{ t("Seller.item") }}</div>
|
||||||
<div class="price">Price</div>
|
<div class="price">{{ t("Seller.price") }}</div>
|
||||||
<div class="buyer-username">Buyer Username</div>
|
<div class="buyer-username">{{ t("Seller.buyerUsername") }}</div>
|
||||||
<div class="date">Date</div>
|
<div class="date">{{ t("Seller.date") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="item" v-for="v in list" :key="v.orderId">
|
<div class="item" v-for="v in list" :key="v.orderId">
|
||||||
@@ -42,27 +42,27 @@
|
|||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="images">
|
<div class="images">
|
||||||
<img
|
<img
|
||||||
v-for="v in v.item.slice(0, maxItemNum)"
|
v-for="v in v.items.slice(0, maxItemNum)"
|
||||||
:key="v.id"
|
:key="v.id"
|
||||||
:src="v.url"
|
:src="v.url"
|
||||||
/>
|
/>
|
||||||
<span v-if="v.item.length > maxItemNum"
|
<span v-if="v.items.length > maxItemNum"
|
||||||
>+{{ v.item.length - maxItemNum }} more</span
|
>+{{ v.items.length - maxItemNum }} more</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="titles">
|
<div class="titles">
|
||||||
<div v-for="v in v.item.slice(0, maxItemNum)" :key="v.id">
|
<div v-for="v in v.items.slice(0, maxItemNum)" :key="v.id">
|
||||||
{{ v.title }}
|
{{ v.title }}
|
||||||
</div>
|
</div>
|
||||||
<span v-if="v.item.length > maxItemNum">...</span>
|
<span v-if="v.items.length > maxItemNum">...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="price">{{ v.price }}</div>
|
<div class="price">{{ v.price }}</div>
|
||||||
<div class="buyer-username">{{ v.username }}</div>
|
<div class="buyer-username">{{ v.username }}</div>
|
||||||
<div class="date">
|
<div
|
||||||
<div>{{ v.date }}</div>
|
class="date"
|
||||||
<div>{{ v.time }}</div>
|
v-html="FormatDate(v.date, $t('Seller.dateTimeFormat'))"
|
||||||
</div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="null" v-show="list.length === 0 && !loading && finish">
|
<div class="null" v-show="list.length === 0 && !loading && finish">
|
||||||
@@ -79,6 +79,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, computed } from "vue"
|
import { ref, onMounted, onBeforeUnmount, computed } from "vue"
|
||||||
import { Https } from "@/tool/https"
|
import { Https } from "@/tool/https"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
import { FormatDate } from "@/tool/util"
|
||||||
|
const { t } = useI18n()
|
||||||
const totals_obj = ref({
|
const totals_obj = ref({
|
||||||
totalRevenue: "--",
|
totalRevenue: "--",
|
||||||
totalPurchases: "--",
|
totalPurchases: "--",
|
||||||
@@ -87,17 +90,17 @@
|
|||||||
const totals = computed(() => [
|
const totals = computed(() => [
|
||||||
{
|
{
|
||||||
icon: "seller-qiandaizi",
|
icon: "seller-qiandaizi",
|
||||||
title: "Total Revenue",
|
title: t("Seller.totalRevenue"),
|
||||||
value: `HK$ ${totals_obj.value.totalRevenue}`
|
value: `HK$ ${totals_obj.value.totalRevenue}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "seller-gouwudai",
|
icon: "seller-gouwudai",
|
||||||
title: "Total Purchases",
|
title: t("Seller.totalPurchases"),
|
||||||
value: totals_obj.value.totalPurchases
|
value: totals_obj.value.totalPurchases
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "seller-eye",
|
icon: "seller-eye",
|
||||||
title: "Total Views",
|
title: t("Seller.totalViews"),
|
||||||
value: totals_obj.value.totalViews
|
value: totals_obj.value.totalViews
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@@ -139,10 +142,9 @@
|
|||||||
}
|
}
|
||||||
list.value.push(obj)
|
list.value.push(obj)
|
||||||
})
|
})
|
||||||
|
|
||||||
total.value = res.total
|
total.value = res.total
|
||||||
page.value++
|
page.value++
|
||||||
finish.value = page.value > total.value / size.value
|
finish.value = total.value <= list.value.length
|
||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -175,6 +177,7 @@
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
})
|
})
|
||||||
|
|
||||||
const formatTimestamp = (ts) => {
|
const formatTimestamp = (ts) => {
|
||||||
const d = new Date(ts)
|
const d = new Date(ts)
|
||||||
const h = d.getHours()
|
const h = d.getHours()
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="settings-index">
|
<div class="settings-index mini-scrollbar">
|
||||||
<div>
|
<div>
|
||||||
<div class="notification">
|
<div class="notification">
|
||||||
<div class="header">Notifications</div>
|
<div class="header">{{ $t("Seller.notifications") }}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="title">New order notification</div>
|
<div class="title">{{ $t("Seller.notificationsTitle") }}</div>
|
||||||
<div class="tip">Receive an inbox message when a new order is placed.</div>
|
<div class="tip">{{ $t("Seller.notificationsTip") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<a-switch v-model:checked="checked" />
|
<a-switch v-model:checked="checked" />
|
||||||
@@ -14,29 +14,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="payout">
|
<div class="payout">
|
||||||
<div class="header">Payout</div>
|
<div class="header">{{ $t("Seller.payout") }}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="title">Payment Providers</div>
|
<div class="title">{{ $t("Seller.payoutTitle") }}</div>
|
||||||
<div class="tip">Select how you want to receive payments.</div>
|
<div class="tip">{{ $t("Seller.payoutTip") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pay-item" v-for="v in payList" :key="v.type">
|
<div class="pay-item" v-for="v in payList" :key="v.type">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<img :src="v.icon" />
|
<img :src="v.icon" />
|
||||||
<div class="value">{{ v.value || "Unbound" }}</div>
|
<div class="value">{{ v.value || $t("Seller.unbound") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button v-if="v.value" class="manage">Manage</button>
|
<button v-if="v.value" class="manage">{{ $t("Seller.manage") }}</button>
|
||||||
<button v-else class="bind-now">Bind Now</button>
|
<button v-else class="bind-now">{{ $t("Seller.bindNow") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="title">Payment Currency</div>
|
<div class="title">{{ $t("Seller.paymentCurrency") }}</div>
|
||||||
<div class="tip">HKD - Hong Kong Dollar</div>
|
<div class="tip">{{ $t("Seller.HKD") }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button>Fixed</button>
|
<button>{{ $t("Seller.fixed") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,40 +44,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="data-privacy">
|
<div class="data-privacy">
|
||||||
<div class="header">Data & Privacy</div>
|
<div class="header">{{ $t("Seller.dataPrivacy") }}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">Copyright licence</div>
|
<div class="title">{{ $t("Seller.dataPrivacyTitle") }}</div>
|
||||||
<div class="tip">
|
<div class="tip">{{ $t("Seller.dataPrivacyTip1") }}</div>
|
||||||
A licence certificate is automatically included with every purchase
|
<div class="tip">{{ $t("Seller.dataPrivacyTip2") }}</div>
|
||||||
download. View the default licensing terms applied to your listings.
|
<div
|
||||||
</div>
|
class="tip"
|
||||||
<div class="tip">
|
v-html="
|
||||||
This licence is issued by Code-Create and is legally binding upon purchase.
|
$t('Seller.dataPrivacyTip3', { click: 'onSellerSettingsContactUs' })
|
||||||
It certifies the buyer's right to use the purchased design asset in
|
"
|
||||||
accordance with the terms below.
|
></div>
|
||||||
</div>
|
|
||||||
<div class="tip">
|
|
||||||
For custom licensing arrangements, <span>contact us</span>.
|
|
||||||
</div>
|
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button>
|
<button @click="onDownloadDataPrivacy">
|
||||||
<span class="icon"><svg-icon name="seller-download" size="14" /></span>
|
<span class="icon"><svg-icon name="seller-download" size="14" /></span>
|
||||||
<span class="label">Download to View</span>
|
<span class="label">{{ $t("Seller.downloadToView") }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stop">
|
<div class="stop">
|
||||||
<div class="header">Stop Selling</div>
|
<div class="header">{{ $t("Seller.stopSelling") }}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">Deactivate seller account</div>
|
<div class="title">{{ $t("Seller.stopSellingTitle") }}</div>
|
||||||
<div class="tip">
|
<div class="tip">{{ $t("Seller.stopSellingTip") }}</div>
|
||||||
Permanently deactivate your seller account. All listings and invoice records
|
|
||||||
will be deleted. You may re-register as a seller in the future, but your
|
|
||||||
previous sales data cannot be recovered.
|
|
||||||
</div>
|
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button>Deactivate</button>
|
<button @click="onStopSelling">{{ $t("Seller.deactivate") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,10 +79,19 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
|
import { Https } from "@/tool/https"
|
||||||
|
import { Modal } from "ant-design-vue"
|
||||||
import paypal from "@/assets/images/seller/setting/paypal.png"
|
import paypal from "@/assets/images/seller/setting/paypal.png"
|
||||||
import stripe from "@/assets/images/seller/setting/stripe.png"
|
import stripe from "@/assets/images/seller/setting/stripe.png"
|
||||||
import alipayHk from "@/assets/images/seller/setting/alipay-hk.png"
|
import alipayHk from "@/assets/images/seller/setting/alipay-hk.png"
|
||||||
import alipayChinese from "@/assets/images/seller/setting/alipay-chinese.png"
|
import alipayChinese from "@/assets/images/seller/setting/alipay-chinese.png"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
const { t } = useI18n()
|
||||||
|
import { useStore } from "vuex"
|
||||||
|
const store = useStore()
|
||||||
|
import { useRouter } from "vue-router"
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const checked = ref(true)
|
const checked = ref(true)
|
||||||
const payList = ref([
|
const payList = ref([
|
||||||
{
|
{
|
||||||
@@ -114,20 +115,46 @@
|
|||||||
value: "123123"
|
value: "123123"
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
window.onSellerSettingsContactUs = () => {
|
||||||
|
console.log("contact us")
|
||||||
|
}
|
||||||
|
const onDownloadDataPrivacy = () => {
|
||||||
|
console.log("download data privacy")
|
||||||
|
}
|
||||||
|
const onStopSelling = () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t("Seller.stopSellingTitle"),
|
||||||
|
content: t("Seller.stopSellingTip"),
|
||||||
|
okText: t("Seller.confirm"),
|
||||||
|
cancelText: t("Seller.cancel"),
|
||||||
|
centered: true,
|
||||||
|
onOk() {
|
||||||
|
store.commit("set_loading", true)
|
||||||
|
Https.axiosDelete(Https.httpUrls.deleteSellerDesigner)
|
||||||
|
.then((res) => {
|
||||||
|
store.commit("set_loading", false)
|
||||||
|
store.commit("seller/clear_state")
|
||||||
|
router.push({ name: "home" })
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
store.commit("set_loading", false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.settings-index {
|
.settings-index {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin: 0 16rem;
|
margin: 0 10rem;
|
||||||
|
padding: 0 6rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4.2rem;
|
gap: 4.2rem;
|
||||||
> div {
|
> div {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4.2rem;
|
|
||||||
> div {
|
> div {
|
||||||
|
margin-bottom: 4.2rem;
|
||||||
border-radius: 1.2rem;
|
border-radius: 1.2rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 0.15rem solid #d1d1d1;
|
border: 0.15rem solid #d1d1d1;
|
||||||
@@ -281,7 +308,7 @@
|
|||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: #999;
|
color: #999;
|
||||||
margin-bottom: 3rem;
|
margin-bottom: 3rem;
|
||||||
> span {
|
&:deep(*) {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: #0080ed;
|
color: #0080ed;
|
||||||
|
|||||||
@@ -28,6 +28,9 @@
|
|||||||
import toolTipBox from "./toolTipBox.vue"
|
import toolTipBox from "./toolTipBox.vue"
|
||||||
import myEvent from "@/tool/myEvents.js"
|
import myEvent from "@/tool/myEvents.js"
|
||||||
import { useStore } from "vuex"
|
import { useStore } from "vuex"
|
||||||
|
import { useI18n } from "vue-i18n"
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
cachedRoutes: {
|
cachedRoutes: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -35,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const isSeller = computed(() => store.state.seller.isSeller)
|
const isSeller = computed(() => store.state.seller.isSeller)
|
||||||
// store.dispatch("seller/get_designerInfo")
|
// store.dispatch("seller/get_designerInfo")
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -43,22 +46,22 @@
|
|||||||
const list = ref([
|
const list = ref([
|
||||||
{
|
{
|
||||||
icon: "seller-brandProfile",
|
icon: "seller-brandProfile",
|
||||||
layer: "Brand Profile",
|
layer: t("Seller.brandProfile"),
|
||||||
path: "/home/seller/brandProfile"
|
path: "/home/seller/brandProfile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "seller-myListings",
|
icon: "seller-myListings",
|
||||||
layer: "My Listings",
|
layer: t("Seller.myListings"),
|
||||||
path: "/home/seller/myListings"
|
path: "/home/seller/myListings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "seller-myOrders",
|
icon: "seller-myOrders",
|
||||||
layer: "My Orders",
|
layer: t("Seller.myOrders"),
|
||||||
path: "/home/seller/myOrders"
|
path: "/home/seller/myOrders"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "seller-settings",
|
icon: "seller-settings",
|
||||||
layer: "Settings",
|
layer: t("Seller.settings"),
|
||||||
path: "/home/seller/settings"
|
path: "/home/seller/settings"
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="seller-header">
|
<div class="seller-header">
|
||||||
<div class="back" @click="() => router.back()">
|
<div class="back" v-if="displayBack" @click="() => router.back()">
|
||||||
<svg-icon name="seller-back" size="24" />
|
<svg-icon name="seller-back" size="24" />
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
clickable: i < breadcrumbList.length - 1
|
clickable: i < breadcrumbList.length - 1
|
||||||
}"
|
}"
|
||||||
@click="onBreadcrumbClick(v, i)"
|
@click="onBreadcrumbClick(v, i)"
|
||||||
>{{ v.title }}</span
|
>{{ $t(v.title) }}</span
|
||||||
>
|
>
|
||||||
<span class="icon" v-show="i < breadcrumbList.length - 1">
|
<span class="icon" v-show="i < breadcrumbList.length - 1">
|
||||||
<svg-icon name="seller-arrow_right_solid" size="10" />
|
<svg-icon name="seller-arrow_right_solid" size="10" />
|
||||||
@@ -55,13 +55,16 @@
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
title?: string
|
title?: string
|
||||||
tip?: string
|
tip?: string
|
||||||
|
displayBack?: boolean
|
||||||
breadcrumbs?: SellerBreadcrumbSource[]
|
breadcrumbs?: SellerBreadcrumbSource[]
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
title: "",
|
title: "",
|
||||||
tip: "",
|
tip: "",
|
||||||
breadcrumbs: () => []
|
breadcrumbs: () => [],
|
||||||
|
displayBack: true
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -108,8 +111,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolveTitle = (title?: RouteMetaValue<string>, titleKey?: RouteMetaValue<string>) => {
|
const resolveTitle = (title?: RouteMetaValue<string>, titleKey?: RouteMetaValue<string>) => {
|
||||||
const key = resolveMetaValue(titleKey)
|
let key = title || titleKey
|
||||||
return key ? t(key) : resolveMetaValue(title) || ""
|
// const key = resolveMetaValue(titleKey)
|
||||||
|
return key ? t(key) || "" : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const autoBreadcrumbs = computed(() => {
|
const autoBreadcrumbs = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user