Browse Source

feat/layout/edit (#12)

* feat: layout edit

* fix: dashboard card main color

* fix: dashboard main color
pull/13/head
PY 3 years ago
committed by GitHub
parent
commit
c83f594b5f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .eslintrc
  2. 6
      src/assets/assets-empty.svg
  3. 32
      src/assets/assets-result-403.svg
  4. 36
      src/assets/assets-result-404.svg
  5. 32
      src/assets/assets-result-500.svg
  6. 33
      src/assets/assets-result-ie.svg
  7. 23
      src/assets/assets-result-wifi.svg
  8. 14
      src/assets/assets-slide-dashboard.svg
  9. 6
      src/assets/assets-slide-detail.svg
  10. 4
      src/assets/assets-slide-form.svg
  11. 6
      src/assets/assets-slide-list.svg
  12. 3
      src/assets/assets-slide-logout.svg
  13. 26
      src/components/card/index.vue
  14. 34
      src/components/result/index.vue
  15. 7
      src/config/global.ts
  16. 7
      src/interface.ts
  17. 54
      src/layouts/components/Header.vue
  18. 74
      src/layouts/components/MenuContent.tsx
  19. 25
      src/layouts/components/Search.vue
  20. 6
      src/layouts/components/SideNav.tsx
  21. 78
      src/layouts/components/SubMenu.tsx
  22. 33
      src/layouts/index.tsx
  23. 6
      src/pages/dashboard/base/index.vue
  24. 12
      src/pages/login/components/components-login.vue
  25. 3
      src/pages/login/components/components-register.vue
  26. 13
      src/pages/login/helper.ts
  27. 44
      src/pages/login/index.less
  28. 4
      src/pages/login/index.vue
  29. 12
      src/pages/result/403/index.vue
  30. 11
      src/pages/result/404/index.vue
  31. 10
      src/pages/result/500/index.vue
  32. 12
      src/pages/result/browser-incompatible/index.vue
  33. 10
      src/pages/result/network-error/index.vue
  34. 73
      src/permisson.js
  35. 58
      src/router/index.js
  36. 26
      src/router/modules/base.ts
  37. 145
      src/router/modules/components.ts
  38. 34
      src/router/modules/others.ts
  39. 4
      src/store/index.ts
  40. 66
      src/store/modules/permission.ts
  41. 1
      src/store/modules/setting.ts
  42. 99
      src/store/modules/user.ts
  43. 11
      src/style/layout.less

1
.eslintrc

@ -44,6 +44,7 @@
"allowForLoopAfterthoughts": true
}
],
"no-param-reassign": 0,
"func-style": 0,
"prefer-default-export": 0,
"max-len": 0,

6
src/assets/assets-empty.svg

@ -0,0 +1,6 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.9997 10.6904L58.2484 25.8451V48.1545L31.9997 63.3092L5.75098 48.1545V25.8451L31.9997 10.6904ZM9.75098 30.4639V45.8451L31.9997 58.6904L54.2484 45.8451V30.4639L31.9997 43.3092L9.75098 30.4639ZM52.2484 26.9998L40.6599 33.6904L31.9997 28.6904L23.3394 33.6904L11.751 26.9998L31.9997 15.3092L52.2484 26.9998ZM27.3394 35.9998L31.9997 38.6904L36.6599 35.9998L31.9997 33.3092L27.3394 35.9998Z" fill="currentcolor" fill-opacity="0.26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30 8V0H34V8H30Z" fill="currentcolor" fill-opacity="0.26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M44.2676 10.7514L48.2676 3.82324L51.7317 5.82324L47.7317 12.7514L44.2676 10.7514Z" fill="currentcolor" fill-opacity="0.26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.2676 12.7514L12.2676 5.82324L15.7317 3.82324L19.7317 10.7514L16.2676 12.7514Z" fill="currentcolor" fill-opacity="0.26"/>
</svg>

32
src/assets/assets-result-403.svg

@ -0,0 +1,32 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_17_619)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
<g filter="url(#filter0_f_17_619)">
<rect x="12" y="84" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_17_619)">
<rect x="80" y="54" width="80" height="60" fill="#E3E6EB" />
</g>
<rect x="46" y="105" width="32" height="2" fill="white" />
<rect x="46" y="98" width="32" height="2" fill="white" />
<rect x="46" y="88" width="16" height="2" fill="white" />
</g>
<path opacity="0.9" d="M63 20H151V30H63V20Z" fill="currentcolor" />
<mask id="mask1_17_619" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="63" y="30" width="88" height="50">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
</mask>
<g mask="url(#mask1_17_619)">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_17_619)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
</g>
</g>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M95.6863 40.8577L105.964 51.1345C106.295 51.0466 106.642 50.9998 107 50.9998C109.213 50.9998 111 52.7865 111 54.9998C111 55.3574 110.953 55.7038 110.866 56.0333L121.142 66.3135L118.314 69.1419L113.716 64.5448C111.653 65.423 109.384 65.9089 107 65.9089C99.7273 65.9089 93.5164 61.3853 91 54.9998C92.1785 52.0093 94.1673 49.4271 96.6961 47.5268L92.8579 43.6861L95.6863 40.8577ZM99 54.9998C99 59.4158 102.584 62.9998 107 62.9998C108.483 62.9998 109.872 62.5957 111.063 61.8917L108.034 58.8657C107.704 58.9532 107.358 58.9998 107 58.9998C104.787 58.9998 103 57.2131 103 54.9998C103 54.6423 103.047 54.2958 103.134 53.9663L100.107 50.9389C99.4037 52.1295 99 53.5178 99 54.9998ZM107 44.0907C114.273 44.0907 120.484 48.6143 123 54.9998C122.071 57.3574 120.638 59.4612 118.834 61.1773L114.729 57.0717C114.906 56.4108 115 55.7162 115 54.9998C115 50.5838 111.416 46.9998 107 46.9998C106.284 46.9998 105.589 47.0941 104.928 47.2711L102.378 44.7205C103.848 44.3101 105.398 44.0907 107 44.0907Z"
fill="white" />
<rect x="68" y="24" width="2" height="2" fill="white" />
<rect x="74" y="24" width="2" height="2" fill="white" />
<rect x="80" y="24" width="66" height="2" fill="white" />
<path d="M157 53.9998L181.249 95.9998H132.751L157 53.9998Z" fill="white" stroke="black" />
<path d="M157 88.9998L157 70.9998" stroke="black" />
</svg>

36
src/assets/assets-result-404.svg

@ -0,0 +1,36 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_16559_24301)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
<g filter="url(#filter0_f_16559_24301)">
<rect x="12" y="84" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_16559_24301)">
<rect x="80" y="54" width="80" height="60" fill="#E3E6EB" />
</g>
<path d="M49 93L42 100L49 107" stroke="white" stroke-width="2" />
<path d="M69 107L76 100L69 93" stroke="white" stroke-width="2" />
<path d="M62.3647 87.4431L55.6355 112.557" stroke="white" stroke-width="2" />
</g>
<path opacity="0.9" d="M63 20H151V30H63V20Z" fill="currentcolor" />
<mask id="mask1_16559_24301" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="63" y="30" width="88" height="50">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
</mask>
<g mask="url(#mask1_16559_24301)">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_16559_24301)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
</g>
</g>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M105.25 41C112.015 41 117.5 46.4845 117.5 53.25C117.5 55.6827 116.791 57.9498 115.568 59.8558L121 65.2877L117.288 69L111.856 63.5681C109.95 64.7909 107.683 65.5 105.25 65.5C98.4845 65.5 93 60.0155 93 53.25C93 46.4845 98.4845 41 105.25 41ZM105.25 44.5C100.418 44.5 96.5 48.4175 96.5 53.25C96.5 58.0825 100.418 62 105.25 62C110.082 62 114 58.0825 114 53.25C114 48.4175 110.082 44.5 105.25 44.5Z"
fill="white" />
<rect x="68" y="24" width="2" height="2" fill="white" />
<rect x="74" y="24" width="2" height="2" fill="white" />
<rect x="80" y="24" width="66" height="2" fill="white" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M153 56C140.85 56 131 65.8497 131 78C131 82.6039 132.414 86.8776 134.832 90.4102L127 98.5L139.495 95.3681C143.222 98.2709 147.909 100 153 100C165.15 100 175 90.1503 175 78C175 65.8497 165.15 56 153 56Z"
fill="white" />
<path
d="M131 78L131.5 78V78L131 78ZM134.832 90.4102L135.191 90.758L135.475 90.4647L135.245 90.1278L134.832 90.4102ZM127 98.5L126.641 98.1522L125.422 99.411L127.122 98.985L127 98.5ZM139.495 95.3681L139.802 94.9736L139.61 94.8238L139.373 94.8831L139.495 95.3681ZM153 100L153 100.5L153 100.5L153 100ZM175 78L174.5 78L174.5 78L175 78ZM131.5 78C131.5 66.1259 141.126 56.5 153 56.5V55.5C140.574 55.5 130.5 65.5736 130.5 78L131.5 78ZM135.245 90.1278C132.882 86.6757 131.5 82.5 131.5 78H130.5C130.5 82.7079 131.946 87.0794 134.419 90.6926L135.245 90.1278ZM134.473 90.0624L126.641 98.1522L127.359 98.8478L135.191 90.758L134.473 90.0624ZM127.122 98.985L139.616 95.8531L139.373 94.8831L126.878 98.015L127.122 98.985ZM153 99.5C148.024 99.5 143.445 97.8105 139.802 94.9736L139.187 95.7626C143 98.7314 147.794 100.5 153 100.5V99.5ZM174.5 78C174.5 89.8741 164.874 99.5 153 99.5L153 100.5C165.426 100.5 175.5 90.4264 175.5 78L174.5 78ZM153 56.5C164.874 56.5 174.5 66.1259 174.5 78H175.5C175.5 65.5736 165.426 55.5 153 55.5V56.5Z"
fill="black" />
</svg>

32
src/assets/assets-result-500.svg

@ -0,0 +1,32 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_17_619)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
<g filter="url(#filter0_f_17_619)">
<rect x="12" y="84" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_17_619)">
<rect x="80" y="54" width="80" height="60" fill="#E3E6EB" />
</g>
<rect x="46" y="105" width="32" height="2" fill="white" />
<rect x="46" y="98" width="32" height="2" fill="white" />
<rect x="46" y="88" width="16" height="2" fill="white" />
</g>
<path opacity="0.9" d="M63 20H151V30H63V20Z" fill="currentcolor" />
<mask id="mask1_17_619" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="63" y="30" width="88" height="50">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
</mask>
<g mask="url(#mask1_17_619)">
<path d="M63 30H151V80H63V30Z" fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_17_619)">
<path d="M30 62H118V122H30V62Z" fill="#97A3B7" />
</g>
</g>
<path fill-rule="evenodd" clip-rule="evenodd"
d="M95.6863 40.8577L105.964 51.1345C106.295 51.0466 106.642 50.9998 107 50.9998C109.213 50.9998 111 52.7865 111 54.9998C111 55.3574 110.953 55.7038 110.866 56.0333L121.142 66.3135L118.314 69.1419L113.716 64.5448C111.653 65.423 109.384 65.9089 107 65.9089C99.7273 65.9089 93.5164 61.3853 91 54.9998C92.1785 52.0093 94.1673 49.4271 96.6961 47.5268L92.8579 43.6861L95.6863 40.8577ZM99 54.9998C99 59.4158 102.584 62.9998 107 62.9998C108.483 62.9998 109.872 62.5957 111.063 61.8917L108.034 58.8657C107.704 58.9532 107.358 58.9998 107 58.9998C104.787 58.9998 103 57.2131 103 54.9998C103 54.6423 103.047 54.2958 103.134 53.9663L100.107 50.9389C99.4037 52.1295 99 53.5178 99 54.9998ZM107 44.0907C114.273 44.0907 120.484 48.6143 123 54.9998C122.071 57.3574 120.638 59.4612 118.834 61.1773L114.729 57.0717C114.906 56.4108 115 55.7162 115 54.9998C115 50.5838 111.416 46.9998 107 46.9998C106.284 46.9998 105.589 47.0941 104.928 47.2711L102.378 44.7205C103.848 44.3101 105.398 44.0907 107 44.0907Z"
fill="white" />
<rect x="68" y="24" width="2" height="2" fill="white" />
<rect x="74" y="24" width="2" height="2" fill="white" />
<rect x="80" y="24" width="66" height="2" fill="white" />
<path d="M157 53.9998L181.249 95.9998H132.751L157 53.9998Z" fill="white" stroke="black" />
<path d="M157 88.9998L157 70.9998" stroke="black" />
</svg>

33
src/assets/assets-result-ie.svg

@ -0,0 +1,33 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_22_990)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M144.569 105.61L96.5692 133.322L48.5693 105.61V83.7121L96.569 55.9995L144.569 83.7122V105.61Z"
fill="#97A3B7" />
<g filter="url(#filter0_f_22_990)">
<rect x="-3" y="33.9995" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_22_990)">
<rect x="97" y="97.9995" width="80" height="60" fill="#E3E6EB" />
</g>
</g>
<mask id="mask1_22_990" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="53" y="16" width="86" height="69">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M113.357 42.715L129.829 33.2059C128.859 32.4995 127.789 31.823 126.643 31.1615C121.268 28.0584 114.723 26.0153 107.758 25.023C103.549 19.4606 97.5775 16.1248 90.4344 16.1945C83.6788 16.2821 74.9482 21.9412 68.9271 30.5602C68.096 30.975 67.2847 31.4099 66.4949 31.8659C52.1168 40.1664 49.5542 52.6155 59.0218 61.9309C57.9871 56.1259 58.712 51.0657 62.1231 45.7161C62.0653 46.3482 61.9127 50.143 61.8906 50.7834C61.2209 69.6969 76.9107 84.8409 88.03 84.7113C96.4806 84.6119 103.595 79.6976 108.349 72.0797C114.563 70.8487 120.438 68.786 125.443 65.8968C138.919 58.1167 142.01 46.7146 134.547 37.629L117.948 47.2113C119.71 50.8655 117.997 55.034 112.87 57.9936C107.744 60.9532 100.523 61.9424 94.1928 60.9252C91.3499 60.4563 88.6706 59.5834 86.4524 58.3029L86.4042 58.275L113.357 42.715ZM78.6546 53.7727C72.5285 49.7965 72.9717 43.5469 79.8498 39.5762C86.7276 35.6056 97.5532 35.3496 104.441 38.8864L78.6546 53.7727ZM93.5561 18.1703C98.1657 18.1302 102.284 20.5752 105.496 24.7401C97.0486 23.8219 88.1122 24.4143 79.9732 26.5054C83.672 21.3809 88.444 18.2301 93.5561 18.1703ZM91.3238 81.6169C85.471 81.685 80.3525 77.691 76.9473 71.2848C85.7921 73.6267 95.8719 74.0599 105.374 72.6022C101.618 78.1222 96.6601 81.5531 91.3238 81.6169Z"
fill="currentcolor" />
</mask>
<g mask="url(#mask1_22_990)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M113.357 42.715L129.829 33.2059C128.859 32.4995 127.789 31.823 126.643 31.1615C121.268 28.0584 114.723 26.0153 107.758 25.023C103.549 19.4606 97.5775 16.1248 90.4344 16.1945C83.6788 16.2821 74.9482 21.9412 68.9271 30.5602C68.096 30.975 67.2847 31.4099 66.4949 31.8659C52.1168 40.1664 49.5542 52.6155 59.0218 61.9309C57.9871 56.1259 58.712 51.0657 62.1231 45.7161C62.0653 46.3482 61.9127 50.143 61.8906 50.7834C61.2209 69.6969 76.9107 84.8409 88.03 84.7113C96.4806 84.6119 103.595 79.6976 108.349 72.0797C114.563 70.8487 120.438 68.786 125.443 65.8968C138.919 58.1167 142.01 46.7146 134.547 37.629L117.948 47.2113C119.71 50.8655 117.997 55.034 112.87 57.9936C107.744 60.9532 100.523 61.9424 94.1928 60.9252C91.3499 60.4563 88.6706 59.5834 86.4524 58.3029L86.4042 58.275L113.357 42.715ZM78.6546 53.7727C72.5285 49.7965 72.9717 43.5469 79.8498 39.5762C86.7276 35.6056 97.5532 35.3496 104.441 38.8864L78.6546 53.7727ZM93.5561 18.1703C98.1657 18.1302 102.284 20.5752 105.496 24.7401C97.0486 23.8219 88.1122 24.4143 79.9732 26.5054C83.672 21.3809 88.444 18.2301 93.5561 18.1703ZM91.3238 81.6169C85.471 81.685 80.3525 77.691 76.9473 71.2848C85.7921 73.6267 95.8719 74.0599 105.374 72.6022C101.618 78.1222 96.6601 81.5531 91.3238 81.6169Z"
fill="currentcolor" />
<g opacity="0.3" filter="url(#filter2_f_22_990)">
<path d="M96.569 55.9995L144.569 83.7122V139.138L96.569 166.85L48.5692 139.138V83.7122L96.569 55.9995Z"
fill="#97A3B7" />
</g>
</g>
<ellipse cx="155" cy="78" rx="22" ry="22" transform="rotate(180 155 78)" fill="white" stroke="black" />
<path d="M155 83L155 65" stroke="black" />
<rect x="155" y="87" width="0.00390625" height="0.00390625" fill="#C4C4C4" stroke="black" stroke-width="2"
stroke-linejoin="round" />
<path d="M96.5693 112L96.5693 87.9995" stroke="white" stroke-width="2" />
<path d="M86.5693 97.9995L96.5693 87.9995L106.569 97.9995" stroke="white" stroke-width="2" />
</svg>

23
src/assets/assets-result-wifi.svg

@ -0,0 +1,23 @@
<svg width="200" height="140" viewBox="0 0 200 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<g mask="url(#mask0_21_716)">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M33 46.843L96.3214 119L159.643 46.843C142.742 31.9998 120.583 23 96.3214 23C72.0601 23 49.9009 31.9998 33 46.843Z"
fill="#97A3B7" />
<g filter="url(#filter0_f_21_716)">
<rect x="95" y="21" width="80" height="60" fill="#E3E6EB" />
</g>
<g filter="url(#filter1_f_21_716)">
<rect x="-7" y="43" width="80" height="60" fill="#E3E6EB" />
</g>
</g>
<path
d="M72.8122 63.6882L69.6548 66.8455L75.9009 73.0916C71.2469 75.1648 66.9663 77.925 63.188 81.2433L96.3213 119L108.234 105.425L114.647 111.837L117.804 108.68L80.4504 71.3261C80.4505 71.3261 80.4503 71.3261 80.4504 71.3261L72.8122 63.6882Z"
fill="currentcolor" />
<path
d="M129.455 81.2433L114.137 98.6982L85.3974 69.9585C88.9142 69.1786 92.5697 68.7674 96.3213 68.7674C109.016 68.7674 120.611 73.4766 129.455 81.2433Z"
fill="currentcolor" />
<path
d="M152 21.822L156.867 38.875L157 39.3405L157.469 39.2228L174.671 34.911L162.336 47.6522L161.999 48L162.336 48.3478L174.671 61.089L157.469 56.7772L157 56.6595L156.867 57.125L152 74.178L147.133 57.125L147 56.6595L146.531 56.7772L129.329 61.089L141.664 48.3478L142.001 48L141.664 47.6522L129.329 34.911L146.531 39.2228L147 39.3405L147.133 38.875L152 21.822Z"
fill="white" stroke="black" />
<path d="M101 31L90 42L101 53L93 61" stroke="white" stroke-width="2" />
</svg>

14
src/assets/assets-slide-dashboard.svg

@ -0,0 +1,14 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M2.5 3.75C2.5 3.05965 3.05962 2.5 3.75 2.5H8.125C8.81538 2.5 9.375 3.05965 9.375 3.75V10C9.375 10.6903 8.81538 11.25 8.125 11.25H3.75C3.05962 11.25 2.5 10.6903 2.5 10V3.75ZM3.75 3.75H8.125V10H3.75V3.75Z"
fill="currentcolor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M10.625 10C10.625 9.30965 11.1846 8.75 11.875 8.75H16.25C16.9404 8.75 17.5 9.30965 17.5 10V16.25C17.5 16.9403 16.9404 17.5 16.25 17.5H11.875C11.1846 17.5 10.625 16.9403 10.625 16.25V10ZM11.875 10H16.25V16.25H11.875V10Z"
fill="currentcolor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M3.75 12.5C3.05962 12.5 2.5 13.0597 2.5 13.75V16.25C2.5 16.9403 3.05962 17.5 3.75 17.5H8.125C8.81538 17.5 9.375 16.9403 9.375 16.25V13.75C9.375 13.0597 8.81538 12.5 8.125 12.5H3.75ZM8.125 13.75H3.75V16.25H8.125V13.75Z"
fill="currentcolor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M10.625 3.75C10.625 3.05965 11.1846 2.5 11.875 2.5H16.25C16.9404 2.5 17.5 3.05965 17.5 3.75V6.25C17.5 6.94035 16.9404 7.5 16.25 7.5H11.875C11.1846 7.5 10.625 6.94035 10.625 6.25V3.75ZM11.875 3.75H16.25V6.25H11.875V3.75Z"
fill="currentcolor" />
</svg>

6
src/assets/assets-slide-detail.svg

@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 5.625H5V6.875H12.5V5.625Z" fill="currentcolor"/>
<path d="M5 8.75H12.5V10H5V8.75Z" fill="currentcolor"/>
<path d="M10 11.875H5V13.125H10V11.875Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 3.75C2.5 3.05962 3.05962 2.5 3.75 2.5H16.25C16.9404 2.5 17.5 3.05962 17.5 3.75V16.25C17.5 16.9404 16.9404 17.5 16.25 17.5H3.75C3.05962 17.5 2.5 16.9404 2.5 16.25V3.75ZM3.75 3.75H16.25V16.25H3.75V3.75Z" fill="currentcolor"/>
</svg>

4
src/assets/assets-slide-form.svg

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1485 2.60504L7.96107 8.79252C7.88191 8.87167 7.82547 8.97063 7.79764 9.07905L6.98445 12.2468C6.92951 12.4608 6.99164 12.6879 7.14788 12.8442C7.30411 13.0004 7.53121 13.0625 7.74522 13.0076L10.913 12.1944C11.0214 12.1666 11.1204 12.1101 11.1995 12.031L17.387 5.8435C18.2813 4.94923 18.2813 3.49931 17.387 2.60504C16.4927 1.71076 15.0428 1.71076 14.1485 2.60504ZM8.45793 11.5341L8.96586 9.55549L15.0324 3.48892C15.4386 3.08279 16.097 3.0828 16.5031 3.48892C16.9093 3.89504 16.9093 4.5535 16.5031 4.95962L10.4366 11.0262L8.45793 11.5341Z" fill="currentcolor"/>
<path d="M3.75 4.37493H10V3.12493H3.75C3.05964 3.12493 2.5 3.68457 2.5 4.37493V15.6249C2.5 16.3153 3.05964 16.8749 3.75 16.8749H16.25C16.9404 16.8749 17.5 16.3153 17.5 15.6249V9.37493H16.25V15.6249H3.75V4.37493Z" fill="currentcolor"/>
</svg>

6
src/assets/assets-slide-list.svg

@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 5.3125H10V6.5625H5V5.3125Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 3.75C2.5 3.05965 3.05962 2.5 3.75 2.5H16.25C16.9404 2.5 17.5 3.05965 17.5 3.75V8.125C17.5 8.81535 16.9404 9.375 16.25 9.375H3.75C3.05962 9.375 2.5 8.81535 2.5 8.125V3.75ZM3.75 3.75H16.25V8.125H3.75V3.75Z" fill="currentcolor"/>
<path d="M10 13.4375H5V14.6875H10V13.4375Z" fill="currentcolor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 11.875C2.5 11.1847 3.05962 10.625 3.75 10.625H16.25C16.9404 10.625 17.5 11.1847 17.5 11.875V16.25C17.5 16.9403 16.9404 17.5 16.25 17.5H3.75C3.05962 17.5 2.5 16.9403 2.5 16.25V11.875ZM16.25 11.875V16.25H3.75V11.875H16.25Z" fill="currentcolor"/>
</svg>

3
src/assets/assets-slide-logout.svg

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5 10C17.5 14.1421 14.1421 17.5 10 17.5C5.85786 17.5 2.5 14.1421 2.5 10C2.5 5.85786 5.85786 2.5 10 2.5C14.1421 2.5 17.5 5.85786 17.5 10ZM18.75 10C18.75 14.8325 14.8325 18.75 10 18.75C5.16751 18.75 1.25 14.8325 1.25 10C1.25 5.16751 5.16751 1.25 10 1.25C14.8325 1.25 18.75 5.16751 18.75 10ZM12.5489 9.37547L10.0934 6.91989L10.9772 6.03601L14.4998 9.55853C14.7438 9.80261 14.7438 10.1983 14.4998 10.4424L10.9804 13.9618L10.0965 13.0779L12.5489 10.6255L5.45213 10.6255L5.45214 9.37545L12.5489 9.37547Z" fill="currentcolor"/>
</svg>

26
src/components/card/index.vue

@ -65,33 +65,9 @@ export default Vue.extend({
},
});
</script>
<style lang="less" scoped>
<style lang="less">
@import '@/style/variables';
.main-color {
background: @brand-color;
color: @text-color-primary;
.card-subtitle {
color: @text-color-anti;
}
.dashboard-item-top span {
color: @text-color-anti;
}
.dashboard-item-block {
color: @text-color-anti;
opacity: .6;
}
.dashboard-item-bottom {
color: @text-color-anti;
}
}
.card {
&-option {
display: flex;
align-items: center;

34
src/components/result/index.vue

@ -1,6 +1,8 @@
<template>
<div class="result-container">
<img class="result-bg-img" :src="bgUrl" />
<div class="result-bg-img">
<component :is="dynamicComponent"></component>
</div>
<div class="result-title">{{ title }}</div>
<div class="result-tip">{{ tip }}</div>
<slot />
@ -9,6 +11,12 @@
<script lang="ts">
import Vue from 'vue';
import Result403Icon from '@/assets/assets-result-403.svg';
import Result404Icon from '@/assets/assets-result-404.svg';
import Result500Icon from '@/assets/assets-result-500.svg';
import ResultIeIcon from '@/assets/assets-result-ie.svg';
import ResultWifiIcon from '@/assets/assets-result-wifi.svg';
export default Vue.extend({
name: 'Result',
props: {
@ -24,6 +32,28 @@ export default Vue.extend({
type: String,
default: '',
},
type: {
type: String,
default: '',
},
},
computed: {
dynamicComponent() {
switch (this.type) {
case '403':
return Result403Icon;
case '404':
return Result404Icon;
case '500':
return Result500Icon;
case 'ie':
return ResultIeIcon;
case 'wifi':
return ResultWifiIcon;
default:
return Result403Icon;
}
},
},
});
</script>
@ -31,7 +61,6 @@ export default Vue.extend({
@import '@/style/variables';
.result {
&-link {
color: @brand-color;
text-decoration: none;
@ -66,6 +95,7 @@ export default Vue.extend({
&-bg-img {
width: 200px;
color: @brand-color;
}
&-title {

7
src/config/global.ts

@ -1,9 +1,4 @@
export const prefix = 'tdesign-starter';
export const theme = 'light';
export const TOKEN_NAME = 'tdesign-starter';
export const authenticationMethod = 'customize';
export default {
prefix,
theme,
authenticationMethod,
};

7
src/interface.ts

@ -8,7 +8,12 @@ export interface ResDataType {
export interface MenuRoute {
path: string;
title?: string;
icon?: string;
icon?:
| string
| {
render: () => void;
};
redirect?: string;
children: MenuRoute[];
meta: any;
}

54
src/layouts/components/Header.vue

@ -2,18 +2,17 @@
<div :class="layoutCls">
<t-head-menu :class="menuCls" :theme="theme" expandType="popup" :value="active">
<template #logo>
<span class="header-logo-container" @click="goHome" v-if="showLogo">
<span v-if="showLogo" class="header-logo-container" @click="handleNav('/dashboard/base')">
<tLogoFull class="t-logo" />
</span>
<div v-else class="header-operate-left">
<t-button theme="default" shape="square" variant="text" @click="changeCollapsed">
<t-icon v-show="isSidebarCompact" class="collapsed-icon" name="menu-fold" />
<t-icon v-show="!isSidebarCompact" class="collapsed-icon" name="menu-unfold" />
<t-icon class="collapsed-icon" name="view-list" />
</t-button>
<search :layout="layout" />
</div>
</template>
<sub-menu v-show="layout !== 'side'" class="header-menu" :navData="menu" />
<menu-content v-show="layout !== 'side'" class="header-menu" :navData="menu" />
<div slot="operations" class="operations-container">
<!-- 搜索框 -->
<search v-if="layout !== 'side'" :layout="layout" />
@ -24,7 +23,7 @@
<t-dropdown-item class="operations-dropdown-container-item" @click="handleNav('/user/index')">
<t-icon name="user-circle"></t-icon>
</t-dropdown-item>
<t-dropdown-item class="operations-dropdown-container-item" @click="handleNav('/login/index')">
<t-dropdown-item class="operations-dropdown-container-item" @click="handleLogout">
<t-icon name="poweroff"></t-icon>退
</t-dropdown-item>
</t-dropdown-menu>
@ -70,11 +69,11 @@ import tLogoFull from '@/assets/assets-logo-full.svg';
import Notice from './Notice.vue';
import Search from './Search.vue';
import SubMenu from './SubMenu';
import MenuContent from './MenuContent';
export default Vue.extend({
components: {
SubMenu,
MenuContent,
tLogoFull,
Notice,
Search,
@ -113,9 +112,6 @@ export default Vue.extend({
};
},
computed: {
isSidebarCompact() {
return this.$store.state.setting.isSidebarCompact;
},
active() {
if (!this.$route.path) {
return '';
@ -147,8 +143,8 @@ export default Vue.extend({
toggleSettingPanel() {
this.$store.commit('setting/toggleSettingPanel', true);
},
goHome() {
this.$router.push('/');
handleLogout() {
this.$router.push(`/login?redirect=${this.$router.history.current.fullPath}`);
},
changeCollapsed() {
this.$store.commit('setting/toggleSidebarCompact');
@ -196,14 +192,6 @@ export default Vue.extend({
display: inline-flex;
height: 64px;
}
.t-logo {
width: 32px;
&:hover {
cursor: pointer;
}
}
}
.header-menu {
@ -224,7 +212,6 @@ export default Vue.extend({
.t-button {
margin: 0 8px;
&.header-user-btn {
margin: 0;
}
@ -232,7 +219,6 @@ export default Vue.extend({
.t-icon {
font-size: 20px;
&.general {
margin-right: 16px;
}
@ -250,8 +236,17 @@ export default Vue.extend({
}
.header-logo-container {
width: 166px;
display: flex;
margin-left: 16px;
margin-left: 24px;
.t-logo {
width: 100%;
height: 100%;
&:hover {
cursor: pointer;
}
}
&:hover {
cursor: pointer;
@ -262,27 +257,30 @@ export default Vue.extend({
display: inline-flex;
align-items: center;
color: @text-color-primary;
.t-icon {
margin-left: 4px;
font-size: 16px;
}
}
.t-head-menu__inner {
border-bottom: 1px solid @border-level-1-color;
}
.t-menu--light {
.header-user-account {
color: @text-color-primary;
}
}
.t-menu--dark {
.t-head-menu__inner {
border-bottom: 1px solid var(--td-gray-color-10);
}
.header-user-account {
color: rgba(255, 255, 255, 0.55);
}
.t-button {
--ripple-color: var(--td-gray-color-10) !important;
&:hover {
background: var(--td-gray-color-12) !important;
}
@ -303,7 +301,6 @@ export default Vue.extend({
display: flex;
justify-content: center;
}
.t-dropdown__item__content__text {
display: flex;
align-items: center;
@ -315,7 +312,6 @@ export default Vue.extend({
width: 100%;
margin-bottom: 0px;
}
&:last-child {
.t-dropdown__item {
margin-bottom: 8px;

74
src/layouts/components/MenuContent.tsx

@ -0,0 +1,74 @@
import { prefix } from '@/config/global';
import { MenuRoute } from '@/interface';
const getMenuList = (list: Array<MenuRoute>, basePath?: string): Array<any> => {
if (!list) {
return [];
}
return list.map((item) => {
const path = basePath ? `${basePath}/${item.path}` : item.path;
return {
path,
title: item.meta?.title,
icon: item.meta?.icon || '',
children: getMenuList(item.children, path),
meta: item.meta,
redirect: item.redirect,
};
});
};
export default {
props: {
navData: Array,
},
data() {
return {
prefix,
};
},
computed: {
list(): Array<MenuRoute> {
return getMenuList(this.navData);
},
},
methods: {
renderIcon(item) {
if (typeof item.icon === 'string') {
return () => item.icon && <t-icon name={item.icon}></t-icon>;
}
if (item.icon && typeof item.icon.render === 'function') {
return () =>
this.$createElement(item.icon, {
class: 't-icon',
});
}
return () => '';
},
renderNav(list: Array<MenuRoute>) {
return list.map((item) => {
if (!item.children || !item.children.length || item.meta?.single) {
return (
<t-menu-item
name={item.path}
value={item.meta?.single ? item.redirect : item.path}
to={item.path}
icon={this.renderIcon(item)}
>
{item.title}
</t-menu-item>
);
}
return (
<t-submenu name={item.path} value={item.path} title={item.title} icon={this.renderIcon(item)}>
{item.children && this.renderNav(item.children)}
</t-submenu>
);
});
},
},
render() {
return <div>{this.renderNav(this.list)}</div>;
},
};

25
src/layouts/components/Search.vue

@ -12,7 +12,7 @@
</t-input>
</div>
<div v-else>
<div v-else class="header-menu-search-left">
<t-button
:class="{ 'search-icon-hide': isSearchFocus }"
theme="default"
@ -55,7 +55,7 @@ export default Vue.extend({
methods: {
changeSearchFocus(value: boolean) {
if (!value) {
this.searchData.value = '';
this.searchData = '';
}
this.isSearchFocus = value;
},
@ -68,24 +68,24 @@ export default Vue.extend({
.header-menu-search {
display: flex;
margin-left: 16px;
.hover-active {
.t-input__inner {
background: @bg-color-secondarycontainer;
}
.t-icon {
color: @brand-color !important;
}
}
.t-icon {
font-size: 20px !important;
color: @text-color-primary !important;
}
.t-input__inner {
border: 0;
outline: 0;
box-shadow: 0;
border: none;
outline: none;
box-shadow: none;
transform: background @anim-duration-base linear;
&:hover {
background: @bg-color-secondarycontainer;
}
@ -95,27 +95,26 @@ export default Vue.extend({
.header-search {
width: 200px;
transition: width @anim-duration-base @anim-time-fn-easing;
.t-input__inner {
border: 0;
padding-left: 40px;
&:focus {
box-shadow: none;
}
}
&.width-zero {
width: 0;
opacity: 0;
}
}
.t-button {
transition: opacity @anim-duration-base @anim-time-fn-easing;
}
.search-icon-hide {
opacity: 0;
}
.header-menu-search-left {
display: flex;
align-items: center;
}
</style>

6
src/layouts/components/SideNav.tsx

@ -1,7 +1,7 @@
import Vue from 'vue';
import { prefix } from '@/config/global';
import SubMenu from './SubMenu';
import MenuContent from './MenuContent';
import pgk from '../../../package.json';
import tLogo from '@/assets/assets-t-logo.svg';
import tLogoFull from '@/assets/assets-logo-full.svg';
@ -11,7 +11,7 @@ const MIN_POINT = 992 - 1;
export default Vue.extend({
name: 'sideNav',
components: {
SubMenu,
MenuContent,
tLogo,
tLogoFull,
},
@ -116,7 +116,7 @@ export default Vue.extend({
)}
</span>
)}
<sub-menu navData={this.menu}></sub-menu>
<menu-content navData={this.menu}></menu-content>
<span slot="operations" class="version-container" onClick={this.changeCollapsed}>
{!this.collapsed && 'TDesign Starter'} {pgk.version}
</span>

78
src/layouts/components/SubMenu.tsx

@ -1,78 +0,0 @@
import { prefix } from '@/config/global';
import { MenuRoute } from '@/interface';
const getMenuList = (list: Array<MenuRoute>, basePath?: string): Array<any> => {
if (!list) {
return [];
}
return list.map((item) => {
const path = basePath ? `${basePath}/${item.path}` : item.path;
return {
path,
title: item.title,
icon: item.icon || '',
children: getMenuList(item.children, path),
meta: item.meta || {},
};
});
};
const filterHiddenList = (routeItem: MenuRoute): boolean => !routeItem.meta || !routeItem.meta.hiddenInMenu;
const getChildrenType = (item: MenuRoute): string => {
if (!item.children || item.children.length === 0) {
return 'none';
}
if (item.children.length === 1 && !filterHiddenList(item.children[0])) {
return 'ghost';
}
return 'normal';
};
export default {
name: 'proSubMenu',
props: {
navData: Array,
},
data() {
return {
prefix,
};
},
computed: {
list(): Array<MenuRoute> {
return getMenuList(this.navData);
},
},
methods: {
renderNav(list: Array<MenuRoute>, deep = 0, maxLevel = 2) {
return list.filter(filterHiddenList).map((item) => {
if (deep < maxLevel) {
const type = getChildrenType(item);
const path = type === 'ghost' ? item.children[0].path : item.path;
if (deep === 0 && type === 'normal') {
return (
<t-submenu name={item.path} value={item.path}>
{item.icon && <t-icon slot="icon" name={item.icon} />}
{item.title && <span slot="title"> {item.title} </span>}
{item.children && this.renderNav(item.children, deep + 1)}
</t-submenu>
);
}
return (
<t-menu-item name={item.path} value={path} to={path}>
{item.icon && <t-icon slot="icon" name={item.icon} />}
{item.title}
{item.children && this.renderNav(item.children, deep + 1)}
</t-menu-item>
);
}
return '';
});
},
},
render() {
return <div>{this.renderNav(this.list)}</div>;
},
};

33
src/layouts/index.tsx

@ -34,11 +34,12 @@ export default Vue.extend({
showHeader: 'setting/showHeader',
showHeaderLogo: 'setting/showHeaderLogo',
showSidebarLogo: 'setting/showSidebarLogo',
headerMenu: 'setting/headerMenu',
sideMenu: 'setting/sideMenu',
// headerMenu: 'setting/headerMenu',
// sideMenu: 'setting/sideMenu',
showAsideFooter: 'setting/showAsideFooter',
showMainFooter: 'setting/showMainFooter',
mode: 'setting/mode',
menuRouters: 'permission/routers',
}),
setting(): SettingType {
return this.$store.state.setting;
@ -50,6 +51,34 @@ export default Vue.extend({
},
];
},
headerMenu() {
const { layout, splitMenu } = this.$store.state.setting;
const { menuRouters } = this;
if (layout === 'mix') {
if (splitMenu) {
return menuRouters.map((menu) => ({
...menu,
children: [],
}));
}
return [];
}
return menuRouters;
},
sideMenu() {
const { layout, splitMenu } = this.$store.state.setting;
const { menuRouters } = this;
if (layout === 'mix' && splitMenu) {
let index;
for (index = 0; index < menuRouters.length; index++) {
const item = menuRouters[index];
if (item.children && item.children.length > 0) {
return item.children.map((menuRouter) => ({ ...menuRouter, path: `${item.path}/${menuRouter.path}` }));
}
}
}
return menuRouters;
},
},
methods: {
getNavTheme(mode: ModeType, layout: string, type: string): string {

6
src/pages/dashboard/base/index.vue

@ -136,7 +136,7 @@
</t-row>
<!-- 出入库概览 -->
<div class="overview-panel">
<div class="overview-panel row-container">
<t-row>
<t-col :xs="12" :xl="9">
<card title="出入库概览" describe="(件)">
@ -387,6 +387,7 @@ export default {
<style lang="less" scoped>
@import './index.less';
</style>
<style lang="less">
@import '@/style/variables.less';
@ -394,6 +395,9 @@ export default {
background: @brand-color;
color: @text-color-primary;
.card-subtitle {
color: @text-color-anti;
}
.card-describe {
color: @text-color-anti;
}

12
src/pages/login/components/components-login.vue

@ -80,7 +80,6 @@
<script lang="ts">
import Vue from 'vue';
import QrcodeVue from 'qrcode.vue';
import { passwordValidator } from '../helper';
const INITIAL_DATA = {
phone: '',
@ -93,7 +92,7 @@ const INITIAL_DATA = {
const FORM_RULES = {
phone: [{ required: true, message: '手机号必填', type: 'error' }],
account: [{ required: true, message: '账号必填', type: 'error' }],
password: [{ required: true, message: '密码必填', type: 'error' }, { validator: passwordValidator }],
password: [{ required: true, message: '密码必填', type: 'error' }],
verifyCode: [{ required: true, message: '验证码必填', type: 'error' }],
};
/** 高级详情 */
@ -120,15 +119,12 @@ export default Vue.extend({
this.type = val;
this.$refs.form.reset();
},
onSubmit({ validateResult }) {
async onSubmit({ validateResult }) {
if (validateResult === true) {
this.$store.commit('user/SET_USER_INFO', this.formData);
await this.$store.dispatch('user/login', this.formData);
this.$message.success('登录成功');
this.$router.push({
path: '/',
});
this.$router.replace('/').catch(() => '');
}
},
handleCounter() {

3
src/pages/login/components/components-register.vue

@ -71,7 +71,6 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { passwordValidator } from '../helper';
const INITIAL_DATA = {
phone: '',
@ -84,7 +83,7 @@ const INITIAL_DATA = {
const FORM_RULES = {
phone: [{ required: true, message: '手机号必填', type: 'error' }],
email: [{ required: true, email: true, message: '邮箱必填', type: 'error' }],
password: [{ required: true, message: '密码必填', type: 'error' }, { validator: passwordValidator }],
password: [{ required: true, message: '密码必填', type: 'error' }],
verifyCode: [{ required: true, message: '验证码必填', type: 'error' }],
};

13
src/pages/login/helper.ts

@ -1,13 +0,0 @@
export const passwordValidator = (val: string) => {
if (!/^[a-z0-9_]{1,20}$/.test(val)) {
return { result: false, message: '需要为1-20个英文或数字字符', type: 'error' };
}
if (val && val.indexOf('_') === -1) {
return { result: false, message: '需包含下划线_', type: 'warning' };
}
return { result: true };
};
export default {
passwordValidator,
};

44
src/pages/login/index.less

@ -1,24 +1,23 @@
@import '@/style/variables.less';
&.light {
.login-wrapper {
.light {
&.login-wrapper {
background-color: white;
background-image: url('@/assets/assets-login-bg-white.png');
}
}
&.dark {
.login-wrapper {
.dark {
&.login-wrapper {
background-color: @bg-color-page;
background-image: url('@/assets/assets-login-bg-black.png');
}
}
.login-wrapper {
width: 100%;
height: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-size: cover;
background-position: 50%;
position: relative;
@ -192,32 +191,3 @@
}
}
.login-header {
height: 64px;
padding: 0 24px;
display: flex;
justify-content: space-between;
align-items: center;
backdrop-filter: blur(5px);
color: @text-color-primary;
.operations-container {
display: flex;
align-items: center;
.t-button {
margin-left: 16px;
}
.icon {
height: 20px;
width: 20px;
padding: 6px;
box-sizing: content-box;
&:hover {
cursor: pointer;
}
}
}
}

4
src/pages/login/index.vue

@ -16,6 +16,8 @@
<login v-if="type === 'login'" />
<register v-else @register-success="switchType('login')" />
<tdesign-setting />
</div>
</div>
</template>
@ -23,6 +25,7 @@
import Login from './components/components-login.vue';
import Register from './components/components-register.vue';
import LoginHeader from './components/components-header.vue';
import TdesignSetting from '@/layouts/setting.vue';
/** 高级详情 */
export default {
@ -31,6 +34,7 @@ export default {
LoginHeader,
Login,
Register,
TdesignSetting,
},
data() {
return {

12
src/pages/result/403/index.vue

@ -1,16 +1,10 @@
<template>
<result
title="403 Forbidden"
tip="抱歉,您无权限访问此页面,企业微信联系创建者xiaolaoshi。"
bgUrl="https://tdesign.gtimg.com/starter/result-page/403.png"
>
<div>
<t-button @click="() => this.$router.push('/')">返回首页</t-button>
</div>
<result title="403 Forbidden" tip="抱歉,您无权限访问此页面,企业微信联系创建者xiaolaoshi">
<t-button @click="() => $router.push('/')">返回首页</t-button>
</result>
</template>
<script lang="ts">
<script>
import result from '@/components/result/index.vue';
export default {

11
src/pages/result/404/index.vue

@ -1,13 +1,6 @@
<template>
<result
pageHeader="404"
title="404 Not Found"
tip="抱歉,您访问的页面不存在。"
bgUrl="https://tdesign.gtimg.com/starter/result-page/404.png"
>
<div>
<t-button @click="() => this.$router.push('/')">返回首页</t-button>
</div>
<result title="404 Not Found" tip="抱歉,您访问的页面不存在" type="404">
<t-button @click="() => $router.push('/')">返回首页</t-button>
</result>
</template>

10
src/pages/result/500/index.vue

@ -1,12 +1,6 @@
<template>
<result
title="500 Internal Server Error"
tip="抱歉,服务器出错啦!"
bgUrl="https://tdesign.gtimg.com/starter/result-page/500.png"
>
<div>
<t-button @click="() => this.$router.push('/')">返回首页</t-button>
</div>
<result title="500 Internal Server Error" type="500" tip="抱歉,服务器出错啦">
<t-button @click="() => $router.push('/')">返回首页</t-button>
</result>
</template>

12
src/pages/result/browser-incompatible/index.vue

@ -1,11 +1,7 @@
<template>
<result
title="浏览器版本低"
tip="抱歉,您正在使用的浏览器版本过低,无法打开当前网页。"
bgUrl="https://tdesign.gtimg.com/starter/result-page/browser-incompatible.png"
>
<result title="浏览器版本低" tip="抱歉,您正在使用的浏览器版本过低,无法打开当前网页。" type="ie">
<div class="result-slot-container">
<t-button class="result-button" @click="() => this.$router.push('/')">返回首页</t-button>
<t-button class="result-button" @click="() => $router.push('/')">返回首页</t-button>
<div class="recommend-container">
<div>TDesign Starter 推荐以下主流浏览器</div>
<div class="recommend-browser">
@ -53,8 +49,8 @@ export default {
top: 175px;
padding: 24px 48px;
width: 640px;
background: #ffffff;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
background: @bg-color-container;
box-shadow: 0px 1px 2px @shadow-1;
border-radius: 3px;
}

10
src/pages/result/network-error/index.vue

@ -1,12 +1,8 @@
<template>
<result
title="网络异常"
tip="当前网络异常,请稍后再试!"
bgUrl="https://tdesign.gtimg.com/starter/result-page/network-error.png"
>
<result title="网络异常" tip="网络异常,请稍后再试" type="wifi">
<div>
<t-button @click="() => this.$router.push('/')" theme="default">返回首页</t-button>
<t-button @click="() => this.$router.push('/')">重新加载</t-button>
<t-button theme="default" @click="() => $router.push('/')">返回首页</t-button>
<t-button @click="() => $router.push('/')">重新加载</t-button>
</div>
</result>
</template>

73
src/permisson.js

@ -1,57 +1,54 @@
import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css'; // progress bar style
import { authenticationMethod } from '@/config/global';
import router from './router';
import store from './store';
NProgress.configure({ showSpinner: false }); // NProgress 配置
import store from '@/store';
import router from '@/router';
const { dispatch, state } = store;
const { user } = state;
NProgress.configure({ showSpinner: false });
const whiteListRouters = store.getters['permission/whiteListRouters'];
router.beforeEach(async (to, from, next) => {
// TODO: 登录页面交互优化后,开启登录逻辑
next();
return;
// start progress bar
// eslint-disable-next-line no-unreachable
NProgress.start();
// 如果不需要登录,那么直接跳过
const notNeedLogin = to.matched.some((route) => route.meta && route.meta.needLogin === false);
if (notNeedLogin) {
next();
return;
}
const token = store.getters['user/token'];
if (token) {
if (to.path === '/login') {
setTimeout(() => {
store.dispatch('user/logout');
store.dispatch('permission/restore');
});
next();
return;
}
const roles = store.getters['user/roles'];
if (authenticationMethod === 'smartProxy') {
// 当登录方式为内网登录,则走智能网关进行OA登录鉴权,并获取用户信息
if (user.loginName === '') {
// 没有用户信息的话需要重新拉取用户信息,
if (roles && roles.length > 0) {
next();
} else {
try {
await dispatch('user/getUserInfo');
NProgress.done();
await store.dispatch('user/getUserInfo');
await store.dispatch('permission/initRoutes', store.getters['user/roles']);
next({ ...to });
} catch (error) {
console.log(`获取用户信息错误:${error.message}`);
// Message.error({ message: `获取用户信息错误:${error.message}`, closeBtn: true });
await store.commit('user/removeToken');
next(`/login?redirect=${to.path}`);
NProgress.done();
return;
}
}
} else if (authenticationMethod === 'customize') {
console.log('自定义');
// 当登录方式选择自定义登录时,若无登录,则重定向跳转到登录页面
if (user.loginName === '') {
console.log('重定向到登录页面');
// 没有用户信息,重定向跳转到登录页面
next({ path: '/login/index' });
return;
} else {
/* white list router */
if (whiteListRouters.indexOf(to.path) !== -1) {
next();
} else {
next(`/login?redirect=${to.path}`);
}
NProgress.done();
}
/** * TODO 这里判断页面权限 ** */
// 权限没有问题,直接路由下一步
next();
});
router.afterEach(() => {

58
src/router/index.js

@ -1,41 +1,39 @@
import VueRouter from 'vue-router';
import routeConfig from '@/config/routes.js';
const layoutModules = import.meta.glob('../layouts/*');
const pagesModules = import.meta.glob('../pages/**/*.vue');
const firstPagesModules = import.meta.glob('../pages/*.vue');
import baseRouters from './modules/base';
import componentsRouters from './modules/components';
import othersRouters from './modules/others';
const modules = { ...layoutModules, ...firstPagesModules, ...pagesModules };
// 存放动态路由
export const asyncRouterList = [...baseRouters, ...componentsRouters, ...othersRouters];
const getMenuRoutes = (list) => {
if (!list) {
return [];
}
return list.map((item) => {
const { path = '', component, meta = { title: item.title, ...item.meta }, redirect = '' } = item;
return {
path,
component: modules[component],
children: getMenuRoutes(item.children),
meta,
redirect,
};
});
};
const routes = [
...getMenuRoutes(routeConfig, true),
// 存放固定的路由
const defaultRouterList = [
{
path: '/login',
name: 'login',
component: () => import('@/pages/login/index.vue'),
},
{
path: '*',
redirect: '/dashboard/base',
},
...asyncRouterList,
];
const route = new VueRouter({
routes,
scrollBehavior() {
return { x: 0, y: 0 };
},
});
const createRouter = () =>
new VueRouter({
routes: defaultRouterList,
scrollBehavior() {
return { x: 0, y: 0 };
},
});
const router = createRouter();
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}
export default route;
export default router;

26
src/router/modules/base.ts

@ -0,0 +1,26 @@
import Layout from '@/layouts';
import DashboardIcon from '@/assets/assets-slide-dashboard.svg';
export default [
{
path: '/dashboard',
component: Layout,
redirect: '/dashboard/base',
name: 'dashboard',
meta: { title: '仪表盘', icon: DashboardIcon },
children: [
{
path: 'base',
name: 'dashboardBase',
component: () => import('@/pages/dashboard/base/index.vue'),
meta: { title: '概览仪表盘' },
},
{
path: 'detail',
name: 'dashboardDetail',
component: () => import('@/pages/dashboard/detail/index.vue'),
meta: { title: '统计报表' },
},
],
},
];

145
src/router/modules/components.ts

@ -0,0 +1,145 @@
import Layout from '@/layouts';
import ListIcon from '@/assets/assets-slide-list.svg';
import FormIcon from '@/assets/assets-slide-form.svg';
import DetailIcon from '@/assets/assets-slide-detail.svg';
export default [
{
path: '/list',
name: 'list',
component: Layout,
redirect: '/list/base',
meta: { title: '列表页', icon: ListIcon },
children: [
{
path: 'base',
name: 'listBase',
component: () => import('@/pages/list/base/index.vue'),
meta: { title: '基础列表页' },
},
{
path: 'card',
name: 'listCard',
component: () => import('@/pages/list/card/index.vue'),
meta: { title: '卡片列表页' },
},
{
path: 'filter',
name: 'listFilter',
component: () => import('@/pages/list/filter/index.vue'),
meta: { title: '筛选列表页' },
},
{
path: 'tree',
name: 'listTree',
component: () => import('@/pages/list/tree/index.vue'),
meta: { title: '树状筛选列表页' },
},
],
},
{
path: '/form',
name: 'form',
component: Layout,
redirect: '/form/base',
meta: { title: '表单页', icon: FormIcon },
children: [
{
path: 'base',
name: 'formBase',
component: () => import('@/pages/form/base/index.vue'),
meta: { title: '基础表单页' },
},
{
path: 'step',
name: 'formStep',
component: () => import('@/pages/form/step/index.vue'),
meta: { title: '分步表单页' },
},
],
},
{
path: '/detail',
name: 'detail',
component: Layout,
redirect: '/detail/base',
meta: { title: '详情页', icon: DetailIcon },
children: [
{
path: 'base',
name: 'detailBase',
component: () => import('@/pages/detail/base/index.vue'),
meta: { title: '基础详情页' },
},
{
path: 'advanced',
name: 'detailAdvanced',
component: () => import('@/pages/detail/advanced/index.vue'),
meta: { title: '多卡片详情页' },
},
{
path: 'deploy',
name: 'detailDeploy',
component: () => import('@/pages/detail/deploy/index.vue'),
meta: { title: '数据详情页' },
},
{
path: 'secondary',
name: 'detailSecondary',
component: () => import('@/pages/detail/secondary/index.vue'),
meta: { title: '二级详情页' },
},
],
},
{
path: '/result',
name: 'result',
component: Layout,
redirect: '/result/success',
meta: { title: '结果页', icon: 'check-circle' },
children: [
{
path: 'success',
name: 'resultSuccess',
component: () => import('@/pages/result/success/index.vue'),
meta: { title: '成功页' },
},
{
path: 'fail',
name: 'resultFail',
component: () => import('@/pages/result/fail/index.vue'),
meta: { title: '失败页' },
},
{
path: 'network-error',
name: 'warningNetworkError',
component: () => import('@/pages/result/network-error/index.vue'),
meta: { title: '网络异常' },
},
{
path: '403',
name: 'warning403',
component: () => import('@/pages/result/403/index.vue'),
meta: { title: '无权限' },
},
{
path: '404',
name: 'warning404',
component: () => import('@/pages/result/404/index.vue'),
meta: { title: '访问页面不存在页' },
},
{
path: '500',
name: 'warning500',
component: () => import('@/pages/result/500/index.vue'),
meta: { title: '服务器出错页' },
},
{
path: 'browser-incompatible',
name: 'warningBrowserIncompatible',
component: () => import('@/pages/result/browser-incompatible/index.vue'),
meta: { title: '浏览器不兼容页' },
},
],
},
];

34
src/router/modules/others.ts

@ -0,0 +1,34 @@
import Layout from '@/layouts';
import LogoutIcon from '@/assets/assets-slide-logout.svg';
export default [
{
path: '/user',
name: 'user',
component: Layout,
redirect: '/user/index',
meta: { title: '个人页', icon: 'user-circle' },
children: [
{
path: 'index',
name: 'userIndex',
component: () => import('@/pages/user/index.vue'),
meta: { title: '个人中心' },
},
],
},
{
path: '/loginRedirect',
name: 'loginRedirect',
meta: { title: '登录页', icon: LogoutIcon },
component: () => import('@/layouts/blank.vue'),
children: [
{
path: 'index',
redirect: '/login',
component: () => import('@/layouts/blank.vue'),
meta: { title: '登陆页' },
},
],
},
];

4
src/store/index.ts

@ -1,8 +1,9 @@
import Vue from 'vue';
import Vuex from 'vuex';
import user from './modules/user';
import setting from './modules/setting';
import notification from './modules/notification';
import setting from './modules/setting';
import permission from './modules/permission';
Vue.use(Vuex);
@ -12,6 +13,7 @@ const store = new Vuex.Store({
user,
setting,
notification,
permission,
},
});

66
src/store/modules/permission.ts

@ -0,0 +1,66 @@
import { resetRouter, asyncRouterList } from '@/router';
function filterPermissionsRouters(routes, roles) {
const res = [];
routes.forEach((route) => {
const children = [];
route.children?.forEach((childRouter) => {
const roleCode = childRouter.meta?.roleCode || childRouter.name;
if (roles.indexOf(roleCode) !== -1) {
children.push(childRouter);
}
});
if (children.length > 0) {
route.children = children;
res.push(route);
}
});
return res;
}
const state = {
whiteListRouters: ['/login'],
routers: [],
};
const mutations = {
setRouters: (state, routers) => {
state.routers = routers;
},
};
const getters = {
routers: (state) => state.routers,
whiteListRouters: (state) => state.whiteListRouters,
};
const actions = {
async initRoutes({ commit }, roles) {
let accessedRouters;
// special token
if (roles.includes('ALL_ROUTERS')) {
accessedRouters = asyncRouterList;
} else {
accessedRouters = filterPermissionsRouters(asyncRouterList, roles);
}
commit('setRouters', accessedRouters);
// register routers
// router.addRoutes(state.routers);
},
async restore({ commit }) {
// remove routers
resetRouter();
commit('setRouters', []);
},
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

1
src/store/modules/setting.ts

@ -111,7 +111,6 @@ const actions = {
dispatch('changeBrandTheme', payload);
commit('update', payload);
},
changeMode({ state }, payload) {
let theme = payload.mode;
if (payload.mode === 'auto') {

99
src/store/modules/user.ts

@ -1,34 +1,92 @@
import { TOKEN_NAME } from '@/config/global';
const InitUserInfo = {
roles: [],
};
// 定义的state初始值
const state = {
loginName: '',
deptNameString: '',
token: localStorage.getItem(TOKEN_NAME) || 'main_token', // 默认token不走权限
userInfo: InitUserInfo,
};
// 定义的state的初始值方法,传入state或者额外的方法,然后利用vuex的双向数据驱动进行值的改变
// 可通过this.$store.commit(' ')调用,但是触发的是同步事件
const mutations = {
SET_USER_INFO(state, userInfo) {
// eslint-disable-next-line no-param-reassign
state.loginName = userInfo.account;
// eslint-disable-next-line no-param-reassign
state.deptNameString = userInfo.DeptNameString;
setToken(state, token) {
localStorage.setItem(TOKEN_NAME, token);
state.token = token;
},
removeToken(state) {
localStorage.removeItem(TOKEN_NAME);
state.token = '';
},
setUserInfo(state, userInfo) {
state.userInfo = userInfo;
},
};
const getters = {
token: (state) => state.token,
roles: (state) => state.userInfo?.roles,
};
// 使用actions的好处在于不会触发同步时间,而是异步事件
// actions里面自定义的函数接收一个context参数和要变化的形参,context与store实例具有相同的方法和属性,所以它可以执行context.commit(' ')
const actions = {
// 获取用户信息
async getUserInfo() {
try {
console.log('当前编译环境');
console.log(process.env.NODE_ENV);
} catch (err) {
// 弹框提示错误信息
console.log(`智能网关获取用户信息错误:${err.message}`);
// Message.error({ message: `获取用户信息错误:${err.message}`, closeBtn: true });
async login({ commit }, userInfo) {
const mockLogin = async (userInfo) => {
// 登录请求流程
console.log(userInfo);
// const { account, password } = userInfo;
// if (account !== 'td') {
// return {
// code: 401,
// message: '账号不存在',
// };
// }
// if (['main_', 'dev_'].indexOf(password) === -1) {
// return {
// code: 401,
// message: '密码错误',
// };
// }
// const token = {
// main_: 'main_token',
// dev_: 'dev_token',
// }[password];
return {
code: 200,
message: '登陆成功',
data: 'main_token',
};
};
const res = await mockLogin(userInfo);
if (res.code === 200) {
commit('setToken', res.data);
} else {
throw res;
}
},
async getUserInfo({ commit, state }) {
const mockRemoteUserInfo = async (token) => {
if (token === 'main_token') {
return {
name: 'td_main',
roles: ['ALL_ROUTERS'],
};
}
return {
name: 'td_dev',
roles: ['userIndex', 'dashboardBase', 'login'],
};
};
const res = await mockRemoteUserInfo(state.token);
commit('setUserInfo', res);
},
async logout({ commit }) {
commit('removeToken');
commit('setUserInfo', InitUserInfo);
},
};
export default {
@ -36,4 +94,5 @@ export default {
state,
mutations,
actions,
getters,
};

11
src/style/layout.less

@ -135,12 +135,13 @@
.t-menu--dark .t-menu__operations .t-icon {
color: rgba(255, 255, 255, .55);
&:hover {
cursor: pointer;
}
}
.t-default-menu.t-menu--dark {
background: var(--td-gray-color-13);
}
.logo-container {
cursor: pointer;
@ -152,13 +153,11 @@
.version-container {
color: @text-color-anti;
opacity: .4;
opacity: 0.4;
}
.t-default-menu__inner .t-menu-group-title {
color: @text-color-anti;
opacity: .3;
opacity: 0.3;
};
Loading…
Cancel
Save