Browse Source

fix: ui改版暂存

pull/18/head
leiyuee 2 weeks ago
parent
commit
0e7a23e969
  1. 15
      .cursor/README.md
  2. 26
      AGENTS.md
  3. BIN
      src/assets/images/layout/nav/characters.png
  4. BIN
      src/assets/images/layout/nav/characters_active.png
  5. BIN
      src/assets/images/layout/nav/collapse.png
  6. BIN
      src/assets/images/layout/nav/create.png
  7. BIN
      src/assets/images/layout/nav/create_active.png
  8. BIN
      src/assets/images/layout/nav/explore.png
  9. BIN
      src/assets/images/layout/nav/explore_active.png
  10. BIN
      src/assets/images/layout/nav/more.png
  11. BIN
      src/assets/images/layout/nav/popi-logo.png
  12. BIN
      src/assets/images/layout/nav/popi-tv.png
  13. BIN
      src/assets/images/layout/nav/popi-tv_active.png
  14. BIN
      src/assets/images/layout/nav/profile.png
  15. BIN
      src/assets/images/layout/nav/profile_active.png
  16. BIN
      src/assets/images/layout/nav/subscribe.png
  17. BIN
      src/assets/images/layout/nav/subscribe_active.png
  18. BIN
      src/assets/images/layout/nav/top-help.png
  19. BIN
      src/assets/images/layout/nav/top-language.png
  20. BIN
      src/assets/images/layout/nav/top-skill.png
  21. BIN
      src/assets/images/layout/nav/top-weixin.png
  22. BIN
      src/assets/images/layout/nav/voice.png
  23. BIN
      src/assets/images/layout/nav/voice_active.png
  24. 6
      src/assets/images/menu/canvas.svg
  25. 8
      src/assets/images/menu/characters.svg
  26. 8
      src/assets/images/menu/create.svg
  27. 6
      src/assets/images/menu/explore-group.svg
  28. 3
      src/assets/images/menu/explore-vector.svg
  29. 12
      src/assets/images/menu/more.svg
  30. 2005
      src/layouts/Layout/Layout.scss
  31. 287
      src/layouts/Layout/Layout.tsx
  32. 6
      src/locales/en-US.json
  33. 8
      src/locales/zh-CN.json
  34. 8
      src/locales/zh-TW.json

15
.cursor/README.md

@ -0,0 +1,15 @@
# Agent Rules
The files in this directory are shared project instructions, not Cursor-only notes.
Codex and other coding agents must read and follow the matching rules through the repository-level `AGENTS.md` entry point.
## Always Applied
- `skills/react-best-practices/SKILL.md`
- `rules/changelog-writing.mdc`
## Conditional Skills
- `skills/frontend-design/SKILL.md`: frontend UI creation, styling, visual refinement, component/page/app design work.
- `skills/figma-ui-implementation/SKILL.md`: Figma-linked implementation or pixel-level design restoration.

26
AGENTS.md

@ -0,0 +1,26 @@
# Codex Project Instructions
This repository keeps project-specific agent rules in `.cursor/`. These rules are mandatory for Codex work in this repo.
## Mandatory Rule Loading
Before making code, UI, release-note, or documentation changes, inspect the relevant `.cursor` instructions and follow them as binding project policy.
Always apply:
- `.cursor/skills/react-best-practices/SKILL.md`
- `.cursor/rules/changelog-writing.mdc`
Apply for frontend UI work:
- `.cursor/skills/frontend-design/SKILL.md`
Apply for Figma design implementation:
- `.cursor/skills/figma-ui-implementation/SKILL.md`
## Enforcement
- Treat the matching `.cursor` instructions as higher-priority project conventions.
- If a task matches multiple files, read all matching files before editing.
- Do not silently ignore a matching rule. If a rule conflicts with user instructions or existing code constraints, explain the conflict and choose the safest path.
- For Figma-linked UI implementation, read the Figma design context first, then implement with Flex/Grid where possible and verify visual fidelity before finishing.
- For React changes, follow the React, TypeScript, state, effect, styling, i18n, and maintainability rules in `react-best-practices`.
- For changelog or version-update requests, inspect real repository changes first and write only from observed changes.

BIN
src/assets/images/layout/nav/characters.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

BIN
src/assets/images/layout/nav/characters_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
src/assets/images/layout/nav/collapse.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 B

BIN
src/assets/images/layout/nav/create.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 B

BIN
src/assets/images/layout/nav/create_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
src/assets/images/layout/nav/explore.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
src/assets/images/layout/nav/explore_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 B

BIN
src/assets/images/layout/nav/more.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

BIN
src/assets/images/layout/nav/popi-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
src/assets/images/layout/nav/popi-tv.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

BIN
src/assets/images/layout/nav/popi-tv_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

BIN
src/assets/images/layout/nav/profile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/assets/images/layout/nav/profile_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

BIN
src/assets/images/layout/nav/subscribe.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/assets/images/layout/nav/subscribe_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

BIN
src/assets/images/layout/nav/top-help.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/assets/images/layout/nav/top-language.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/assets/images/layout/nav/top-skill.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
src/assets/images/layout/nav/top-weixin.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
src/assets/images/layout/nav/voice.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/images/layout/nav/voice_active.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

6
src/assets/images/menu/canvas.svg

@ -0,0 +1,6 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 22.9239 16.665" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group">
<path id="Vector" d="M0.25 9.45643V2.49791H1.634H3.01799V3.01764V3.53736H2.19382H1.36964V9.47664V15.413L1.86103 15.3957C2.1285 15.3842 6.67859 15.3755 11.9689 15.3755H21.5853V9.45643V3.53736H20.7767H19.9681V3.01764V2.49791H21.321H22.6739V9.45643V16.415H11.4619H0.25V9.45643Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
<path id="Vector_2" d="M5.49984 2.18029L4.76897 0.820343L5.03644 0.687525C5.18261 0.615341 5.3941 0.508509 5.50606 0.450762C5.61803 0.393015 5.71755 0.34393 5.72688 0.338155C5.73932 0.332381 6.00368 0.800132 6.31469 1.3776C6.62881 1.95507 6.89628 2.44304 6.91494 2.46325C6.93671 2.48923 7.88529 2.4979 11.5366 2.49212L16.1271 2.48346L16.7118 1.40648L17.2965 0.329493L17.6759 0.514284C18.1891 0.765484 18.2575 0.803019 18.2575 0.837667C18.2575 0.854991 17.9372 1.46711 17.5422 2.20339L16.83 3.53734H11.5303H6.23383L5.49984 2.18029Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

8
src/assets/images/menu/characters.svg

@ -0,0 +1,8 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16.5127 18.4847" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group">
<path id="Vector" d="M7.37077 18.2009C6.0221 18.0585 4.69413 17.5713 3.59137 16.8192C3.10212 16.4819 2.83808 16.2646 2.39801 15.8273C1.3289 14.7655 0.611852 13.3888 0.358166 11.9047C0.329691 11.7398 0.298628 11.5549 0.288273 11.4925C0.277919 11.43 0.264975 9.31629 0.259798 6.79532L0.250005 2.45903C0.249632 2.29415 0.271207 2.12994 0.314159 1.97075C0.529016 1.15124 1.13993 0.544112 1.96571 0.331741C2.20127 0.269279 2.77077 0.271777 2.99598 0.334239C3.23414 0.3992 3.56807 0.554106 3.75704 0.684027C4.08839 0.911389 4.41197 1.31365 4.55175 1.67343C4.66565 1.95825 4.70448 2.23059 4.70448 2.73029V2.80144C4.70448 3.02018 4.52716 3.1975 4.30842 3.1975C4.08968 3.1975 3.91236 3.02018 3.91236 2.80144V2.7153C3.91495 2.19311 3.88647 2.03071 3.75704 1.78586C3.43346 1.17873 2.71123 0.881407 2.04596 1.08628C1.60589 1.2212 1.27195 1.54351 1.09852 2.00573C1.06084 2.1082 1.04147 2.2165 1.04128 2.32567L1.0338 6.59544C1.02862 10.4831 1.03121 11.0727 1.06745 11.3675C1.2616 12.9841 1.8984 14.2808 3.04776 15.3926C3.47229 15.8049 3.81658 16.0797 4.24112 16.347C5.18856 16.9442 6.21624 17.3064 7.35783 17.4539C7.81602 17.5113 8.68839 17.5113 9.14399 17.4539C11.3443 17.174 13.2159 16.0622 14.3911 14.3307C14.9244 13.5462 15.2791 12.6218 15.4395 11.6149C15.481 11.3526 15.4836 10.9578 15.4913 6.80781C15.4965 2.81773 15.4939 2.26557 15.4577 2.12315C15.3075 1.50853 14.7717 1.0663 14.1271 1.03132C13.4256 0.988842 12.7888 1.47355 12.6386 2.16313C12.6128 2.27556 12.5998 2.48543 12.5998 2.76776V2.80662C12.5998 3.0225 12.4248 3.1975 12.2089 3.1975C11.9946 3.1975 11.8223 3.02102 11.8274 2.80673L11.831 2.65533C11.8439 2.02321 11.8724 1.89829 12.0821 1.48604C12.372 0.921383 12.8872 0.511632 13.5214 0.346732C13.8346 0.264282 14.3187 0.269279 14.6267 0.359224C15.4007 0.584088 15.9443 1.10877 16.1902 1.86581C16.2383 2.01146 16.2627 2.16383 16.2627 2.31718V6.69538C16.2627 11.4925 16.2653 11.4275 16.1566 12.0172C16.0815 12.4244 15.8796 13.104 15.7088 13.5112C15.512 13.9835 15.0875 14.703 14.7458 15.1403C13.6404 16.5519 12.1157 17.5313 10.2959 17.9985C9.95423 18.0835 9.38214 18.1809 9.0275 18.2134C8.63403 18.2459 7.74354 18.2409 7.37077 18.2009Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
<path id="Vector_2" d="M4.75396 12.8043C4.17669 12.6993 3.74439 12.382 3.50106 11.8873C3.40528 11.6924 3.3328 11.3926 3.3328 11.1902C3.3328 10.7305 3.59166 10.2208 3.97478 9.931C4.51321 9.52125 5.29498 9.46628 5.8826 9.79858C6.22171 9.99096 6.51164 10.3383 6.64884 10.718C6.7265 10.9304 6.73426 11.4276 6.66696 11.6375C6.4754 12.2096 6.05604 12.6119 5.48136 12.7643C5.22509 12.8342 4.98952 12.8467 4.75396 12.8043ZM5.427 12.0047C5.61597 11.9123 5.7687 11.7699 5.8593 11.6C5.92661 11.4676 5.93437 11.4326 5.93437 11.2052C5.93179 11.0028 5.92143 10.9329 5.87484 10.8355C5.79718 10.6706 5.61338 10.4907 5.43736 10.4032C5.30016 10.3383 5.26392 10.3308 5.02835 10.3308C4.78243 10.3308 4.76172 10.3358 4.6064 10.4157C4.41226 10.5181 4.28283 10.6506 4.18963 10.8404C4.13269 10.9579 4.12233 11.0153 4.12233 11.2052C4.12233 11.3976 4.13269 11.4501 4.19222 11.5725C4.30353 11.8024 4.55722 12.0047 4.81867 12.0697C4.97658 12.1097 5.27945 12.0772 5.427 12.0047Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
<path id="Vector_3" d="M11.2799 12.8018C10.8606 12.7344 10.4179 12.4745 10.1953 12.1672C9.96749 11.8549 9.87171 11.5626 9.87171 11.1928C9.8743 10.5707 10.2419 10.016 10.8269 9.74369C11.0987 9.61877 11.2618 9.58379 11.5776 9.58629C11.8934 9.58629 12.0358 9.61877 12.3024 9.74369C12.8978 10.0235 13.2473 10.5582 13.2473 11.1928C13.2473 11.8374 12.8978 12.3796 12.3024 12.6544C11.9426 12.8193 11.6501 12.8618 11.2799 12.8018ZM11.9607 12.0023C12.1264 11.9249 12.3076 11.7525 12.3904 11.5976C12.4189 11.5426 12.4578 11.4202 12.4733 11.3252C12.538 10.9405 12.3283 10.5657 11.9426 10.3908C11.8106 10.3308 11.7511 10.3183 11.5854 10.3183C11.1039 10.3183 10.7337 10.6082 10.6587 11.0454C10.6198 11.2653 10.6405 11.3977 10.7389 11.5901C10.8554 11.8124 11.0314 11.9648 11.2825 12.0523C11.4585 12.1148 11.7744 12.0923 11.9607 12.0023Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
<path id="Vector_4" d="M5.8411 2.89272C5.83593 2.36304 5.84628 2.21813 5.90841 1.98077C6.14139 1.09381 6.91021 0.416723 7.84212 0.274309C8.15534 0.22434 8.65236 0.254321 8.93711 0.336771C9.71629 0.559137 10.335 1.15627 10.5705 1.91082C10.6353 2.11569 10.6404 2.18065 10.6534 2.66786L10.6561 2.80931C10.6603 3.02248 10.4886 3.19754 10.2754 3.19754C10.0597 3.19754 9.8836 3.025 9.87917 2.80932L9.87678 2.69284C9.86643 2.25561 9.85608 2.16566 9.80948 2.04074C9.71629 1.79838 9.59721 1.62099 9.40306 1.4361C9.20633 1.24872 9.01736 1.13878 8.74296 1.04634C8.51775 0.971386 8.03109 0.963891 7.79811 1.03385C7.21308 1.20375 6.78854 1.62349 6.65652 2.16566C6.62546 2.28809 6.61511 2.45549 6.61511 2.7678V2.81183C6.61511 3.02485 6.44242 3.19754 6.2294 3.19754H6.14852C5.97975 3.19754 5.84254 3.06148 5.8411 2.89272Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

8
src/assets/images/menu/create.svg

@ -0,0 +1,8 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 18.1539 17.0249" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group">
<path id="Vector" d="M7.91299 16.758C7.08296 16.6579 6.5879 16.5388 5.91057 16.2815C4.93769 15.9075 3.88352 15.1903 3.20373 14.4327C2.26533 13.394 1.73332 12.3004 1.49194 10.9234C1.43283 10.5874 1.42544 10.4945 1.42544 9.87509C1.42298 9.33904 1.43283 9.13415 1.46731 8.89829C1.588 8.09064 1.84415 7.31634 2.21607 6.63496C2.40326 6.29189 2.81704 5.69628 3.06827 5.408C3.44511 4.9744 4.02638 4.46693 4.43277 4.2144C4.52883 4.15483 4.56824 4.10957 4.59287 4.04286C4.61503 3.97615 4.62488 3.49728 4.62981 2.10355C4.63526 1.07827 5.46795 0.25 6.49323 0.25H8.59278H10.7518C11.7413 0.25 12.5434 1.05213 12.5434 2.0416C12.5434 3.63308 12.5484 3.8475 12.5853 3.97377C12.6075 4.05477 12.6469 4.12624 12.6764 4.14769C13.5262 4.72424 14.1099 5.30079 14.637 6.07747C15.1936 6.9018 15.5655 7.84525 15.7232 8.83873C15.7921 9.25804 15.8069 10.2372 15.7552 10.6494C15.5114 12.5744 14.4941 14.2636 12.8907 15.4095C11.8957 16.1195 10.775 16.5626 9.55089 16.727C9.20361 16.7723 8.19131 16.7913 7.91299 16.758ZM9.46469 15.8455C10.5484 15.6835 11.5533 15.2761 12.4203 14.6448C12.7454 14.4065 13.3464 13.8395 13.5779 13.5488C14.2134 12.7555 14.5927 11.9478 14.7823 10.9948C14.8587 10.597 14.871 10.4469 14.871 9.82745C14.871 9.17465 14.8439 8.94594 14.6986 8.37892C14.4326 7.32587 13.8464 6.33954 13.0459 5.58669C12.7257 5.2865 12.5287 5.13164 12.1518 4.88148C11.8341 4.66944 11.7898 4.60988 11.6962 4.27634C11.6518 4.10957 11.6469 4.00474 11.6395 2.6253C11.6341 1.81224 10.9727 1.15636 10.1597 1.15763L8.58293 1.1601L7.03718 1.16372C6.20566 1.16567 5.5307 1.83669 5.52388 2.66819C5.50664 4.30731 5.5091 4.29302 5.3564 4.58367C5.29728 4.69803 5.25541 4.73615 5.00172 4.90531C4.43031 5.28888 4.01899 5.65816 3.64461 6.12274C2.92049 7.02569 2.51902 7.95246 2.35892 9.1008C2.31212 9.44864 2.32444 10.4016 2.38109 10.7566C2.58552 12.0098 3.15447 13.1176 4.05839 14.0158C5.10517 15.0617 6.43519 15.6954 7.93762 15.867C8.29722 15.9098 9.11001 15.8979 9.46469 15.8455Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
<path id="Vector_2" d="M8.15906 5.23147V3.79134C8.15906 3.75698 8.1824 3.72702 8.21571 3.71862C8.24527 3.71147 8.44477 3.70432 8.65905 3.70432C8.95215 3.70432 9.04574 3.71147 9.04574 3.73529C9.0482 3.77341 9.0482 6.63473 9.0482 6.68953C9.04574 6.72527 9.0088 6.73003 8.6024 6.73003C8.35755 6.73003 8.15906 6.53154 8.15906 6.28669V5.23147Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
<path id="Vector_3" d="M0.400389 2.83236L0.357493 2.79053C0.213767 2.65039 0.214231 2.4192 0.358518 2.27963C0.501372 2.13907 0.910231 1.73882 1.26737 1.38622L1.60627 1.05216C1.77861 0.882293 2.05449 0.87936 2.2304 1.04552C2.41759 1.22183 2.54074 1.35524 2.53089 1.37192C2.50133 1.4148 0.737821 3.13255 0.723043 3.13255C0.713191 3.13255 0.567873 2.99913 0.400389 2.83236Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
<path id="Vector_4" d="M16.528 2.24159L15.9417 1.66821C15.7688 1.49914 15.7722 1.21988 15.9492 1.05513C16.1314 0.88692 16.4134 0.890958 16.5907 1.06432L17.1783 1.63883L17.7781 2.22855C17.9444 2.39209 17.9461 2.65974 17.7817 2.82529C17.6167 2.98968 17.4714 3.12548 17.4566 3.12548C17.4443 3.12548 17.0256 2.72761 16.528 2.24159Z" fill="var(--fill-0, black)" stroke="var(--stroke-0, black)" stroke-width="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

6
src/assets/images/menu/explore-group.svg

@ -0,0 +1,6 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 16.0049 15.5843" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Vector">
<path d="M13.3229 15.3214C12.7161 15.2615 11.82 14.9957 11.2583 14.7084L11.1421 14.6485L10.9003 14.7443C9.81943 15.1681 8.53705 15.3525 7.44667 15.2376C7.00103 15.1897 6.60755 15.1203 6.23302 15.0221C4.2893 14.5169 2.61817 13.2765 1.6155 11.5956C1.16512 10.8414 0.86171 10.0464 0.688671 9.17964C0.577263 8.61934 0.56304 8.43976 0.56304 7.731C0.56304 6.8283 0.61993 6.40448 0.847488 5.59755L0.956526 5.21444L0.868821 5.0181C0.416076 4.01722 0.188518 2.95888 0.264371 2.18787C0.31889 1.63954 0.50141 1.22291 0.833265 0.882895C1.12719 0.585984 1.46616 0.404006 1.91417 0.30344C2.56128 0.159773 3.57818 0.317806 4.74916 0.741623L5.08101 0.861345L5.44842 0.727256C6.03391 0.514151 6.71421 0.348934 7.30444 0.28189C7.58889 0.248367 8.41616 0.23879 8.75512 0.265129C9.70565 0.339356 10.7107 0.619506 11.5688 1.0529C13.1712 1.85983 14.4488 3.2055 15.1007 4.77386C15.5013 5.73643 15.6648 6.51223 15.6885 7.57297C15.7122 8.58103 15.5676 9.47416 15.2334 10.3721L15.1481 10.6067L15.231 10.8031C15.4254 11.2628 15.6008 11.8614 15.7004 12.3882C15.743 12.6252 15.7525 12.7617 15.7549 13.1664C15.7549 13.6046 15.7502 13.6788 15.7004 13.8608C15.4539 14.7826 14.7901 15.2903 13.7756 15.331C13.6381 15.3381 13.4343 15.3334 13.3229 15.3214ZM14.2378 14.5025C14.6361 14.3612 14.8636 14.1098 14.975 13.6812C15.0319 13.4657 15.0319 12.9485 14.975 12.6037C14.9134 12.2326 14.7522 11.6124 14.7001 11.5549C14.6906 11.5454 14.629 11.6268 14.5602 11.7393C14.0387 12.6013 13.1451 13.4968 12.2728 14.038C12.1472 14.117 12.0452 14.1888 12.0452 14.196C12.0452 14.2152 12.2894 14.3109 12.5193 14.3852C12.9863 14.5312 13.2944 14.5791 13.7282 14.5695C14.0198 14.5623 14.098 14.5528 14.2378 14.5025ZM1.62261 3.852C1.93787 3.3372 2.43328 2.72422 2.85047 2.33393C3.17996 2.02504 3.70144 1.62996 4.121 1.37136C4.15656 1.34742 4.13285 1.33544 3.96219 1.28037C3.74885 1.21093 3.28899 1.10079 2.99032 1.04811C2.72721 1.00022 2.18439 1.00501 1.99476 1.05769C1.48512 1.19657 1.1509 1.53658 1.03475 2.03462C0.963637 2.34111 1.00156 2.97324 1.12008 3.45692C1.17223 3.67003 1.32394 4.15131 1.35949 4.22075C1.3832 4.26385 1.38794 4.26385 1.42112 4.1968C1.44009 4.15849 1.53016 4.00285 1.62261 3.852Z" fill="var(--fill-0, #6F47F5)"/>
<path d="M14.1075 10.2021L14.3208 10.6235L14.1928 10.8845C13.842 11.5861 13.1569 12.4193 12.4956 12.9509C11.4763 13.7674 10.3291 14.2654 9.03483 14.4474C8.77883 14.4833 7.81408 14.5169 7.64815 14.4929C7.61022 14.4881 7.46563 14.4714 7.32815 14.457C6.56725 14.3756 5.7945 14.1409 5.04072 13.7674C4.31774 13.4058 3.78678 13.0203 3.22025 12.4433C2.17254 11.3753 1.56098 10.1087 1.34053 8.55708C1.28601 8.162 1.28364 7.30479 1.33816 6.89295C1.46853 5.93756 1.75061 5.1043 2.21046 4.32132M14.757 9.38317C14.757 9.38317 14.693 9.61303 14.6906 9.61782M6.32073 1.24206C6.2828 1.25403 6.20221 1.27558 6.14532 1.28995L6.04102 1.31868L6.41791 1.51982M13.3229 15.3214C12.7161 15.2615 11.82 14.9957 11.2583 14.7084L11.1421 14.6485L10.9003 14.7443C9.81943 15.1681 8.53705 15.3525 7.44667 15.2376C7.00103 15.1897 6.60755 15.1203 6.23302 15.0221C4.2893 14.5169 2.61817 13.2765 1.6155 11.5956C1.16512 10.8414 0.86171 10.0464 0.688671 9.17964C0.577263 8.61934 0.56304 8.43976 0.56304 7.731C0.56304 6.8283 0.61993 6.40448 0.847488 5.59755L0.956526 5.21444L0.868821 5.0181C0.416076 4.01722 0.188518 2.95888 0.264371 2.18787C0.31889 1.63954 0.50141 1.22291 0.833265 0.882895C1.12719 0.585984 1.46616 0.404006 1.91417 0.30344C2.56128 0.159773 3.57818 0.317806 4.74916 0.741623L5.08101 0.861345L5.44842 0.727256C6.03391 0.514151 6.71421 0.348934 7.30444 0.28189C7.58889 0.248367 8.41616 0.23879 8.75512 0.265129C9.70565 0.339356 10.7107 0.619506 11.5688 1.0529C13.1712 1.85983 14.4488 3.2055 15.1007 4.77386C15.5013 5.73643 15.6648 6.51223 15.6885 7.57297C15.7122 8.58103 15.5676 9.47416 15.2334 10.3721L15.1481 10.6067L15.231 10.8031C15.4254 11.2628 15.6008 11.8614 15.7004 12.3882C15.743 12.6252 15.7525 12.7617 15.7549 13.1664C15.7549 13.6046 15.7502 13.6788 15.7004 13.8608C15.4539 14.7826 14.7901 15.2903 13.7756 15.331C13.6381 15.3381 13.4343 15.3334 13.3229 15.3214ZM14.2378 14.5025C14.6361 14.3612 14.8636 14.1098 14.975 13.6812C15.0319 13.4657 15.0319 12.9485 14.975 12.6037C14.9134 12.2326 14.7522 11.6124 14.7001 11.5549C14.6906 11.5454 14.629 11.6268 14.5602 11.7393C14.0387 12.6013 13.1451 13.4968 12.2728 14.038C12.1472 14.117 12.0452 14.1888 12.0452 14.196C12.0452 14.2152 12.2894 14.3109 12.5193 14.3852C12.9863 14.5312 13.2944 14.5791 13.7282 14.5695C14.0198 14.5623 14.098 14.5528 14.2378 14.5025ZM1.62261 3.852C1.93787 3.3372 2.43328 2.72422 2.85047 2.33393C3.17996 2.02504 3.70144 1.62996 4.121 1.37136C4.15656 1.34742 4.13285 1.33544 3.96219 1.28037C3.74885 1.21093 3.28899 1.10079 2.99032 1.04811C2.72721 1.00022 2.18439 1.00501 1.99476 1.05769C1.48512 1.19657 1.1509 1.53658 1.03475 2.03462C0.963637 2.34111 1.00156 2.97324 1.12008 3.45692C1.17223 3.67003 1.32394 4.15131 1.35949 4.22075C1.3832 4.26385 1.38794 4.26385 1.42112 4.1968C1.44009 4.15849 1.53016 4.00285 1.62261 3.852Z" stroke="var(--stroke-0, #6F47F5)" stroke-width="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

3
src/assets/images/menu/explore-vector.svg

@ -0,0 +1,3 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 4.1577 4.20716" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Vector" d="M0.313791 1.61963C0.0388255 2.67558 0.678832 3.71237 1.74077 3.93266C1.93514 3.97337 2.34522 3.96139 2.53485 3.91111C2.75767 3.85125 3.08715 3.67406 3.27441 3.51603C3.46642 3.35081 3.69397 3.02996 3.77931 2.80488C3.91916 2.42416 3.94524 2.00992 3.85516 1.64836C3.68923 1.00186 3.1796 0.482268 2.53248 0.302685C2.39263 0.264373 2.28122 0.250007 2.08448 0.250007C1.75973 0.252401 1.55825 0.297896 1.27854 0.436773C0.794981 0.673823 0.451274 1.09525 0.313791 1.61963Z" fill="var(--fill-0, white)" stroke="var(--stroke-0, white)" stroke-width="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 754 B

12
src/assets/images/menu/more.svg

@ -0,0 +1,12 @@
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 19.4444 13.8889" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="_&#233;&#141;&#165;&#230;&#131;&#167;&#231;&#156;&#176;_1" clip-path="url(#clip0_0_6)">
<path id="Vector" d="M18.3732 8.05282H1.07121C0.479574 8.05282 0 7.55648 0 6.94416C0 6.33184 0.479574 5.83551 1.07121 5.83551H18.3732C18.9649 5.83551 19.4444 6.33184 19.4444 6.94416C19.4444 7.55648 18.9649 8.05282 18.3732 8.05282Z" fill="var(--fill-0, #666666)"/>
<path id="Vector_2" d="M18.3732 13.8889H1.07121C0.479574 13.8889 0 13.3926 0 12.7802C0 12.1679 0.479574 11.6716 1.07121 11.6716H18.3732C18.9649 11.6716 19.4444 12.1679 19.4444 12.7802C19.4444 13.3926 18.9649 13.8889 18.3732 13.8889Z" fill="var(--fill-0, #666666)"/>
<path id="Vector_3" d="M18.3732 2.21731H1.07121C0.479574 2.21731 0 1.72097 0 1.10866C0 0.496336 0.479574 0 1.07121 0H18.3732C18.9649 0 19.4444 0.496336 19.4444 1.10866C19.4444 1.72097 18.9649 2.21731 18.3732 2.21731Z" fill="var(--fill-0, #666666)"/>
</g>
<defs>
<clipPath id="clip0_0_6">
<rect width="19.4444" height="13.8889" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

2005
src/layouts/Layout/Layout.scss

File diff suppressed because it is too large

287
src/layouts/Layout/Layout.tsx

@ -1,7 +1,6 @@
import type { MouseEvent as ReactMouseEvent, ReactNode } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { Dropdown, Message, Tooltip } from "@arco-design/web-react";
import { Dropdown, Tooltip } from "@arco-design/web-react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useShallow } from "zustand/react/shallow";
@ -12,14 +11,12 @@ import { hasPendingExploreTemplateTabIntent } from "@/store/useExploreFeedTabSto
import { isLayoutContentScrollLocked, isScrollEventFromLayoutModalMount, isScrollEventInLayoutContentScroll, setLayoutContentModalMount, setLayoutContentScrollRoot } from "@/layouts/layoutContentModalMount";
import { changeLanguage, type AppLanguage, SUPPORTED_LANGUAGES } from "@/i18n";
import { closeAssetDetailsModal, closeCharacterDetailModal, openGatewayApikeyDialog, openLoginDialog, openPointsDetailDialog, openPointsRechargeDialog } from "@/dialog";
import { AliceLiveClassDialogOverlay } from "@/dialog/alice-live-class";
import { AppMacDownloadDialogOverlay } from "@/dialog/app-download-mac";
import { canAccessRoute, guardRouteAccess } from "@/router/routeGuard";
import { useComposerStore } from "@/store/useComposerStore";
import { useUserStore } from "@/store/useUserStore";
import { getToken, removeToken } from "@/utils/auth";
import { openLatestPrivacyPolicyLink, PRIVACY_POLICY_KEY_AI_USE_NOTICE, PRIVACY_POLICY_KEY_PRIVACY_POLICY, PRIVACY_POLICY_KEY_USAGE_TUTORIAL, PRIVACY_POLICY_KEY_USER_TERMS } from "@/utils/openLatestPrivacyPolicyLink";
import { getAppLatest, getParamConfig, logout } from "@/api/api";
import { getParamConfig, logout } from "@/api/api";
import { useCopyText } from "@/hooks/useCopyText";
import { useLayoutViewportScale } from "@/hooks/useViewportScale";
import { memberLevelMap } from "@/constants";
@ -34,44 +31,78 @@ interface LayoutSideNavItem {
id: string;
labelKey: string;
iconSrc: string;
activeIconSrc?: string;
to: string;
badge?: string;
badgeKey?: string;
}
interface LayoutSideNavIconProps {
item: LayoutSideNavItem;
}
const CANVAS_BASE_URL = import.meta.env.VITE_CANVAS_URL as string | undefined;
// 侧栏图标(顺序:探索 → 角色 → 声音 → 创造 → Popi TV)
const LAYOUT_NAV_ASSETS = {
logo: new URL("@/assets/images/layout/nav/popi-logo.png", import.meta.url).href,
collapse: new URL("@/assets/images/layout/nav/collapse.png", import.meta.url).href,
explore: new URL("@/assets/images/layout/nav/explore.png", import.meta.url).href,
exploreActive: new URL("@/assets/images/layout/nav/explore_active.png", import.meta.url).href,
characters: new URL("@/assets/images/layout/nav/characters.png", import.meta.url).href,
charactersActive: new URL("@/assets/images/layout/nav/characters_active.png", import.meta.url).href,
create: new URL("@/assets/images/layout/nav/create.png", import.meta.url).href,
createActive: new URL("@/assets/images/layout/nav/create_active.png", import.meta.url).href,
canvas: new URL("@/assets/images/layout/nav/popi-tv.png", import.meta.url).href,
canvasActive: new URL("@/assets/images/layout/nav/popi-tv_active.png", import.meta.url).href,
voice: new URL("@/assets/images/layout/nav/voice.png", import.meta.url).href,
voiceActive: new URL("@/assets/images/layout/nav/voice_active.png", import.meta.url).href,
subscribe: new URL("@/assets/images/layout/nav/subscribe.png", import.meta.url).href,
subscribeActive: new URL("@/assets/images/layout/nav/subscribe_active.png", import.meta.url).href,
profile: new URL("@/assets/images/layout/nav/profile.png", import.meta.url).href,
profileActive: new URL("@/assets/images/layout/nav/profile_active.png", import.meta.url).href,
more: new URL("@/assets/images/layout/nav/more.png", import.meta.url).href,
topHelp: new URL("@/assets/images/layout/nav/top-language.png", import.meta.url).href,
topLanguage: new URL("@/assets/images/layout/nav/top-help.png", import.meta.url).href,
topService: new URL("@/assets/images/layout/nav/top-weixin.png", import.meta.url).href,
topSkill: new URL("@/assets/images/layout/nav/top-skill.png", import.meta.url).href,
} as const;
// 侧栏图标顺序与 2026 7月官网 Figma 设计保持一致。
const LAYOUT_SIDE_NAV_ITEMS: readonly LayoutSideNavItem[] = [
{
id: "explore",
labelKey: "layout.nav.explore",
iconSrc: new URL("@/assets/images/menu/explore.png", import.meta.url).href,
iconSrc: LAYOUT_NAV_ASSETS.explore,
activeIconSrc: LAYOUT_NAV_ASSETS.exploreActive,
to: ROUTES.EXPLORE,
},
{
id: "characters",
labelKey: "layout.nav.characters",
iconSrc: new URL("@/assets/images/menu/characters.png", import.meta.url).href,
iconSrc: LAYOUT_NAV_ASSETS.characters,
activeIconSrc: LAYOUT_NAV_ASSETS.charactersActive,
to: ROUTES.CHARACTERS,
},
{
id: "voice",
labelKey: "layout.nav.voice",
iconSrc: new URL("@/assets/images/menu/voice.png", import.meta.url).href,
to: ROUTES.VOICE,
},
{
id: "create",
labelKey: "layout.nav.create",
iconSrc: new URL("@/assets/images/menu/create.png", import.meta.url).href,
iconSrc: LAYOUT_NAV_ASSETS.create,
activeIconSrc: LAYOUT_NAV_ASSETS.createActive,
to: ROUTES.CREATE,
},
{
id: "canvas",
labelKey: "layout.nav.canvas",
iconSrc: new URL("@/assets/images/menu/canvas.png", import.meta.url).href,
iconSrc: LAYOUT_NAV_ASSETS.canvas,
activeIconSrc: LAYOUT_NAV_ASSETS.canvasActive,
to: CANVAS_BASE_URL ?? "",
badge: "Beta",
badgeKey: "layout.nav.canvasBadge",
},
{
id: "voice",
labelKey: "layout.nav.voice",
iconSrc: LAYOUT_NAV_ASSETS.voice,
activeIconSrc: LAYOUT_NAV_ASSETS.voiceActive,
to: ROUTES.VOICE,
},
];
@ -82,7 +113,6 @@ const LANGUAGE_LABEL_KEYS: Record<AppLanguage, string> = {
};
const LAYOUT_PROMO_BAR_DISMISSED_KEY = "popiart.layout.promoBar.dismissed";
const LAYOUT_APP_DOWNLOAD_TITLE_ICON = new URL("@/assets/images/layout/app-download/ic-popi-logo.png", import.meta.url).href;
function isLayoutPromoBarDismissed(): boolean {
try {
@ -109,21 +139,43 @@ function buildCanvasUrl(): string | null {
return url.toString();
}
function readServiceQrCode(value: unknown): string {
if (typeof value !== "object" || value == null || !("systemConfig" in value)) return "";
const systemConfig = value.systemConfig;
if (typeof systemConfig !== "object" || systemConfig == null || !("serviceQrCode" in systemConfig)) return "";
return typeof systemConfig.serviceQrCode === "string" ? systemConfig.serviceQrCode : "";
}
const LAYOUT_FOOTER_NAV_ITEMS: readonly LayoutSideNavItem[] = [
{
id: "subscribe",
labelKey: "layout.footer.subscribe",
iconSrc: new URL("@/assets/images/menu/subscribe.png", import.meta.url).href,
iconSrc: LAYOUT_NAV_ASSETS.subscribe,
activeIconSrc: LAYOUT_NAV_ASSETS.subscribeActive,
to: ROUTES.SUBSCRIBE,
},
{
id: "profile",
labelKey: "layout.footer.profile",
iconSrc: new URL("@/assets/images/menu/profile.png", import.meta.url).href,
iconSrc: LAYOUT_NAV_ASSETS.profile,
activeIconSrc: LAYOUT_NAV_ASSETS.profileActive,
to: ROUTES.PROFILE,
},
];
function LayoutSideNavIcon({ item }: LayoutSideNavIconProps) {
if (!item.activeIconSrc) {
return <img className="layoutShell__sideIcon" src={item.iconSrc} alt="" />;
}
return (
<span className="layoutShell__sideIcon" aria-hidden="true">
<img className="layoutShell__sideIconImage layoutShell__sideIconImage_default" src={item.iconSrc} alt="" />
<img className="layoutShell__sideIconImage layoutShell__sideIconImage_active" src={item.activeIconSrc} alt="" />
</span>
);
}
/**
* + +
*
@ -147,10 +199,8 @@ export function Layout({ children }: LayoutProps) {
const [topbarServiceQrOpen, setTopbarServiceQrOpen] = useState(false);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [promoBarVisible, setPromoBarVisible] = useState(() => !isLayoutPromoBarDismissed());
const [aliceLiveClassOpen, setAliceLiveClassOpen] = useState(false);
const activePromptPanelHost = useComposerStore((s) => s.activePromptPanelHost);
const footerRef = useRef<HTMLDivElement>(null);
const footerPopoverRef = useRef<HTMLDivElement>(null);
const userMenuRef = useRef<HTMLDivElement>(null);
const languageToolRef = useRef<HTMLDivElement>(null);
@ -231,35 +281,17 @@ export function Layout({ children }: LayoutProps) {
}, []);
const [serviceQrCode, setServiceQrCode] = useState("");
const [macIntelDownloadUrl, setMacIntelDownloadUrl] = useState("");
const [macArmDownloadUrl, setMacArmDownloadUrl] = useState("");
const [windowsDownloadUrl, setWindowsDownloadUrl] = useState("");
const [appMacDownloadOpen, setAppMacDownloadOpen] = useState(false);
useEffect(() => {
void getParamConfig()
.then((res) => {
setServiceQrCode(res.systemConfig?.serviceQrCode ?? "");
setServiceQrCode(readServiceQrCode(res));
})
.catch(() => {
setServiceQrCode("");
});
}, []);
useEffect(() => {
void getAppLatest()
.then((data) => {
setMacIntelDownloadUrl(data.macIntel?.url ?? "");
setMacArmDownloadUrl(data.macArm?.url ?? "");
setWindowsDownloadUrl(data.windowsX64?.url ?? "");
})
.catch(() => {
setMacIntelDownloadUrl("");
setMacArmDownloadUrl("");
setWindowsDownloadUrl("");
});
}, []);
useEffect(() => {
if (pathname === ROUTES.EXPLORE && hasPendingExploreTemplateTabIntent()) {
return;
@ -413,12 +445,12 @@ export function Layout({ children }: LayoutProps) {
<span>{t("layout.promo.seedanceSuffix")}</span>
</p>
<div className="layoutShell__promoSegment">
<span className="layoutShell__promoBadge">{t("layout.promo.badgeHot")}</span>
<p className="layoutShell__promoText">
<span>{t("layout.promo.discountPrefix")}</span>
<span className="layoutShell__promoText_heavy">{t("layout.promo.discountValue")}</span>
<span>{t("layout.promo.discountSuffix")}</span>
</p>
<span className="layoutShell__promoBadge">{t("layout.promo.badgeHot")}</span>
</div>
<p className="layoutShell__promoText">
<span>{t("layout.promo.pointsPrefix")}</span>
@ -430,82 +462,45 @@ export function Layout({ children }: LayoutProps) {
</button>
</div>
) : null}
<button
type="button"
className="layoutShell__sidebarCollapse"
aria-disabled={isCharactersCreateRoute}
onClick={() => {
if (!isCharactersCreateRoute) setSidebarCollapsed((v) => !v);
}}
aria-label={effectiveSidebarCollapsed ? t("layout.sidebar.expand") : t("layout.sidebar.collapse")}
aria-expanded={!effectiveSidebarCollapsed}
>
<span className="layoutShell__sidebarCollapseText">{t("layout.sidebar.menuLabel")}</span>
<img src={LAYOUT_NAV_ASSETS.collapse} alt="" />
</button>
<header className="layoutShell__topbar" aria-label={t("layout.aria.topbar")} onClick={dismissMainDetailModals}>
<div className="layoutShell__topbarLeft flex items-center justify-between gap-2">
{!effectiveSidebarCollapsed ? <img className="layoutShell__logo" onClick={() => navigate(ROUTES.INDEX)} src={new URL("@/assets/images/popiart.png", import.meta.url).href} /> : null}
<button
type="button"
className="layoutShell__collapse"
aria-disabled={isCharactersCreateRoute}
onClick={() => {
if (!isCharactersCreateRoute) setSidebarCollapsed((v) => !v);
}}
aria-label={effectiveSidebarCollapsed ? t("layout.sidebar.expand") : t("layout.sidebar.collapse")}
aria-expanded={!effectiveSidebarCollapsed}
>
<img src={new URL("@/assets/images/ic_collapse.png", import.meta.url).href} />
</button>
</div>
<div className="layoutShell__topbarRight" aria-label={t("layout.aria.userArea")}>
<div className="layoutShell__aliceMeetupTool">
<button type="button" className="layoutShell__aliceMeetup" aria-label={t("layout.topbar.aliceMeetup")}>
<img className="layoutShell__aliceMeetupIcon" src={new URL("@/assets/images/popi_avatar.png", import.meta.url).href} alt="" />
<span className="layoutShell__aliceMeetupText">{t("layout.topbar.aliceMeetup")}</span>
</button>
<div className="layoutShell__aliceMeetupFloat" role="dialog" aria-label={t("layout.topbar.aliceMeetupBook")} onClick={(e) => e.stopPropagation()}>
<img className="layoutShell__aliceMeetupQr" src={serviceQrCode} alt={t("layout.topbar.aliceMeetupBook")} />
<p className="layoutShell__aliceMeetupFloatText">{t("layout.topbar.aliceMeetupBook")}</p>
</div>
</div>
<div className="layoutShell__topbarTools">
<div
<button
type="button"
className="goToSkillHub"
onClick={() => {
const url = buildSkillHubUrl();
if (url) window.open(url, "_blank");
}}
>
<img src={new URL("@/assets/images/dot.png", import.meta.url).href} />
SKILL
</div>
{/* 联系客服:顶栏定位浮层展示二维码 */}
{serviceQrCode ? (
<div className="layoutShell__topbarTool" ref={serviceToolRef}>
<button
type="button"
className={["layoutShell__topbarIconBtn", "flex", "items-center", "justify-center", topbarServiceQrOpen ? "layoutShell__topbarIconBtn_active" : ""].filter(Boolean).join(" ")}
aria-label={t("layout.topbar.contactServiceAria")}
aria-expanded={topbarServiceQrOpen}
onClick={(e) => {
e.stopPropagation();
setTopbarServiceQrOpen((v) => !v);
setLanguageMenuOpen(false);
setUserMenuOpen(false);
setFooterPopoverOpen(false);
}}
>
<img src={new URL("@/assets/images/ic_service_wx.png", import.meta.url).href} alt="" />
</button>
{topbarServiceQrOpen ? (
<div className="layoutShell__topbarServiceQrFloat" role="dialog" aria-label={t("layout.topbar.contactServiceAria")} onClick={(e) => e.stopPropagation()}>
<img className="layoutShell__topbarServiceQrImage" src={serviceQrCode} alt={t("contactService.qrAlt")} />
<p className="layoutShell__topbarServiceQrTip">{t("layout.topbar.scan_contact_service")}</p>
</div>
) : null}
</div>
) : null}
<img className="layoutShell__topbarSkillIcon" src={LAYOUT_NAV_ASSETS.topSkill} alt="" />
<span className="layoutShell__topbarSkillText">{t("layout.topbar.skill")}</span>
</button>
{/* 使用说明 */}
<div className="layoutShell__topbarTool">
<Tooltip content={t("layout.usageTutorial")}>
<button
type="button"
className="layoutShell__topbarIconBtn flex items-center justify-center"
className="layoutShell__topbarIconBtn"
onClick={() => {
void openLatestPrivacyPolicyLink(PRIVACY_POLICY_KEY_USAGE_TUTORIAL);
}}
aria-label={t("layout.usageTutorial")}
>
<img src={new URL("@/assets/images/ic_help.png", import.meta.url).href} />
<img src={LAYOUT_NAV_ASSETS.topHelp} alt="" />
</button>
</Tooltip>
</div>
@ -550,16 +545,40 @@ export function Layout({ children }: LayoutProps) {
}
>
<Tooltip content={t("layout.language.tooltip")} disabled={languageMenuOpen} {...(languageMenuOpen ? { popupVisible: false } : {})} style={{ zIndex: 1000 }}>
<button type="button" className="layoutShell__topbarIconBtn flex items-center justify-center" aria-label={t("layout.language.menuAria")} aria-expanded={languageMenuOpen} aria-haspopup="menu">
<img src={new URL("@/assets/images/ic_language.png", import.meta.url).href} />
<button type="button" className="layoutShell__topbarIconBtn" aria-label={t("layout.language.menuAria")} aria-expanded={languageMenuOpen} aria-haspopup="menu">
<img src={LAYOUT_NAV_ASSETS.topLanguage} alt="" />
</button>
</Tooltip>
</Dropdown>
</div>
{/* 联系客服:顶栏定位浮层展示二维码 */}
<div className="layoutShell__topbarTool" ref={serviceToolRef}>
<button
type="button"
className={["layoutShell__topbarIconBtn", topbarServiceQrOpen ? "layoutShell__topbarIconBtn_active" : ""].filter(Boolean).join(" ")}
aria-label={t("layout.topbar.contactServiceAria")}
aria-expanded={topbarServiceQrOpen}
onClick={(e) => {
e.stopPropagation();
if (!serviceQrCode) return;
setTopbarServiceQrOpen((v) => !v);
setLanguageMenuOpen(false);
setUserMenuOpen(false);
setFooterPopoverOpen(false);
}}
>
<img src={LAYOUT_NAV_ASSETS.topService} alt="" />
</button>
{topbarServiceQrOpen ? (
<div className="layoutShell__topbarServiceQrFloat" role="dialog" aria-label={t("layout.topbar.contactServiceAria")} onClick={(e) => e.stopPropagation()}>
<img className="layoutShell__topbarServiceQrImage" src={serviceQrCode} alt={t("contactService.qrAlt")} />
<p className="layoutShell__topbarServiceQrTip">{t("layout.topbar.scan_contact_service")}</p>
</div>
) : null}
</div>
</div>
{/* 用户积分 */}
<div className="layoutShell__userPoints" onClick={() => openPointsDetailDialog()}>
<img className="layoutShell__userPointsIcon" src={new URL("@/assets/images/ic_stars_active.png", import.meta.url).href} />
<div className="layoutShell__userPointsValue">{String(user?.allCoins ?? 0)}</div>
<div className="layoutShell__userPointsLabel">{t("layout.nav.points")}</div>
</div>
@ -660,82 +679,40 @@ export function Layout({ children }: LayoutProps) {
) : null}
</div>
) : (
<button type="button" className="layoutShell__loginBtn" aria-label={t("auth.login_or_register")} onClick={() => void openLoginDialog()}>
{t("auth.login_or_register")}
<button type="button" className="layoutShell__avatar layoutShell__avatar_guest" aria-label={t("auth.login_or_register")} onClick={() => void openLoginDialog()}>
<img className="layoutShell__avatarBg" src={new URL("@/assets/images/avatar.png", import.meta.url).href} alt="" />
</button>
)}
</div>
</header>
{aliceLiveClassOpen
? createPortal(
<AliceLiveClassDialogOverlay onClose={() => setAliceLiveClassOpen(false)} />,
document.body,
)
: null}
{appMacDownloadOpen
? createPortal(
<AppMacDownloadDialogOverlay
macIntelUrl={macIntelDownloadUrl}
macArmUrl={macArmDownloadUrl}
windowsUrl={windowsDownloadUrl}
onClose={() => setAppMacDownloadOpen(false)}
/>,
document.body,
)
: null}
<div className="layoutShell__body">
{/* 左侧栏菜单 */}
<aside className={["layoutShell__sidebar", footerPopoverOpen ? "layoutShell__sidebar_footerPopoverOpen" : ""].filter(Boolean).join(" ")} aria-label={t("layout.aria.sidebar")} onClick={dismissMainDetailModals}>
<button type="button" className="layoutShell__brand" aria-label={t("layout.brand.aria")} onClick={() => navigate(ROUTES.INDEX)}>
<img className="layoutShell__brandLogo" src={LAYOUT_NAV_ASSETS.logo} alt="" />
</button>
<nav className="layoutShell__sideNav" aria-label={t("layout.aria.sideNav")}>
{LAYOUT_SIDE_NAV_ITEMS.map((item) => {
const isActive = pathname === item.to || (item.to !== "/" && pathname.startsWith(`${item.to}/`));
const className = ["layoutShell__sideItem", isActive ? "layoutShell__sideItem_active" : ""].filter(Boolean).join(" ");
return (
<Link key={item.id} to={item.to} className={className} title={effectiveSidebarCollapsed ? t(item.labelKey) : undefined} onClick={(evt) => handleLayoutNavClick(evt, item.to)}>
<img className="layoutShell__sideIcon" src={item.iconSrc} />
<LayoutSideNavIcon item={item} />
<span className="layoutShell__sideText">{t(item.labelKey)}</span>
{item.badge ? <span className="layoutShell__sideBadge">{item.badge}</span> : null}
{item.badgeKey ? <span className="layoutShell__sideBadge">{t(item.badgeKey)}</span> : null}
</Link>
);
})}
</nav>
<div className="layoutShell__aliceLiveClassTool">
<button
type="button"
className="layoutShell__aliceLiveClass"
aria-label={t("layout.sidebar.aliceLiveClass")}
onClick={() => {
setAliceLiveClassOpen(true);
}}
>
<img className="layoutShell__aliceLiveClassIcon" src={new URL("@/assets/images/layout/alice-live-class/meetup-avatar.png", import.meta.url).href} alt="" />
<span className="layoutShell__aliceLiveClassText">{t("layout.sidebar.aliceLiveClass")}</span>
<img className="layoutShell__aliceLiveClassArrow" src={new URL("@/assets/images/layout/alice-live-class/meetup-arrow.png", import.meta.url).href} alt="" />
</button>
</div>
<div className="layoutShell__footer" ref={footerRef}>
<div className="layoutShell__appDownload">
<button
type="button"
className="layoutShell__appDownloadTitle"
aria-label={t("layout.sidebar.appDownloadTitle")}
onClick={() => setAppMacDownloadOpen(true)}
>
<img className="layoutShell__appDownloadTitleIcon" src={LAYOUT_APP_DOWNLOAD_TITLE_ICON} alt="" />
<span className="layoutShell__appDownloadTitleText">{t("layout.sidebar.appDownloadTitle")}</span>
</button>
</div>
<div className="layoutShell__footer">
<nav className="layoutShell__footerNav" aria-label={t("layout.footer.navAria")}>
{LAYOUT_FOOTER_NAV_ITEMS.map((item) => {
const isActive = pathname === item.to || (item.to !== "/" && pathname.startsWith(`${item.to}/`));
const className = ["layoutShell__sideItem", isActive ? "layoutShell__sideItem_active" : ""].filter(Boolean).join(" ");
return (
<Link key={item.id} to={item.to} className={className} title={effectiveSidebarCollapsed ? t(item.labelKey) : undefined} onClick={(evt) => handleLayoutNavClick(evt, item.to)}>
<img className="layoutShell__sideIcon" src={item.iconSrc} />
<LayoutSideNavIcon item={item} />
<span className="layoutShell__sideText">{t(item.labelKey)}</span>
</Link>
);
@ -756,7 +733,7 @@ export function Layout({ children }: LayoutProps) {
aria-expanded={footerPopoverOpen}
aria-haspopup="dialog"
>
<img src={new URL("@/assets/images/ic_menu.png", import.meta.url).href} />
<img src={LAYOUT_NAV_ASSETS.more} alt="" />
</button>
{footerPopoverOpen ? (
<div className="layoutShell__footerPopoverPanel" role="dialog" aria-label={t("layout.footerPopover.menuAria")} onClick={(e) => e.stopPropagation()}>

6
src/locales/en-US.json

@ -11,8 +11,10 @@
"layout.aria.sidebar": "Sidebar",
"layout.aria.sideNav": "Sidebar navigation",
"layout.aria.content": "Content",
"layout.brand.aria": "Popi home",
"layout.sidebar.expand": "Expand sidebar",
"layout.sidebar.collapse": "Collapse sidebar",
"layout.sidebar.menuLabel": "Menu",
"layout.language.tooltip": "Switch language",
"layout.language.menuAria": "Switch language",
"layout.language.zhCN": "简体中文",
@ -81,9 +83,11 @@
"layout.nav.explore": "Explore",
"layout.nav.characters": "Characters",
"layout.nav.create": "Create",
"layout.nav.canvas": "Popi TV",
"layout.nav.canvas": "Popi.TV",
"layout.nav.canvasBadge": "BETA",
"layout.nav.voice": "Voice",
"layout.nav.points": "Points",
"layout.topbar.skill": "SKILL",
"layout.footer.subscribe": "Subscribe",
"layout.footer.profile": "Profile",
"layout.footer.navAria": "Footer navigation",

8
src/locales/zh-CN.json

@ -11,8 +11,10 @@
"layout.aria.sidebar": "左侧栏",
"layout.aria.sideNav": "侧边栏菜单",
"layout.aria.content": "内容区",
"layout.brand.aria": "Popi 首页",
"layout.sidebar.expand": "展开侧边栏",
"layout.sidebar.collapse": "收起侧边栏",
"layout.sidebar.menuLabel": "菜\n单",
"layout.language.tooltip": "切换语言",
"layout.language.menuAria": "切换语言",
"layout.language.zhCN": "简体中文",
@ -81,11 +83,13 @@
"layout.nav.explore": "探索",
"layout.nav.characters": "角色",
"layout.nav.create": "创造",
"layout.nav.canvas": "Popi TV",
"layout.nav.canvas": "Popi.TV",
"layout.nav.canvasBadge": "BETA",
"layout.nav.voice": "声音",
"layout.nav.points": "积分",
"layout.topbar.skill": "SKILL",
"layout.footer.subscribe": "订阅",
"layout.footer.profile": "个人主页",
"layout.footer.profile": "个人",
"layout.footer.navAria": "底部导航",
"layout.footerPopover.menuAria": "更多与协议",
"layout.footerPopover.icp": "沪ICP备2025151816号-1",

8
src/locales/zh-TW.json

@ -11,8 +11,10 @@
"layout.aria.sidebar": "側邊欄",
"layout.aria.sideNav": "側邊欄選單",
"layout.aria.content": "內容區",
"layout.brand.aria": "Popi 首頁",
"layout.sidebar.expand": "展開側邊欄",
"layout.sidebar.collapse": "收起側邊欄",
"layout.sidebar.menuLabel": "菜\n單",
"layout.language.tooltip": "切換語言",
"layout.language.menuAria": "切換語言",
"layout.language.zhCN": "简体中文",
@ -81,11 +83,13 @@
"layout.nav.explore": "探索",
"layout.nav.characters": "角色",
"layout.nav.create": "創造",
"layout.nav.canvas": "Popi TV",
"layout.nav.canvas": "Popi.TV",
"layout.nav.canvasBadge": "BETA",
"layout.nav.voice": "聲音",
"layout.nav.points": "積分",
"layout.topbar.skill": "SKILL",
"layout.footer.subscribe": "訂閱",
"layout.footer.profile": "個人主頁",
"layout.footer.profile": "個人",
"layout.footer.navAria": "底部導覽",
"layout.footerPopover.menuAria": "更多與協議",
"layout.footerPopover.icp": "滬ICP備2025151816號-1",

Loading…
Cancel
Save