Commit ec9478f76fadbcd936d9d298b2623cebb1525ca5
1 parent
e3851dc5
refactor: refactor login page
Showing
33 changed files
with
1220 additions
and
401 deletions
.vscode/extensions.json
1 | { | 1 | { |
2 | "recommendations": [ | 2 | "recommendations": [ |
3 | - "johnsoncodehk.volar", | ||
4 | "octref.vetur", | 3 | "octref.vetur", |
5 | "dbaeumer.vscode-eslint", | 4 | "dbaeumer.vscode-eslint", |
6 | "stylelint.vscode-stylelint", | 5 | "stylelint.vscode-stylelint", |
7 | - "DavidAnson.vscode-markdownlint", | ||
8 | "esbenp.prettier-vscode", | 6 | "esbenp.prettier-vscode", |
9 | "mrmlnc.vscode-less", | 7 | "mrmlnc.vscode-less", |
10 | "antfu.i18n-ally", | 8 | "antfu.i18n-ally", |
11 | - "cpylua.language-postcss", | ||
12 | - "Orta.vscode-jest", | ||
13 | "antfu.iconify", | 9 | "antfu.iconify", |
14 | "mikestead.dotenv", | 10 | "mikestead.dotenv", |
15 | "bradlc.vscode-tailwindcss", | 11 | "bradlc.vscode-tailwindcss", |
16 | - "heybourn.headwind", | ||
17 | - "znck.vue-language-features" | 12 | + "heybourn.headwind" |
18 | ] | 13 | ] |
19 | } | 14 | } |
.vscode/i18n-ally-reviews.yml
0 → 100644
CHANGELOG.zh_CN.md
1 | ## Wip | 1 | ## Wip |
2 | 2 | ||
3 | +### ✨ Refactor | ||
4 | + | ||
5 | +- 登录页重构,新增注册页面/重置密码页面/手机登录/二维码登录 | ||
6 | + | ||
3 | ### ✨ Features | 7 | ### ✨ Features |
4 | 8 | ||
5 | - 新增 `settingButtonPosition`配置项,用于配置`设置`按钮位置 | 9 | - 新增 `settingButtonPosition`配置项,用于配置`设置`按钮位置 |
6 | - `modal`可以通过双击头部切换全屏 | 10 | - `modal`可以通过双击头部切换全屏 |
11 | +- 新增`CountDownInput`组件 | ||
7 | 12 | ||
8 | ### ⚡ Performance Improvements | 13 | ### ⚡ Performance Improvements |
9 | 14 |
build/config/themeConfig.ts
@@ -94,7 +94,7 @@ export function generateModifyVars() { | @@ -94,7 +94,7 @@ export function generateModifyVars() { | ||
94 | 'disabled-color': 'rgba(0, 0, 0, 0.25)', // Failure color | 94 | 'disabled-color': 'rgba(0, 0, 0, 0.25)', // Failure color |
95 | 'heading-color': 'rgba(0, 0, 0, 0.85)', // Title color | 95 | 'heading-color': 'rgba(0, 0, 0, 0.85)', // Title color |
96 | 'text-color': 'rgba(0, 0, 0, 0.85)', // Main text color | 96 | 'text-color': 'rgba(0, 0, 0, 0.85)', // Main text color |
97 | - 'text-color-secondary ': 'rgba(0, 0, 0, 0.45)', // Subtext color | 97 | + 'text-color-secondary': 'rgba(0, 0, 0, 0.45)', // Subtext color |
98 | 'font-size-base': '14px', // Main font size | 98 | 'font-size-base': '14px', // Main font size |
99 | 'box-shadow-base': '0 2px 8px rgba(0, 0, 0, 0.15)', // Floating shadow | 99 | 'box-shadow-base': '0 2px 8px rgba(0, 0, 0, 0.15)', // Floating shadow |
100 | 'border-color-base': '#d9d9d9', // Border color, | 100 | 'border-color-base': '#d9d9d9', // Border color, |
package.json
@@ -43,7 +43,7 @@ | @@ -43,7 +43,7 @@ | ||
43 | "vditor": "^3.8.1", | 43 | "vditor": "^3.8.1", |
44 | "vue": "^3.0.5", | 44 | "vue": "^3.0.5", |
45 | "vue-i18n": "9.0.0-rc.2", | 45 | "vue-i18n": "9.0.0-rc.2", |
46 | - "vue-router": "^4.0.3", | 46 | + "vue-router": "^4.0.4", |
47 | "vue-types": "^3.0.2", | 47 | "vue-types": "^3.0.2", |
48 | "vuex": "^4.0.0", | 48 | "vuex": "^4.0.0", |
49 | "vuex-module-decorators": "^1.0.1", | 49 | "vuex-module-decorators": "^1.0.1", |
@@ -92,7 +92,7 @@ | @@ -92,7 +92,7 @@ | ||
92 | "pretty-quick": "^3.1.0", | 92 | "pretty-quick": "^3.1.0", |
93 | "rimraf": "^3.0.2", | 93 | "rimraf": "^3.0.2", |
94 | "rollup-plugin-visualizer": "^4.2.0", | 94 | "rollup-plugin-visualizer": "^4.2.0", |
95 | - "stylelint": "^13.10.0", | 95 | + "stylelint": "^13.11.0", |
96 | "stylelint-config-prettier": "^8.0.2", | 96 | "stylelint-config-prettier": "^8.0.2", |
97 | "stylelint-config-standard": "^20.0.0", | 97 | "stylelint-config-standard": "^20.0.0", |
98 | "stylelint-order": "^4.1.0", | 98 | "stylelint-order": "^4.1.0", |
@@ -104,10 +104,10 @@ | @@ -104,10 +104,10 @@ | ||
104 | "vite-plugin-imagemin": "^0.2.6", | 104 | "vite-plugin-imagemin": "^0.2.6", |
105 | "vite-plugin-mock": "^2.1.4", | 105 | "vite-plugin-mock": "^2.1.4", |
106 | "vite-plugin-purge-icons": "^0.7.0", | 106 | "vite-plugin-purge-icons": "^0.7.0", |
107 | - "vite-plugin-pwa": "^0.5.1", | ||
108 | - "vite-plugin-style-import": "^0.7.2", | 107 | + "vite-plugin-pwa": "^0.5.2", |
108 | + "vite-plugin-style-import": "^0.7.3", | ||
109 | "vite-plugin-theme": "^0.4.3", | 109 | "vite-plugin-theme": "^0.4.3", |
110 | - "vite-plugin-windicss": "0.3.12", | 110 | + "vite-plugin-windicss": "0.4.3", |
111 | "vue-eslint-parser": "^7.5.0", | 111 | "vue-eslint-parser": "^7.5.0", |
112 | "yargs": "^16.2.0" | 112 | "yargs": "^16.2.0" |
113 | }, | 113 | }, |
src/assets/images/login/login-bg.png deleted
100644 → 0
180 KB
src/assets/images/login/login-in.png deleted
100644 → 0
91.5 KB
src/assets/svg/login-bg.svg
0 → 100644
1 | +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6395" height="1079" viewBox="0 0 6395 1079"> | ||
2 | + <defs> | ||
3 | + <clipPath id="clip-path"> | ||
4 | + <rect id="Rectangle_73" data-name="Rectangle 73" width="6395" height="1079" transform="translate(-5391)" fill="#fff"/> | ||
5 | + </clipPath> | ||
6 | + <linearGradient id="linear-gradient" x1="0.747" y1="0.222" x2="0.973" y2="0.807" gradientUnits="objectBoundingBox"> | ||
7 | + <stop offset="0" stop-color="#2b51b4"/> | ||
8 | + <stop offset="1" stop-color="#1c3faa"/> | ||
9 | + </linearGradient> | ||
10 | + </defs> | ||
11 | + <g id="Mask_Group_1" data-name="Mask Group 1" transform="translate(5391)" clip-path="url(#clip-path)"> | ||
12 | + <g id="Group_118" data-name="Group 118" transform="translate(-419.333 -1.126)"> | ||
13 | + <path id="Path_142" data-name="Path 142" d="M6271.734-6.176s-222.478,187.809-55.349,583.254c44.957,106.375,81.514,205.964,84.521,277,8.164,192.764-156.046,268.564-156.046,268.564l-653.53-26.8L5475.065-21.625Z" transform="translate(-4876.383 0)" fill="#f1f5f8"/> | ||
14 | + <path id="Union_6" data-name="Union 6" d="M-2631.1,1081.8v-1.6H-8230.9V.022H-2631.1V0H-1871.4s-187.845,197.448-91.626,488.844c49.167,148.9,96.309,256.289,104.683,362.118,7.979,100.852-57.98,201.711-168.644,254.286-65.858,31.29-144.552,42.382-223.028,42.383C-2441.2,1147.632-2631.1,1081.8-2631.1,1081.8Z" transform="translate(3259.524 0.803)" fill="url(#linear-gradient)"/> | ||
15 | + </g> | ||
16 | + </g> | ||
17 | +</svg> |
src/assets/svg/login-box-bg.svg
0 → 100644
1 | +<svg id="a622e68e-7a65-46e9-94a9-d455de519afc" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="971.44" height="502" viewBox="0 0 971.44 502"><defs><linearGradient id="341b0e5e-a21f-44db-b85f-76180f33f0d3" x1="599.5" y1="668.05" x2="599.5" y2="199" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient><linearGradient id="9c19d1ba-0c1d-4cca-8c15-e6f3831a5e67" x1="485.72" y1="258.88" x2="485.72" y2="71.12" xlink:href="#341b0e5e-a21f-44db-b85f-76180f33f0d3"/><linearGradient id="fe76f7c7-2126-4e48-920d-21143a22d340" x1="132" y1="515" x2="303" y2="515" xlink:href="#341b0e5e-a21f-44db-b85f-76180f33f0d3"/><linearGradient id="2cf89a04-5a05-413b-983a-d2bc296cbb5e" x1="933" y1="568.28" x2="1031" y2="568.28" xlink:href="#341b0e5e-a21f-44db-b85f-76180f33f0d3"/></defs><title>responsive</title><g opacity="0.7"><path d="M852.69,199H346.31A16.37,16.37,0,0,0,330,215.42V563.94a16.37,16.37,0,0,0,16.31,16.42H520.47v60.16h-7.94a8.3,8.3,0,0,0-8.27,8.33v12.07h16.21v7.14H678.53v-7.14h16.21V648.85a8.3,8.3,0,0,0-8.27-8.33H679V640h-.51V580.36H852.69A16.37,16.37,0,0,0,869,563.94V215.42A16.37,16.37,0,0,0,852.69,199Z" transform="translate(-114.28 -199)" fill="url(#341b0e5e-a21f-44db-b85f-76180f33f0d3)"/></g><rect x="407.72" y="371" width="156" height="92" fill="#bdbdbd"/><g opacity="0.1"><path d="M525.07,579H675.24c1.81-7.87,3.26-13,3.26-13h-157S523.11,571.11,525.07,579Z" transform="translate(-114.28 -199)"/></g><path d="M235.82,3h499.8a16.1,16.1,0,0,1,16.1,16.1V327a0,0,0,0,1,0,0h-532a0,0,0,0,1,0,0V19.1A16.1,16.1,0,0,1,235.82,3Z" fill="#535461"/><path d="M849.9,576H350.1A16.1,16.1,0,0,1,334,559.9V526H866v33.9A16.1,16.1,0,0,1,849.9,576Z" transform="translate(-114.28 -199)" fill="#bdbdbd"/><circle cx="485.72" cy="352" r="9" fill="#535461"/><path d="M399.89,436H571.55a8.17,8.17,0,0,1,8.17,8.17V456a0,0,0,0,1,0,0h-188a0,0,0,0,1,0,0V444.17A8.17,8.17,0,0,1,399.89,436Z" fill="#bdbdbd"/><g opacity="0.5"><rect x="320.72" y="71.12" width="330" height="187.76" rx="4.5" ry="4.5" fill="url(#9c19d1ba-0c1d-4cca-8c15-e6f3831a5e67)"/></g><rect x="324.95" y="72.5" width="321.54" height="183.96" rx="4.5" ry="4.5" fill="#fff"/><g opacity="0.5"><rect x="414.52" y="98.91" width="35.44" height="31.9" rx="4.5" ry="4.5" fill="#0960bd"/></g><rect x="460.59" y="98.91" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/><rect x="460.59" y="109.55" width="79.54" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/><g opacity="0.5"><rect x="414.52" y="148.53" width="35.44" height="31.9" rx="4.5" ry="4.5" fill="#0960bd"/></g><rect x="460.59" y="148.53" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/><rect x="460.59" y="159.16" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/><g opacity="0.5"><rect x="414.52" y="198.15" width="35.44" height="31.9" rx="4.5" ry="4.5" fill="#0960bd"/></g><rect x="460.59" y="198.15" width="95.69" height="3.54" rx="1.77" ry="1.77" fill="#e0e0e0"/><rect x="460.59" y="208.78" width="96.33" height="3.54" rx="1.59" ry="1.59" fill="#e0e0e0"/><line x1="485.72" y1="42" x2="485.72" y2="20" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><line x1="485.72" y1="79" x2="485.72" y2="50.13" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><circle cx="485.72" cy="79" r="4" fill="#0960bd"/><circle cx="485.72" cy="46" r="4" fill="none" stroke="#fff" stroke-miterlimit="10"/><line x1="485.72" y1="42" x2="485.72" y2="20" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><line x1="485.72" y1="79" x2="485.72" y2="50.13" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><circle cx="485.72" cy="79" r="4" fill="#0960bd"/><circle cx="485.72" cy="46" r="4" fill="none" stroke="#fff" stroke-miterlimit="10"/><line x1="485.72" y1="279" x2="485.72" y2="310" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><line x1="485.72" y1="251" x2="485.72" y2="279.87" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><circle cx="485.72" cy="251" r="4" fill="#0960bd"/><line x1="305.72" y1="168.5" x2="274.22" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><line x1="333.22" y1="168.5" x2="304.35" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><circle cx="333.22" cy="168.5" r="4" fill="#0960bd"/><g opacity="0.1"><rect x="408.22" y="435.5" width="156" height="3"/></g><g opacity="0.7"><path d="M293.48,566.06H221.08l1-8.14c20.46-18.37,33.69-67.31,33.69-67.31a6.78,6.78,0,0,0-.87.18c-12,2.42-20.54,7.35-26.51,13.28l2.54-21.66c37.8-8.14,52.79-58.14,52.79-58.14-24.12,5.35-39.16,13.63-48.5,21.49l3.72-31.82c25.56,8.77,52-37.82,52-37.82l-1-.21.5-.32-.76.27c-28.25-6.09-43.35,10.06-48.25,16.77l.37-3.12q-1.12,3-2.18,5.88h0l0,.08q-3,8.13-5.49,16.06l0,0h0q-2.17,6.77-4.06,13.4l0-.06s-1.17-28.46-31.18-35.95c0,0,3.15,62.07,26.93,51.91h0c-2.2,9-4,17.66-5.56,26.07h0q-1.49,8.21-2.6,16l-.14.16.14-.12-.06.41v0h0q-1,7.07-1.7,13.78c.46-8.62-1.11-33.52-30.45-56.92,0,0-39,68.54,27.5,82,.15.13.3.26.44.38l-.1-.31.6.13.27-3.52a369.39,369.39,0,0,0,.23,44.1h0c.07,1,.14,2,.21,2.95H141.37c-27.94,57.79,15.52,89.46,15.52,89.46h120C323.49,596.66,293.48,566.06,293.48,566.06Zm-78-65.68h0v0Z" transform="translate(-114.28 -199)" fill="url(#fe76f7c7-2126-4e48-920d-21143a22d340)"/></g><path d="M217,588s-19-83,23-190" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="3" opacity="0.6"/><path d="M143,563H290s29,37-16,92H158S116,617,143,563Z" transform="translate(-114.28 -199)" fill="#0960bd"/><path d="M237.89,403.5s14.61-26,49.61-18c0,0-28.93,49.26-55,33.13Z" transform="translate(-114.28 -199)" fill="#4db6ac"/><path d="M228.63,431.09S227.5,404.5,198.5,397.5c0,0,3,58,26,48.5Z" transform="translate(-114.28 -199)" fill="#4db6ac"/><path d="M219.15,470.36s5.35-27.86,61.35-39.86c0,0-17.86,57.62-63.93,55.31Z" transform="translate(-114.28 -199)" fill="#4db6ac"/><path d="M214.61,501.63s5.89-29.13-29.11-56.13c0,0-38,64.67,27.48,76.83Z" transform="translate(-114.28 -199)" fill="#4db6ac"/><path d="M213.56,541.67S209.5,500.5,253.5,492.5c0,0-16.07,57.49-40,67.74Z" transform="translate(-114.28 -199)" fill="#4db6ac"/><path d="M233,419s38-29,54-34" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/><path d="M216.5,485.5s46-49,64-55" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/><path d="M198.5,397.5s28,38,26,48" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/><path d="M185.5,445.5s15,68,27,77" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/><path d="M213.5,560.5s24-66,40-68" transform="translate(-114.28 -199)" fill="none" stroke="#535461" stroke-miterlimit="10" opacity="0.3"/><g opacity="0.1"><path d="M290,563H143c-.33.67-.65,1.34-1,2H285s28.29,36.11-14.4,90H274C319,600,290,563,290,563Z" transform="translate(-114.28 -199)"/></g><rect y="455.6" width="971.44" height="32.93" fill="#e0e0e0"/><rect x="41.16" y="488.53" width="889.11" height="13.47" fill="#e0e0e0"/><rect x="41.16" y="488.53" width="889.11" height="4.49" opacity="0.1"/><line x1="690.22" y1="168.5" x2="696.22" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><line x1="637.22" y1="168.5" x2="682.1" y2="168.5" stroke="#0960bd" stroke-miterlimit="10" stroke-width="2"/><circle cx="637.22" cy="168.5" r="4" fill="#0960bd"/><circle cx="686.22" cy="168.5" r="4" fill="none" stroke="#fff" stroke-miterlimit="10"/><g opacity="0.7"><path d="M1027,643.88l.1-.15q.31-.48.61-1l.11-.19q.29-.49.55-1l.09-.17c.2-.39.39-.78.56-1.19h0a23.79,23.79,0,0,0,.94-2.51l.1-.33c.09-.31.18-.62.26-.93l.1-.44q.1-.42.18-.85c0-.16.06-.32.09-.48s.09-.56.13-.85,0-.33.06-.49.06-.61.08-.92c0-.14,0-.29,0-.43,0-.45,0-.91,0-1.36V548h-13.85V507.52h-17V548H988.39V489.86h-17V548H965V481.55h-17V548H933V630.6c0,13.48,11.21,24.4,25,24.4H1006a25.19,25.19,0,0,0,20.24-10.06l0,0Q1026.61,644.41,1027,643.88Z" transform="translate(-114.28 -199)" fill="url(#2cf89a04-5a05-413b-983a-d2bc296cbb5e)"/></g><rect x="835.72" y="321" width="16" height="100" fill="#535461"/><rect x="835.72" y="288" width="16" height="33" fill="#3ad29f"/><rect x="857.72" y="329" width="16" height="100" fill="#535461"/><rect x="857.72" y="296" width="16" height="33" fill="#4d8af0"/><rect x="884.72" y="346" width="16" height="100" fill="#535461"/><rect x="884.72" y="313" width="16" height="33" fill="#f55f44"/><path d="M821.72,352h92a0,0,0,0,1,0,0v79.5a23.5,23.5,0,0,1-23.5,23.5h-45a23.5,23.5,0,0,1-23.5-23.5V352A0,0,0,0,1,821.72,352Z" fill="#0960bd"/><g opacity="0.1"><path d="M936,551v4h88v79.5a23.39,23.39,0,0,1-5,14.49,23.45,23.45,0,0,0,9-18.49V551Z" transform="translate(-114.28 -199)"/></g></svg> | ||
0 | \ No newline at end of file | 2 | \ No newline at end of file |
src/components/Application/src/AppLogo.vue
@@ -10,8 +10,13 @@ | @@ -10,8 +10,13 @@ | ||
10 | > | 10 | > |
11 | <img src="../../../assets/images/logo.png" /> | 11 | <img src="../../../assets/images/logo.png" /> |
12 | <div | 12 | <div |
13 | - class="ml-2 truncate xs:opacity-0 md:opacity-100" | ||
14 | - :class="`${prefixCls}__title`" | 13 | + class="ml-2 truncate md:opacity-100" |
14 | + :class="[ | ||
15 | + `${prefixCls}__title`, | ||
16 | + { | ||
17 | + 'xs:opacity-0': !alwaysShowTitle, | ||
18 | + }, | ||
19 | + ]" | ||
15 | v-show="showTitle" | 20 | v-show="showTitle" |
16 | > | 21 | > |
17 | {{ title }} | 22 | {{ title }} |
@@ -38,6 +43,7 @@ | @@ -38,6 +43,7 @@ | ||
38 | theme: propTypes.oneOf(['light', 'dark']), | 43 | theme: propTypes.oneOf(['light', 'dark']), |
39 | // Whether to show title | 44 | // Whether to show title |
40 | showTitle: propTypes.bool.def(true), | 45 | showTitle: propTypes.bool.def(true), |
46 | + alwaysShowTitle: propTypes.bool.def(false), | ||
41 | }, | 47 | }, |
42 | setup() { | 48 | setup() { |
43 | const { prefixCls } = useDesign('app-logo'); | 49 | const { prefixCls } = useDesign('app-logo'); |
src/components/CountDown/index.ts
0 → 100644
src/components/CountDown/src/CountButton.vue
0 → 100644
1 | +<template> | ||
2 | + <Button v-bind="$attrs" :disabled="isStart" @click="handleStart" :loading="loading"> | ||
3 | + {{ | ||
4 | + !isStart | ||
5 | + ? t('component.countdown.normalText') | ||
6 | + : t('component.countdown.sendText', [currentCount]) | ||
7 | + }} | ||
8 | + </Button> | ||
9 | +</template> | ||
10 | +<script lang="ts"> | ||
11 | + import { defineComponent, ref, PropType } from 'vue'; | ||
12 | + | ||
13 | + import { Button } from 'ant-design-vue'; | ||
14 | + | ||
15 | + import { useCountdown } from './useCountdown'; | ||
16 | + import { isFunction } from '/@/utils/is'; | ||
17 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
18 | + | ||
19 | + export default defineComponent({ | ||
20 | + name: 'CountButton', | ||
21 | + components: { Button }, | ||
22 | + props: { | ||
23 | + count: { | ||
24 | + type: Number, | ||
25 | + default: 60, | ||
26 | + }, | ||
27 | + beforeStartFunc: { | ||
28 | + type: Function as PropType<() => boolean>, | ||
29 | + default: null, | ||
30 | + }, | ||
31 | + }, | ||
32 | + setup(props) { | ||
33 | + const loading = ref(false); | ||
34 | + | ||
35 | + const { currentCount, isStart, start } = useCountdown(props.count); | ||
36 | + const { t } = useI18n(); | ||
37 | + /** | ||
38 | + * @description: Judge whether there is an external function before execution, and decide whether to start after execution | ||
39 | + */ | ||
40 | + async function handleStart() { | ||
41 | + const { beforeStartFunc } = props; | ||
42 | + if (beforeStartFunc && isFunction(beforeStartFunc)) { | ||
43 | + loading.value = true; | ||
44 | + try { | ||
45 | + const canStart = await beforeStartFunc(); | ||
46 | + canStart && start(); | ||
47 | + } finally { | ||
48 | + loading.value = false; | ||
49 | + } | ||
50 | + } else { | ||
51 | + start(); | ||
52 | + } | ||
53 | + } | ||
54 | + return { handleStart, isStart, currentCount, loading, t }; | ||
55 | + }, | ||
56 | + }); | ||
57 | +</script> |
src/components/CountDown/src/CountdownInput.vue
0 → 100644
1 | +<template> | ||
2 | + <div :class="prefixCls"> | ||
3 | + <AInput v-bind="$attrs" :size="size" v-model:value="state"> | ||
4 | + <template #addonAfter> | ||
5 | + <CountButton :size="size" :count="count" :beforeStartFunc="sendCodeApi" /> | ||
6 | + </template> | ||
7 | + </AInput> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | +<script lang="ts"> | ||
11 | + import { defineComponent, PropType } from 'vue'; | ||
12 | + | ||
13 | + import { Input } from 'ant-design-vue'; | ||
14 | + import CountButton from './CountButton.vue'; | ||
15 | + | ||
16 | + import { propTypes } from '/@/utils/propTypes'; | ||
17 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
18 | + | ||
19 | + import { useRuleFormItem } from '/@/hooks/component/useFormItem'; | ||
20 | + | ||
21 | + export default defineComponent({ | ||
22 | + name: 'CountDownInput', | ||
23 | + components: { [Input.name]: Input, CountButton }, | ||
24 | + props: { | ||
25 | + value: propTypes.string, | ||
26 | + size: propTypes.oneOf(['default', 'large', 'small']), | ||
27 | + count: propTypes.number.def(60), | ||
28 | + sendCodeApi: { | ||
29 | + type: Function as PropType<() => boolean>, | ||
30 | + default: null, | ||
31 | + }, | ||
32 | + }, | ||
33 | + setup(props) { | ||
34 | + const { prefixCls } = useDesign('countdown-input'); | ||
35 | + | ||
36 | + const [state] = useRuleFormItem(props); | ||
37 | + return { prefixCls, state }; | ||
38 | + }, | ||
39 | + }); | ||
40 | +</script> | ||
41 | +<style lang="less"> | ||
42 | + @prefix-cls: ~'@{namespace}-countdown-input'; | ||
43 | + | ||
44 | + .@{prefix-cls} { | ||
45 | + .ant-input-group-addon { | ||
46 | + padding-right: 0; | ||
47 | + background-color: transparent; | ||
48 | + border: none; | ||
49 | + | ||
50 | + button { | ||
51 | + font-size: 14px; | ||
52 | + } | ||
53 | + } | ||
54 | + } | ||
55 | +</style> |
src/components/CountDown/src/useCountdown.ts
0 → 100644
1 | +import { ref, unref } from 'vue'; | ||
2 | +import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; | ||
3 | + | ||
4 | +export function useCountdown(count: number) { | ||
5 | + const currentCount = ref(count); | ||
6 | + | ||
7 | + const isStart = ref(false); | ||
8 | + | ||
9 | + let timerId: ReturnType<typeof setInterval> | null; | ||
10 | + | ||
11 | + function clear() { | ||
12 | + timerId && window.clearInterval(timerId); | ||
13 | + } | ||
14 | + | ||
15 | + function stop() { | ||
16 | + isStart.value = false; | ||
17 | + timerId = null; | ||
18 | + clear(); | ||
19 | + } | ||
20 | + | ||
21 | + function start() { | ||
22 | + if (unref(isStart) || !!timerId) { | ||
23 | + return; | ||
24 | + } | ||
25 | + isStart.value = true; | ||
26 | + timerId = setInterval(() => { | ||
27 | + if (unref(currentCount) === 1) { | ||
28 | + stop(); | ||
29 | + currentCount.value = count; | ||
30 | + } else { | ||
31 | + currentCount.value -= 1; | ||
32 | + } | ||
33 | + }, 1000); | ||
34 | + } | ||
35 | + | ||
36 | + function reset() { | ||
37 | + currentCount.value = count; | ||
38 | + stop(); | ||
39 | + } | ||
40 | + | ||
41 | + function restart() { | ||
42 | + reset(); | ||
43 | + start(); | ||
44 | + } | ||
45 | + | ||
46 | + tryOnUnmounted(() => { | ||
47 | + reset(); | ||
48 | + }); | ||
49 | + | ||
50 | + return { start, reset, restart, clear, stop, currentCount, isStart }; | ||
51 | +} |
src/components/StrengthMeter/src/index.vue
1 | <template> | 1 | <template> |
2 | - <div :class="prefixCls"> | 2 | + <div :class="prefixCls" class="relative"> |
3 | <InputPassword | 3 | <InputPassword |
4 | v-if="showInput" | 4 | v-if="showInput" |
5 | v-bind="$attrs" | 5 | v-bind="$attrs" |
@@ -24,15 +24,14 @@ | @@ -24,15 +24,14 @@ | ||
24 | import { Input } from 'ant-design-vue'; | 24 | import { Input } from 'ant-design-vue'; |
25 | 25 | ||
26 | import zxcvbn from '@zxcvbn-ts/core'; | 26 | import zxcvbn from '@zxcvbn-ts/core'; |
27 | - import { propTypes } from '/@/utils/propTypes'; | ||
28 | import { useDesign } from '/@/hooks/web/useDesign'; | 27 | import { useDesign } from '/@/hooks/web/useDesign'; |
28 | + import { propTypes } from '/@/utils/propTypes'; | ||
29 | 29 | ||
30 | export default defineComponent({ | 30 | export default defineComponent({ |
31 | name: 'StrengthMeter', | 31 | name: 'StrengthMeter', |
32 | components: { InputPassword: Input.Password }, | 32 | components: { InputPassword: Input.Password }, |
33 | props: { | 33 | props: { |
34 | value: propTypes.string, | 34 | value: propTypes.string, |
35 | - | ||
36 | showInput: propTypes.bool.def(true), | 35 | showInput: propTypes.bool.def(true), |
37 | disabled: propTypes.bool, | 36 | disabled: propTypes.bool, |
38 | }, | 37 | }, |
@@ -43,9 +42,9 @@ | @@ -43,9 +42,9 @@ | ||
43 | 42 | ||
44 | const getPasswordStrength = computed(() => { | 43 | const getPasswordStrength = computed(() => { |
45 | const { disabled } = props; | 44 | const { disabled } = props; |
46 | - if (disabled) return null; | 45 | + if (disabled) return -1; |
47 | const innerValue = unref(innerValueRef); | 46 | const innerValue = unref(innerValueRef); |
48 | - const score = innerValue ? zxcvbn(unref(innerValueRef)).score : null; | 47 | + const score = innerValue ? zxcvbn(unref(innerValueRef)).score : -1; |
49 | emit('score-change', score); | 48 | emit('score-change', score); |
50 | return score; | 49 | return score; |
51 | }); | 50 | }); |
@@ -57,6 +56,7 @@ | @@ -57,6 +56,7 @@ | ||
57 | watchEffect(() => { | 56 | watchEffect(() => { |
58 | innerValueRef.value = props.value || ''; | 57 | innerValueRef.value = props.value || ''; |
59 | }); | 58 | }); |
59 | + | ||
60 | watch( | 60 | watch( |
61 | () => unref(innerValueRef), | 61 | () => unref(innerValueRef), |
62 | (val) => { | 62 | (val) => { |
@@ -77,14 +77,12 @@ | @@ -77,14 +77,12 @@ | ||
77 | @prefix-cls: ~'@{namespace}-strength-meter'; | 77 | @prefix-cls: ~'@{namespace}-strength-meter'; |
78 | 78 | ||
79 | .@{prefix-cls} { | 79 | .@{prefix-cls} { |
80 | - position: relative; | ||
81 | - | ||
82 | &-bar { | 80 | &-bar { |
83 | position: relative; | 81 | position: relative; |
84 | - height: 4px; | 82 | + height: 6px; |
85 | margin: 10px auto 6px; | 83 | margin: 10px auto 6px; |
86 | background: @disabled-color; | 84 | background: @disabled-color; |
87 | - border-radius: 3px; | 85 | + border-radius: 6px; |
88 | 86 | ||
89 | &::before, | 87 | &::before, |
90 | &::after { | 88 | &::after { |
src/design/ant/index.less
src/design/var/breakpoint.less
@@ -2,37 +2,17 @@ | @@ -2,37 +2,17 @@ | ||
2 | // ==============屏幕断点============ | 2 | // ==============屏幕断点============ |
3 | // ================================= | 3 | // ================================= |
4 | 4 | ||
5 | -// Extra small screen / phone | ||
6 | -@screen-xs: 480px; | ||
7 | -@screen-xs-min: @screen-xs; | ||
8 | - | ||
9 | // Small screen / tablet | 5 | // Small screen / tablet |
10 | -@screen-sm: 576px; | ||
11 | -@screen-sm-min: @screen-sm; | 6 | +@screen-sm: 640px; |
12 | 7 | ||
13 | // Medium screen / desktop | 8 | // Medium screen / desktop |
14 | @screen-md: 768px; | 9 | @screen-md: 768px; |
15 | -@screen-md-min: @screen-md; | ||
16 | 10 | ||
17 | // Large screen / wide desktop | 11 | // Large screen / wide desktop |
18 | -@screen-lg: 992px; | ||
19 | -@screen-lg-min: @screen-lg; | 12 | +@screen-lg: 1024px; |
20 | 13 | ||
21 | // Extra large screen / full hd | 14 | // Extra large screen / full hd |
22 | -@screen-xl: 1200px; | ||
23 | -@screen-xl-min: @screen-xl; | 15 | +@screen-xl: 1280px; |
24 | 16 | ||
25 | // Extra extra large screen / large desktop | 17 | // Extra extra large screen / large desktop |
26 | -@screen-xxl: 1600px; | ||
27 | -@screen-xxl-min: @screen-xxl; | ||
28 | - | ||
29 | -@screen-xxxl: 1900px; | ||
30 | -@screen-xxxl-min: @screen-xxxl; | ||
31 | - | ||
32 | -// provide a maximum | ||
33 | -@screen-xs-max: (@screen-sm-min - 1px); | ||
34 | -@screen-sm-max: (@screen-md-min - 1px); | ||
35 | -@screen-md-max: (@screen-lg-min - 1px); | ||
36 | -@screen-lg-max: (@screen-xl-min - 1px); | ||
37 | -@screen-xl-max: (@screen-xxl-min - 1px); | ||
38 | -@screen-xxl-max: (@screen-xxxl-min - 1px); | 18 | +@screen-2xl: 1536px; |
src/layouts/default/header/components/user-dropdown/index.vue
@@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
3 | <span :class="[prefixCls, `${prefixCls}--${theme}`]"> | 3 | <span :class="[prefixCls, `${prefixCls}--${theme}`]"> |
4 | <img :class="`${prefixCls}__header`" :src="headerImg" /> | 4 | <img :class="`${prefixCls}__header`" :src="headerImg" /> |
5 | <span :class="`${prefixCls}__info`"> | 5 | <span :class="`${prefixCls}__info`"> |
6 | - <span :class="`${prefixCls}__name anticon`">{{ getUserInfo.realName }}</span> | 6 | + <span :class="`${prefixCls}__name`" class="truncate">{{ getUserInfo.realName }}</span> |
7 | </span> | 7 | </span> |
8 | </span> | 8 | </span> |
9 | 9 |
src/locales/lang/en/component/countdown.ts
0 → 100644
src/locales/lang/en/sys/login.ts
1 | export default { | 1 | export default { |
2 | - loginButton: 'Login', | ||
3 | - autoLogin: 'AutoLogin', | ||
4 | - forgetPassword: 'Forget Password', | 2 | + backSignIn: 'Back sign in', |
3 | + mobileSignInFormTitle: 'Mobile sign in', | ||
4 | + qrSignInFormTitle: 'Qr code sign in', | ||
5 | + signInFormTitle: 'Sign in', | ||
6 | + signUpFormTitle: 'Sign up', | ||
7 | + forgetFormTitle: 'Reset password', | ||
8 | + | ||
9 | + signInTitle: 'Backstage management system', | ||
10 | + signInDesc: 'Enter your personal details and get started!', | ||
11 | + policy: 'I agree to the xxx Privacy Policy', | ||
12 | + scanSign: `scanning the code to complete the login`, | ||
13 | + | ||
14 | + loginButton: 'Sign in', | ||
15 | + registerButton: 'Sign up', | ||
16 | + rememberMe: 'Remember me', | ||
17 | + forgetPassword: 'Forget Password?', | ||
18 | + otherSignIn: 'Sign in with', | ||
5 | 19 | ||
6 | // notify | 20 | // notify |
7 | loginSuccessTitle: 'Login successful', | 21 | loginSuccessTitle: 'Login successful', |
8 | loginSuccessDesc: 'Welcome back', | 22 | loginSuccessDesc: 'Welcome back', |
9 | 23 | ||
10 | // placeholder | 24 | // placeholder |
11 | - accountPlaceholder: 'Please input Username', | ||
12 | - passwordPlaceholder: 'Please input Password', | 25 | + accountPlaceholder: 'Please input username', |
26 | + passwordPlaceholder: 'Please input password', | ||
27 | + smsPlaceholder: 'Please input sms code', | ||
28 | + mobilePlaceholder: 'Please input mobile', | ||
29 | + policyPlaceholder: 'Register after checking', | ||
30 | + diffPwd: 'The two passwords are inconsistent', | ||
31 | + | ||
32 | + userName: 'Username', | ||
33 | + password: 'Password', | ||
34 | + confirmPassword: 'Confirm Password', | ||
35 | + email: 'Email', | ||
36 | + smsCode: 'SMS code', | ||
37 | + mobile: 'Mobile', | ||
13 | }; | 38 | }; |
src/locales/lang/zh_CN/component/countdown.ts
0 → 100644
src/locales/lang/zh_CN/sys/login.ts
1 | export default { | 1 | export default { |
2 | + backSignIn: '返回', | ||
3 | + signInFormTitle: '登录', | ||
4 | + mobileSignInFormTitle: '手机登录', | ||
5 | + qrSignInFormTitle: '二维码登录', | ||
6 | + signUpFormTitle: '注册', | ||
7 | + forgetFormTitle: '重置密码', | ||
8 | + | ||
9 | + signInTitle: '开箱即用的中后台管理系统', | ||
10 | + signInDesc: '输入您的个人详细信息开始使用!', | ||
11 | + policy: '我同意xxx隐私政策', | ||
12 | + scanSign: `扫码后点击"确认",即可完成登录`, | ||
13 | + | ||
2 | loginButton: '登录', | 14 | loginButton: '登录', |
3 | - autoLogin: '自动登录', | ||
4 | - forgetPassword: '忘记密码', | 15 | + registerButton: '注册', |
16 | + rememberMe: '记住我', | ||
17 | + forgetPassword: '忘记密码?', | ||
18 | + otherSignIn: '其他登录方式', | ||
5 | 19 | ||
6 | // notify | 20 | // notify |
7 | loginSuccessTitle: '登录成功', | 21 | loginSuccessTitle: '登录成功', |
@@ -10,4 +24,15 @@ export default { | @@ -10,4 +24,15 @@ export default { | ||
10 | // placeholder | 24 | // placeholder |
11 | accountPlaceholder: '请输入账号', | 25 | accountPlaceholder: '请输入账号', |
12 | passwordPlaceholder: '请输入密码', | 26 | passwordPlaceholder: '请输入密码', |
27 | + smsPlaceholder: '请输入验证码', | ||
28 | + mobilePlaceholder: '请输入手机号码', | ||
29 | + policyPlaceholder: '勾选后才能注册', | ||
30 | + diffPwd: '两次输入密码不一致', | ||
31 | + | ||
32 | + userName: '账号', | ||
33 | + password: '密码', | ||
34 | + confirmPassword: '确认密码', | ||
35 | + email: '邮箱', | ||
36 | + smsCode: '短信验证码', | ||
37 | + mobile: '手机号码', | ||
13 | }; | 38 | }; |
src/views/sys/lock/LockPage.vue
1 | <template> | 1 | <template> |
2 | - <div :class="prefixCls"> | ||
3 | - <div :class="`${prefixCls}__unlock`" @click="handleShowForm(false)" v-show="showDate"> | 2 | + <div |
3 | + :class="prefixCls" | ||
4 | + class="fixed inset-0 flex h-screen w-screen bg-black items-center justify-center" | ||
5 | + > | ||
6 | + <div | ||
7 | + :class="`${prefixCls}__unlock`" | ||
8 | + class="absolute top-0 left-1/2 flex pt-5 h-16 items-center justify-center sm:text-md xl:text-xl text-white flex-col cursor-pointer transform translate-x-1/2" | ||
9 | + @click="handleShowForm(false)" | ||
10 | + v-show="showDate" | ||
11 | + > | ||
4 | <LockOutlined /> | 12 | <LockOutlined /> |
5 | <span>{{ t('sys.lock.unlock') }}</span> | 13 | <span>{{ t('sys.lock.unlock') }}</span> |
6 | </div> | 14 | </div> |
7 | 15 | ||
8 | - <div :class="`${prefixCls}__date`"> | ||
9 | - <div :class="`${prefixCls}__hour`"> | ||
10 | - {{ hour }} | ||
11 | - <span class="meridiem" v-show="showDate">{{ meridiem }}</span> | 16 | + <div class="flex w-screen h-screen justify-center items-center"> |
17 | + <div :class="`${prefixCls}__hour`" class="relative mr-5 md:mr-20 w-2/5 h-2/5 md:h-4/5"> | ||
18 | + <span>{{ hour }}</span> | ||
19 | + <span class="meridiem absolute left-5 top-5 text-md xl:text-xl" v-show="showDate"> | ||
20 | + {{ meridiem }} | ||
21 | + </span> | ||
12 | </div> | 22 | </div> |
13 | - <div :class="`${prefixCls}__minute`"> | ||
14 | - {{ minute }} | 23 | + <div :class="`${prefixCls}__minute w-2/5 h-2/5 md:h-4/5 `"> |
24 | + <span> {{ minute }}</span> | ||
15 | </div> | 25 | </div> |
16 | </div> | 26 | </div> |
17 | <transition name="fade-slide"> | 27 | <transition name="fade-slide"> |
18 | <div :class="`${prefixCls}-entry`" v-show="!showDate"> | 28 | <div :class="`${prefixCls}-entry`" v-show="!showDate"> |
19 | <div :class="`${prefixCls}-entry-content`"> | 29 | <div :class="`${prefixCls}-entry-content`"> |
20 | - <div :class="`${prefixCls}-entry__header`"> | 30 | + <div :class="`${prefixCls}-entry__header enter-x`"> |
21 | <img :src="headerImg" :class="`${prefixCls}-entry__header-img`" /> | 31 | <img :src="headerImg" :class="`${prefixCls}-entry__header-img`" /> |
22 | <p :class="`${prefixCls}-entry__header-name`"> | 32 | <p :class="`${prefixCls}-entry__header-name`"> |
23 | {{ realName }} | 33 | {{ realName }} |
24 | </p> | 34 | </p> |
25 | </div> | 35 | </div> |
26 | - <InputPassword :placeholder="t('sys.lock.placeholder')" v-model:value="password" /> | ||
27 | - <span :class="`${prefixCls}-entry__err-msg`" v-if="errMsgRef"> | 36 | + <InputPassword |
37 | + :placeholder="t('sys.lock.placeholder')" | ||
38 | + class="enter-x" | ||
39 | + v-model:value="password" | ||
40 | + /> | ||
41 | + <span :class="`${prefixCls}-entry__err-msg enter-x`" v-if="errMsgRef"> | ||
28 | {{ t('sys.lock.alert') }} | 42 | {{ t('sys.lock.alert') }} |
29 | </span> | 43 | </span> |
30 | - <div :class="`${prefixCls}-entry__footer`"> | 44 | + <div :class="`${prefixCls}-entry__footer enter-x`"> |
31 | <a-button | 45 | <a-button |
32 | type="link" | 46 | type="link" |
33 | size="small" | 47 | size="small" |
34 | - class="mt-2 mr-2" | 48 | + class="mt-2 mr-2 enter-x" |
35 | :disabled="loadingRef" | 49 | :disabled="loadingRef" |
36 | @click="handleShowForm(true)" | 50 | @click="handleShowForm(true)" |
37 | > | 51 | > |
@@ -40,7 +54,7 @@ | @@ -40,7 +54,7 @@ | ||
40 | <a-button | 54 | <a-button |
41 | type="link" | 55 | type="link" |
42 | size="small" | 56 | size="small" |
43 | - class="mt-2 mr-2" | 57 | + class="mt-2 mr-2 enter-x" |
44 | :disabled="loadingRef" | 58 | :disabled="loadingRef" |
45 | @click="goLogin" | 59 | @click="goLogin" |
46 | > | 60 | > |
@@ -54,11 +68,11 @@ | @@ -54,11 +68,11 @@ | ||
54 | </div> | 68 | </div> |
55 | </transition> | 69 | </transition> |
56 | 70 | ||
57 | - <div :class="`${prefixCls}__footer-date`"> | ||
58 | - <div class="time" v-show="!showDate"> | ||
59 | - {{ hour }}:{{ minute }} <span class="meridiem">{{ meridiem }}</span> | 71 | + <div class="absolute bottom-5 w-full text-gray-300 xl:text-xl 2xl:text-3xl text-center enter-y"> |
72 | + <div class="text-5xl mb-4 enter-x" v-show="!showDate"> | ||
73 | + {{ hour }}:{{ minute }} <span class="text-3xl">{{ meridiem }}</span> | ||
60 | </div> | 74 | </div> |
61 | - <div class="date"> {{ year }}/{{ month }}/{{ day }} {{ week }} </div> | 75 | + <div class="text-2xl"> {{ year }}/{{ month }}/{{ day }} {{ week }} </div> |
62 | </div> | 76 | </div> |
63 | </div> | 77 | </div> |
64 | </template> | 78 | </template> |
@@ -144,125 +158,54 @@ | @@ -144,125 +158,54 @@ | ||
144 | @prefix-cls: ~'@{namespace}-lock-page'; | 158 | @prefix-cls: ~'@{namespace}-lock-page'; |
145 | 159 | ||
146 | .@{prefix-cls} { | 160 | .@{prefix-cls} { |
147 | - position: fixed; | ||
148 | - top: 0; | ||
149 | - right: 0; | ||
150 | - bottom: 0; | ||
151 | - left: 0; | ||
152 | z-index: @lock-page-z-index; | 161 | z-index: @lock-page-z-index; |
153 | - display: flex; | ||
154 | - width: 100vw; | ||
155 | - height: 100vh; | ||
156 | - // background: rgba(23, 27, 41); | ||
157 | - background: #000; | ||
158 | - align-items: center; | ||
159 | - justify-content: center; | ||
160 | 162 | ||
161 | &__unlock { | 163 | &__unlock { |
162 | - position: absolute; | ||
163 | - top: 0; | ||
164 | - left: 50%; | ||
165 | - display: flex; | ||
166 | - height: 50px; | ||
167 | - padding-top: 20px; | ||
168 | - font-size: 18px; | ||
169 | - color: #fff; | ||
170 | - cursor: pointer; | ||
171 | transform: translate(-50%, 0); | 164 | transform: translate(-50%, 0); |
172 | - flex-direction: column; | ||
173 | - align-items: center; | ||
174 | - justify-content: space-between; | ||
175 | - transition: all 0.3s; | ||
176 | - } | ||
177 | - | ||
178 | - &__date { | ||
179 | - display: flex; | ||
180 | - width: 100vw; | ||
181 | - height: 100vh; | ||
182 | - align-items: center; | ||
183 | - justify-content: center; | ||
184 | - } | ||
185 | - | ||
186 | - &__hour { | ||
187 | - position: relative; | ||
188 | - margin-right: 80px; | ||
189 | - | ||
190 | - .meridiem { | ||
191 | - position: absolute; | ||
192 | - top: 20px; | ||
193 | - left: 20px; | ||
194 | - font-size: 26px; | ||
195 | - } | ||
196 | - @media (max-width: @screen-xs) { | ||
197 | - margin-right: 20px; | ||
198 | - } | ||
199 | } | 165 | } |
200 | 166 | ||
201 | &__hour, | 167 | &__hour, |
202 | &__minute { | 168 | &__minute { |
203 | display: flex; | 169 | display: flex; |
204 | - width: 40%; | ||
205 | - height: 74%; | ||
206 | font-weight: 700; | 170 | font-weight: 700; |
207 | color: #bababa; | 171 | color: #bababa; |
208 | background: #141313; | 172 | background: #141313; |
209 | border-radius: 30px; | 173 | border-radius: 30px; |
210 | justify-content: center; | 174 | justify-content: center; |
211 | align-items: center; | 175 | align-items: center; |
212 | - @media (min-width: @screen-xxxl-min) { | ||
213 | - font-size: 46em; | ||
214 | - } | ||
215 | - @media (min-width: @screen-xl-max) and (max-width: @screen-xxl-max) { | ||
216 | - font-size: 38em; | ||
217 | - } | ||
218 | 176 | ||
219 | - @media (min-width: @screen-lg-max) and (max-width: @screen-xl-max) { | ||
220 | - font-size: 30em; | ||
221 | - } | ||
222 | - @media (min-width: @screen-md-max) and (max-width: @screen-lg-max) { | ||
223 | - font-size: 23em; | 177 | + @media screen and (max-width: @screen-md) { |
178 | + span:not(.meridiem) { | ||
179 | + font-size: 160px; | ||
180 | + } | ||
224 | } | 181 | } |
225 | - @media (min-width: @screen-sm-max) and (max-width: @screen-md-max) { | ||
226 | - height: 50%; | ||
227 | - font-size: 12em; | ||
228 | - border-radius: 10px; | ||
229 | 182 | ||
230 | - .meridiem { | ||
231 | - font-size: 20px; | 183 | + @media screen and (min-width: @screen-md) { |
184 | + span:not(.meridiem) { | ||
185 | + font-size: 160px; | ||
232 | } | 186 | } |
233 | } | 187 | } |
234 | - @media (min-width: @screen-xs-max) and (max-width: @screen-sm-max) { | ||
235 | - font-size: 13em; | ||
236 | - } | ||
237 | - @media (max-width: @screen-xs) { | ||
238 | - height: 30%; | ||
239 | - font-size: 5em; | ||
240 | - border-radius: 10px; | ||
241 | 188 | ||
242 | - .meridiem { | ||
243 | - font-size: 14px; | 189 | + @media screen and (max-width: @screen-sm) { |
190 | + span:not(.meridiem) { | ||
191 | + font-size: 90px; | ||
244 | } | 192 | } |
245 | } | 193 | } |
246 | - } | ||
247 | - | ||
248 | - &__footer-date { | ||
249 | - position: absolute; | ||
250 | - bottom: 20px; | ||
251 | - width: 100%; | ||
252 | - font-family: helvetica; | ||
253 | - color: #bababa; | ||
254 | - text-align: center; | ||
255 | - | ||
256 | - .time { | ||
257 | - font-size: 50px; | ||
258 | - | ||
259 | - .meridiem { | ||
260 | - font-size: 32px; | 194 | + @media screen and (min-width: @screen-lg) { |
195 | + span:not(.meridiem) { | ||
196 | + font-size: 220px; | ||
261 | } | 197 | } |
262 | } | 198 | } |
263 | 199 | ||
264 | - .date { | ||
265 | - font-size: 26px; | 200 | + @media screen and (min-width: @screen-xl) { |
201 | + span:not(.meridiem) { | ||
202 | + font-size: 260px; | ||
203 | + } | ||
204 | + } | ||
205 | + @media screen and (min-width: @screen-2xl) { | ||
206 | + span:not(.meridiem) { | ||
207 | + font-size: 320px; | ||
208 | + } | ||
266 | } | 209 | } |
267 | } | 210 | } |
268 | 211 |
src/views/sys/login/ForgetPasswordForm.vue
0 → 100644
1 | +<template> | ||
2 | + <Form class="p-4" :model="formData" :rules="getFormRules" ref="formRef"> | ||
3 | + <FormItem name="account" class="enter-x"> | ||
4 | + <Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" /> | ||
5 | + </FormItem> | ||
6 | + | ||
7 | + <FormItem name="mobile" class="enter-x"> | ||
8 | + <Input size="large" v-model:value="formData.mobile" :placeholder="t('sys.login.mobile')" /> | ||
9 | + </FormItem> | ||
10 | + <FormItem name="sms" class="enter-x"> | ||
11 | + <CountdownInput | ||
12 | + size="large" | ||
13 | + v-model:value="formData.sms" | ||
14 | + :placeholder="t('sys.login.smsCode')" | ||
15 | + /> | ||
16 | + </FormItem> | ||
17 | + | ||
18 | + <FormItem class="enter-x"> | ||
19 | + <Button | ||
20 | + type="primary" | ||
21 | + size="large" | ||
22 | + block | ||
23 | + @click="handleReset" | ||
24 | + :loading="loading" | ||
25 | + class="enter-x" | ||
26 | + > | ||
27 | + {{ t('common.resetText') }} | ||
28 | + </Button> | ||
29 | + <Button size="large" block class="mt-4 enter-x" @click="handleBackLogin"> | ||
30 | + {{ t('sys.login.backSignIn') }} | ||
31 | + </Button> | ||
32 | + </FormItem> | ||
33 | + </Form> | ||
34 | +</template> | ||
35 | +<script lang="ts"> | ||
36 | + import { defineComponent, reactive, ref } from 'vue'; | ||
37 | + | ||
38 | + import { Form, Input, Button } from 'ant-design-vue'; | ||
39 | + import { CountdownInput } from '/@/components/CountDown'; | ||
40 | + | ||
41 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
42 | + import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin'; | ||
43 | + | ||
44 | + export default defineComponent({ | ||
45 | + name: 'ForgetPasswordForm', | ||
46 | + components: { | ||
47 | + Button, | ||
48 | + Form, | ||
49 | + FormItem: Form.Item, | ||
50 | + Input, | ||
51 | + CountdownInput, | ||
52 | + }, | ||
53 | + setup() { | ||
54 | + const { t } = useI18n(); | ||
55 | + const { setLoginState } = useLoginState(); | ||
56 | + const { getFormRules } = useFormRules(); | ||
57 | + | ||
58 | + const formRef = ref<any>(null); | ||
59 | + const loading = ref(false); | ||
60 | + | ||
61 | + const formData = reactive({ | ||
62 | + account: '', | ||
63 | + mobile: '', | ||
64 | + sms: '', | ||
65 | + }); | ||
66 | + | ||
67 | + const { validForm } = useFormValid(formRef); | ||
68 | + | ||
69 | + async function handleReset() { | ||
70 | + const data = await validForm(); | ||
71 | + if (!data) return; | ||
72 | + console.log(data); | ||
73 | + } | ||
74 | + | ||
75 | + function handleBackLogin() { | ||
76 | + setLoginState(LoginStateEnum.LOGIN); | ||
77 | + } | ||
78 | + | ||
79 | + return { | ||
80 | + t, | ||
81 | + formRef, | ||
82 | + formData, | ||
83 | + getFormRules, | ||
84 | + handleReset, | ||
85 | + loading, | ||
86 | + handleBackLogin, | ||
87 | + }; | ||
88 | + }, | ||
89 | + }); | ||
90 | +</script> |
src/views/sys/login/Login.vue
1 | <template> | 1 | <template> |
2 | - <div class="login"> | ||
3 | - <div class="opacity-0 login-mask lg:opacity-100"></div> | ||
4 | - <div class="justify-center login-form-wrap lg:justify-end"> | ||
5 | - <div class="mx-6 login-form"> | ||
6 | - <AppLocalePicker v-if="showLocale" class="login-form__locale" /> | ||
7 | - <div class="px-2 py-10 login-form__content"> | ||
8 | - <header> | ||
9 | - <img :src="logo" class="mr-4" /> | ||
10 | - <h1>{{ title }}</h1> | ||
11 | - </header> | ||
12 | - | ||
13 | - <a-form class="login-form__main" :model="formData" :rules="formRules" ref="formRef"> | ||
14 | - <a-form-item name="account"> | ||
15 | - <a-input size="large" v-model:value="formData.account" placeholder="username: vben" /> | ||
16 | - </a-form-item> | ||
17 | - <a-form-item name="password"> | ||
18 | - <a-input-password | ||
19 | - size="large" | ||
20 | - visibilityToggle | ||
21 | - v-model:value="formData.password" | ||
22 | - placeholder="password: 123456" | ||
23 | - /> | ||
24 | - </a-form-item> | ||
25 | - | ||
26 | - <a-row> | ||
27 | - <a-col :span="12"> | ||
28 | - <a-form-item> | ||
29 | - <!-- No logic, you need to deal with it yourself --> | ||
30 | - <a-checkbox v-model:checked="autoLogin" size="small">{{ | ||
31 | - t('sys.login.autoLogin') | ||
32 | - }}</a-checkbox> | ||
33 | - </a-form-item> | ||
34 | - </a-col> | ||
35 | - <a-col :span="12"> | ||
36 | - <a-form-item :style="{ 'text-align': 'right' }"> | ||
37 | - <!-- No logic, you need to deal with it yourself --> | ||
38 | - <a-button type="link" size="small"> | ||
39 | - {{ t('sys.login.forgetPassword') }} | ||
40 | - </a-button> | ||
41 | - </a-form-item> | ||
42 | - </a-col> | ||
43 | - </a-row> | ||
44 | - <a-form-item> | ||
45 | - <a-button | ||
46 | - type="primary" | ||
47 | - size="large" | ||
48 | - class="rounded-sm" | ||
49 | - :block="true" | ||
50 | - @click="login" | ||
51 | - :loading="formState.loading" | ||
52 | - > | ||
53 | - {{ t('sys.login.loginButton') }} | ||
54 | - </a-button> | ||
55 | - </a-form-item> | ||
56 | - </a-form> | 2 | + <div :class="prefixCls" class="relative w-full h-full px-4"> |
3 | + <AppLocalePicker | ||
4 | + class="absolute top-4 right-4 enter-x text-white xl:text-gray-600" | ||
5 | + :showText="false" | ||
6 | + /> | ||
7 | + | ||
8 | + <span class="-enter-x xl:hidden"> | ||
9 | + <AppLogo :alwaysShowTitle="true" /> | ||
10 | + </span> | ||
11 | + | ||
12 | + <div class="container relative h-full py-2 mx-auto sm:px-10"> | ||
13 | + <div class="flex h-full"> | ||
14 | + <div class="hidden xl:flex xl:flex-col xl:w-6/12 min-h-full mr-4 pl-4"> | ||
15 | + <AppLogo class="-enter-x" /> | ||
16 | + <div class="my-auto"> | ||
17 | + <img | ||
18 | + :alt="title" | ||
19 | + src="../../../assets/svg/login-box-bg.svg" | ||
20 | + class="w-1/2 -mt-16 -enter-x" | ||
21 | + /> | ||
22 | + <div class="mt-10 font-medium text-white -enter-x"> | ||
23 | + <span class="mt-4 text-3xl inline-block"> {{ t('sys.login.signInTitle') }}</span> | ||
24 | + </div> | ||
25 | + <div class="mt-5 text-md text-white font-normal dark:text-gray-500 -enter-x"> | ||
26 | + {{ t('sys.login.signInDesc') }} | ||
27 | + </div> | ||
28 | + </div> | ||
29 | + </div> | ||
30 | + <div class="h-full xl:h-auto flex py-5 xl:py-0 xl:my-0 w-full xl:w-6/12"> | ||
31 | + <div | ||
32 | + class="my-auto mx-auto xl:ml-20 bg-white xl:bg-transparent px-5 py-8 sm:px-8 xl:p-0 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto enter-x relative" | ||
33 | + > | ||
34 | + <h2 class="font-bold text-2xl xl:text-3xl enter-x text-center xl:text-left mb-6"> | ||
35 | + {{ getFormTitle }} | ||
36 | + </h2> | ||
37 | + <LoginForm v-show="getShowLogin" /> | ||
38 | + <ForgetPasswordForm v-if="getShowResetPassword" /> | ||
39 | + <RegisterForm v-if="getShowRegister" /> | ||
40 | + <MobileForm v-if="getShowMobile" /> | ||
41 | + <QrCodeForm v-if="getShowQrCode" /> | ||
42 | + </div> | ||
57 | </div> | 43 | </div> |
58 | </div> | 44 | </div> |
59 | </div> | 45 | </div> |
60 | </div> | 46 | </div> |
61 | </template> | 47 | </template> |
62 | <script lang="ts"> | 48 | <script lang="ts"> |
63 | - import { defineComponent, reactive, ref, unref, toRaw } from 'vue'; | ||
64 | - import { Checkbox, Form, Input, Row, Col } from 'ant-design-vue'; | 49 | + import { defineComponent, computed } from 'vue'; |
65 | 50 | ||
66 | - import { Button } from '/@/components/Button'; | 51 | + import { AppLogo } from '/@/components/Application'; |
67 | import { AppLocalePicker } from '/@/components/Application'; | 52 | import { AppLocalePicker } from '/@/components/Application'; |
53 | + import LoginForm from './LoginForm.vue'; | ||
54 | + import ForgetPasswordForm from './ForgetPasswordForm.vue'; | ||
55 | + import RegisterForm from './RegisterForm.vue'; | ||
56 | + import MobileForm from './MobileForm.vue'; | ||
57 | + import QrCodeForm from './QrCodeForm.vue'; | ||
68 | 58 | ||
69 | - import { userStore } from '/@/store/modules/user'; | ||
70 | - | ||
71 | - import { useMessage } from '/@/hooks/web/useMessage'; | ||
72 | import { useGlobSetting, useProjectSetting } from '/@/hooks/setting'; | 59 | import { useGlobSetting, useProjectSetting } from '/@/hooks/setting'; |
73 | - import logo from '/@/assets/images/logo.png'; | ||
74 | import { useI18n } from '/@/hooks/web/useI18n'; | 60 | import { useI18n } from '/@/hooks/web/useI18n'; |
61 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
62 | + import { useShowLoginForm, useFormTitle } from './useLogin'; | ||
75 | 63 | ||
76 | export default defineComponent({ | 64 | export default defineComponent({ |
65 | + name: 'Login', | ||
77 | components: { | 66 | components: { |
78 | - [Checkbox.name]: Checkbox, | ||
79 | - [Form.name]: Form, | ||
80 | - [Form.Item.name]: Form.Item, | ||
81 | - [Input.name]: Input, | ||
82 | - [Input.Password.name]: Input.Password, | ||
83 | - AButton: Button, | 67 | + AppLogo, |
68 | + LoginForm, | ||
69 | + ForgetPasswordForm, | ||
70 | + RegisterForm, | ||
71 | + MobileForm, | ||
72 | + QrCodeForm, | ||
84 | AppLocalePicker, | 73 | AppLocalePicker, |
85 | - [Row.name]: Row, | ||
86 | - [Col.name]: Col, | ||
87 | }, | 74 | }, |
88 | setup() { | 75 | setup() { |
89 | - const formRef = ref<any>(null); | ||
90 | - const autoLoginRef = ref(false); | ||
91 | - | ||
92 | const globSetting = useGlobSetting(); | 76 | const globSetting = useGlobSetting(); |
77 | + const { getFormTitle } = useFormTitle(); | ||
78 | + const { prefixCls } = useDesign('login'); | ||
93 | const { locale } = useProjectSetting(); | 79 | const { locale } = useProjectSetting(); |
94 | - const { notification } = useMessage(); | ||
95 | const { t } = useI18n(); | 80 | const { t } = useI18n(); |
96 | 81 | ||
97 | - const formData = reactive({ | ||
98 | - account: 'vben', | ||
99 | - password: '123456', | ||
100 | - }); | ||
101 | - | ||
102 | - const formState = reactive({ | ||
103 | - loading: false, | ||
104 | - }); | ||
105 | - | ||
106 | - const formRules = reactive({ | ||
107 | - account: [{ required: true, message: t('sys.login.accountPlaceholder'), trigger: 'blur' }], | ||
108 | - password: [ | ||
109 | - { required: true, message: t('sys.login.passwordPlaceholder'), trigger: 'blur' }, | ||
110 | - ], | ||
111 | - }); | ||
112 | - | ||
113 | - async function handleLogin() { | ||
114 | - const form = unref(formRef); | ||
115 | - if (!form) return; | ||
116 | - formState.loading = true; | ||
117 | - try { | ||
118 | - const data = await form.validate(); | ||
119 | - const userInfo = await userStore.login( | ||
120 | - toRaw({ | ||
121 | - password: data.password, | ||
122 | - username: data.account, | ||
123 | - }) | ||
124 | - ); | ||
125 | - if (userInfo) { | ||
126 | - notification.success({ | ||
127 | - message: t('sys.login.loginSuccessTitle'), | ||
128 | - description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`, | ||
129 | - duration: 3, | ||
130 | - }); | ||
131 | - } | ||
132 | - } catch (error) { | ||
133 | - } finally { | ||
134 | - formState.loading = false; | ||
135 | - } | ||
136 | - } | ||
137 | return { | 82 | return { |
138 | - formRef, | ||
139 | - formData, | ||
140 | - formState, | ||
141 | - formRules, | ||
142 | - login: handleLogin, | ||
143 | - autoLogin: autoLoginRef, | ||
144 | - title: globSetting && globSetting.title, | ||
145 | - logo, | ||
146 | t, | 83 | t, |
147 | - showLocale: locale.show, | 84 | + prefixCls, |
85 | + title: computed(() => globSetting?.title ?? ''), | ||
86 | + showLocale: computed(() => locale.show), | ||
87 | + getFormTitle, | ||
88 | + ...useShowLoginForm(), | ||
148 | }; | 89 | }; |
149 | }, | 90 | }, |
150 | }); | 91 | }); |
151 | </script> | 92 | </script> |
152 | <style lang="less"> | 93 | <style lang="less"> |
153 | - .login-form__locale { | ||
154 | - position: absolute; | ||
155 | - top: 14px; | ||
156 | - right: 14px; | ||
157 | - z-index: 1; | ||
158 | - } | 94 | + @prefix-cls: ~'@{namespace}-login'; |
95 | + @logo-prefix-cls: ~'@{namespace}-app-logo'; | ||
96 | + @countdown-prefix-cls: ~'@{namespace}-countdown-input'; | ||
159 | 97 | ||
160 | - .login { | ||
161 | - position: relative; | ||
162 | - height: 100vh; | ||
163 | - background: url(../../../assets/images/login/login-bg.png) no-repeat; | ||
164 | - background-size: 100% 100%; | 98 | + .@{prefix-cls} { |
99 | + @media (max-width: @screen-xl) { | ||
100 | + background: linear-gradient(180deg, #1c3faa, #1c3faa); | ||
101 | + } | ||
165 | 102 | ||
166 | - &-mask { | 103 | + &::before { |
104 | + position: absolute; | ||
105 | + top: 0; | ||
106 | + left: 0; | ||
107 | + width: 100%; | ||
167 | height: 100%; | 108 | height: 100%; |
168 | - background: url(../../../assets/images/login/login-in.png) no-repeat; | ||
169 | - background-position: 30% 30%; | ||
170 | - background-size: 80% 80%; | 109 | + margin-left: -48%; |
110 | + background-image: url(/@/assets/svg/login-bg.svg); | ||
111 | + background-position: 100%; | ||
112 | + background-repeat: no-repeat; | ||
113 | + background-size: auto 100%; | ||
114 | + content: ''; | ||
115 | + @media (max-width: @screen-xl) { | ||
116 | + display: none; | ||
117 | + } | ||
171 | } | 118 | } |
172 | 119 | ||
173 | - &-form { | ||
174 | - position: relative; | ||
175 | - bottom: 60px; | ||
176 | - width: 400px; | ||
177 | - background: @white; | ||
178 | - border: 10px solid rgba(255, 255, 255, 0.5); | ||
179 | - border-width: 8px; | ||
180 | - border-radius: 4px; | ||
181 | - background-clip: padding-box; | ||
182 | - | ||
183 | - &__main { | ||
184 | - margin: 30px auto 0 auto !important; | 120 | + .@{logo-prefix-cls} { |
121 | + position: absolute; | ||
122 | + top: 12px; | ||
123 | + height: 30px; | ||
124 | + | ||
125 | + &__title { | ||
126 | + font-size: 16px; | ||
127 | + color: #fff; | ||
185 | } | 128 | } |
186 | 129 | ||
187 | - &-wrap { | ||
188 | - position: absolute; | ||
189 | - top: 0; | ||
190 | - right: 0; | ||
191 | - display: flex; | ||
192 | - width: 100%; | ||
193 | - height: 100%; | ||
194 | - align-items: center; | 130 | + img { |
131 | + width: 32px; | ||
195 | } | 132 | } |
133 | + } | ||
134 | + | ||
135 | + .container { | ||
136 | + .@{logo-prefix-cls} { | ||
137 | + display: flex; | ||
138 | + width: 60%; | ||
139 | + height: 80px; | ||
140 | + | ||
141 | + &__title { | ||
142 | + font-size: 24px; | ||
143 | + color: #fff; | ||
144 | + } | ||
196 | 145 | ||
197 | - &__content { | ||
198 | - position: relative; | ||
199 | - width: 100%; | ||
200 | - height: 100%; | ||
201 | - padding: 60px 0 40px 0; | ||
202 | - border: 1px solid #999; | ||
203 | - border-radius: 2px; | ||
204 | - | ||
205 | - header { | ||
206 | - display: flex; | ||
207 | - justify-content: center; | ||
208 | - align-items: center; | ||
209 | - | ||
210 | - img { | ||
211 | - display: inline-block; | ||
212 | - width: 48px; | ||
213 | - } | ||
214 | - | ||
215 | - h1 { | ||
216 | - margin-bottom: 0; | ||
217 | - font-size: 24px; | ||
218 | - text-align: center; | ||
219 | - } | 146 | + img { |
147 | + width: 48px; | ||
220 | } | 148 | } |
149 | + } | ||
150 | + } | ||
151 | + | ||
152 | + &-sign-in-way { | ||
153 | + .anticon { | ||
154 | + font-size: 22px; | ||
155 | + color: #888; | ||
156 | + cursor: pointer; | ||
221 | 157 | ||
222 | - form { | ||
223 | - width: 80%; | 158 | + &:hover { |
159 | + color: @primary-color; | ||
224 | } | 160 | } |
225 | } | 161 | } |
226 | } | 162 | } |
163 | + | ||
164 | + input:not([type='checkbox']) { | ||
165 | + min-width: 360px; | ||
166 | + @media (max-width: @screen-sm) { | ||
167 | + min-width: 240px; | ||
168 | + } | ||
169 | + } | ||
170 | + .@{countdown-prefix-cls} input { | ||
171 | + min-width: unset; | ||
172 | + } | ||
173 | + | ||
174 | + .ant-divider-inner-text { | ||
175 | + font-size: 12px; | ||
176 | + color: @text-color-secondary; | ||
177 | + } | ||
227 | } | 178 | } |
228 | </style> | 179 | </style> |
src/views/sys/login/LoginForm.vue
0 → 100644
1 | +<template> | ||
2 | + <Form class="p-4" :model="formData" :rules="getFormRules" ref="formRef"> | ||
3 | + <FormItem name="account" class="enter-x"> | ||
4 | + <Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" /> | ||
5 | + </FormItem> | ||
6 | + <FormItem name="password" class="enter-x"> | ||
7 | + <InputPassword | ||
8 | + size="large" | ||
9 | + visibilityToggle | ||
10 | + v-model:value="formData.password" | ||
11 | + :placeholder="t('sys.login.password')" | ||
12 | + /> | ||
13 | + </FormItem> | ||
14 | + | ||
15 | + <ARow class="enter-x"> | ||
16 | + <ACol :span="12"> | ||
17 | + <FormItem> | ||
18 | + <!-- No logic, you need to deal with it yourself --> | ||
19 | + <Checkbox v-model:checked="rememberMe" size="small"> | ||
20 | + {{ t('sys.login.rememberMe') }} | ||
21 | + </Checkbox> | ||
22 | + </FormItem> | ||
23 | + </ACol> | ||
24 | + <ACol :span="12"> | ||
25 | + <FormItem :style="{ 'text-align': 'right' }"> | ||
26 | + <!-- No logic, you need to deal with it yourself --> | ||
27 | + <Button type="link" size="small" @click="setLoginState(LoginStateEnum.RESET_PASSWORD)"> | ||
28 | + {{ t('sys.login.forgetPassword') }} | ||
29 | + </Button> | ||
30 | + </FormItem> | ||
31 | + </ACol> | ||
32 | + </ARow> | ||
33 | + | ||
34 | + <FormItem class="enter-x"> | ||
35 | + <Button | ||
36 | + type="primary" | ||
37 | + size="large" | ||
38 | + block | ||
39 | + @click="handleLogin" | ||
40 | + :loading="loading" | ||
41 | + class="enter-x" | ||
42 | + > | ||
43 | + {{ t('sys.login.loginButton') }} | ||
44 | + </Button> | ||
45 | + <!-- <Button size="large" class="mt-4 enter-x" block @click="handleRegister"> | ||
46 | + {{ t('sys.login.registerButton') }} | ||
47 | + </Button> --> | ||
48 | + </FormItem> | ||
49 | + <ARow class="enter-x"> | ||
50 | + <ACol :span="7"> | ||
51 | + <Button block @click="setLoginState(LoginStateEnum.MOBILE)"> | ||
52 | + {{ t('sys.login.mobileSignInFormTitle') }} | ||
53 | + </Button> | ||
54 | + </ACol> | ||
55 | + <ACol :span="8" :offset="1"> | ||
56 | + <Button block @click="setLoginState(LoginStateEnum.QR_CODE)"> | ||
57 | + {{ t('sys.login.qrSignInFormTitle') }} | ||
58 | + </Button> | ||
59 | + </ACol> | ||
60 | + <ACol :span="7" :offset="1"> | ||
61 | + <Button block @click="setLoginState(LoginStateEnum.REGISTER)"> | ||
62 | + {{ t('sys.login.registerButton') }} | ||
63 | + </Button> | ||
64 | + </ACol> | ||
65 | + </ARow> | ||
66 | + | ||
67 | + <Divider>{{ t('sys.login.otherSignIn') }}</Divider> | ||
68 | + | ||
69 | + <div class="flex justify-evenly enter-x" :class="`${prefixCls}-sign-in-way`"> | ||
70 | + <GithubFilled /> | ||
71 | + <WechatFilled /> | ||
72 | + <AlipayCircleFilled /> | ||
73 | + <GoogleCircleFilled /> | ||
74 | + <TwitterCircleFilled /> | ||
75 | + </div> | ||
76 | + </Form> | ||
77 | +</template> | ||
78 | +<script lang="ts"> | ||
79 | + import { defineComponent, reactive, ref, toRaw } from 'vue'; | ||
80 | + | ||
81 | + import { Checkbox, Form, Input, Row, Col, Button, Divider } from 'ant-design-vue'; | ||
82 | + import { | ||
83 | + GithubFilled, | ||
84 | + WechatFilled, | ||
85 | + AlipayCircleFilled, | ||
86 | + GoogleCircleFilled, | ||
87 | + TwitterCircleFilled, | ||
88 | + } from '@ant-design/icons-vue'; | ||
89 | + | ||
90 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
91 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
92 | + | ||
93 | + import { userStore } from '/@/store/modules/user'; | ||
94 | + import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin'; | ||
95 | + import { useDesign } from '/@/hooks/web/useDesign'; | ||
96 | + | ||
97 | + export default defineComponent({ | ||
98 | + name: 'LoginForm', | ||
99 | + components: { | ||
100 | + Checkbox, | ||
101 | + Button, | ||
102 | + Form, | ||
103 | + FormItem: Form.Item, | ||
104 | + Input, | ||
105 | + Divider, | ||
106 | + InputPassword: Input.Password, | ||
107 | + [Col.name]: Col, | ||
108 | + [Row.name]: Row, | ||
109 | + GithubFilled, | ||
110 | + WechatFilled, | ||
111 | + AlipayCircleFilled, | ||
112 | + GoogleCircleFilled, | ||
113 | + TwitterCircleFilled, | ||
114 | + }, | ||
115 | + setup() { | ||
116 | + const { t } = useI18n(); | ||
117 | + const { notification } = useMessage(); | ||
118 | + const { prefixCls } = useDesign('login'); | ||
119 | + | ||
120 | + const { setLoginState } = useLoginState(); | ||
121 | + const { getFormRules } = useFormRules(); | ||
122 | + | ||
123 | + const formRef = ref<any>(null); | ||
124 | + const loading = ref(false); | ||
125 | + const rememberMe = ref(false); | ||
126 | + | ||
127 | + const formData = reactive({ | ||
128 | + account: 'vben', | ||
129 | + password: '123456', | ||
130 | + }); | ||
131 | + | ||
132 | + const { validForm } = useFormValid(formRef); | ||
133 | + | ||
134 | + async function handleLogin() { | ||
135 | + const data = await validForm(); | ||
136 | + if (!data) return; | ||
137 | + try { | ||
138 | + loading.value = true; | ||
139 | + const userInfo = await userStore.login( | ||
140 | + toRaw({ | ||
141 | + password: data.password, | ||
142 | + username: data.account, | ||
143 | + }) | ||
144 | + ); | ||
145 | + if (userInfo) { | ||
146 | + notification.success({ | ||
147 | + message: t('sys.login.loginSuccessTitle'), | ||
148 | + description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.realName}`, | ||
149 | + duration: 3, | ||
150 | + }); | ||
151 | + } | ||
152 | + } finally { | ||
153 | + loading.value = false; | ||
154 | + } | ||
155 | + } | ||
156 | + | ||
157 | + return { | ||
158 | + t, | ||
159 | + prefixCls, | ||
160 | + formRef, | ||
161 | + formData, | ||
162 | + getFormRules, | ||
163 | + rememberMe, | ||
164 | + handleLogin, | ||
165 | + loading, | ||
166 | + setLoginState, | ||
167 | + LoginStateEnum, | ||
168 | + }; | ||
169 | + }, | ||
170 | + }); | ||
171 | +</script> |
src/views/sys/login/MobileForm.vue
0 → 100644
1 | +<template> | ||
2 | + <Form class="p-4" :model="formData" :rules="getFormRules" ref="formRef"> | ||
3 | + <FormItem name="mobile" class="enter-x"> | ||
4 | + <Input size="large" v-model:value="formData.mobile" :placeholder="t('sys.login.mobile')" /> | ||
5 | + </FormItem> | ||
6 | + <FormItem name="sms" class="enter-x"> | ||
7 | + <CountdownInput | ||
8 | + size="large" | ||
9 | + v-model:value="formData.sms" | ||
10 | + :placeholder="t('sys.login.smsCode')" | ||
11 | + /> | ||
12 | + </FormItem> | ||
13 | + | ||
14 | + <FormItem class="enter-x"> | ||
15 | + <Button | ||
16 | + type="primary" | ||
17 | + size="large" | ||
18 | + block | ||
19 | + @click="handleLogin" | ||
20 | + :loading="loading" | ||
21 | + class="enter-x" | ||
22 | + > | ||
23 | + {{ t('sys.login.loginButton') }} | ||
24 | + </Button> | ||
25 | + <Button size="large" block class="mt-4 enter-x" @click="handleBackLogin"> | ||
26 | + {{ t('sys.login.backSignIn') }} | ||
27 | + </Button> | ||
28 | + </FormItem> | ||
29 | + </Form> | ||
30 | +</template> | ||
31 | +<script lang="ts"> | ||
32 | + import { defineComponent, reactive, ref } from 'vue'; | ||
33 | + | ||
34 | + import { Form, Input, Button } from 'ant-design-vue'; | ||
35 | + import { CountdownInput } from '/@/components/CountDown'; | ||
36 | + | ||
37 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
38 | + import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin'; | ||
39 | + | ||
40 | + export default defineComponent({ | ||
41 | + name: 'MobileForm', | ||
42 | + components: { | ||
43 | + Button, | ||
44 | + Form, | ||
45 | + FormItem: Form.Item, | ||
46 | + Input, | ||
47 | + CountdownInput, | ||
48 | + }, | ||
49 | + setup() { | ||
50 | + const { t } = useI18n(); | ||
51 | + const { setLoginState } = useLoginState(); | ||
52 | + const { getFormRules } = useFormRules(); | ||
53 | + | ||
54 | + const formRef = ref<any>(null); | ||
55 | + const loading = ref(false); | ||
56 | + | ||
57 | + const formData = reactive({ | ||
58 | + mobile: '', | ||
59 | + sms: '', | ||
60 | + }); | ||
61 | + | ||
62 | + const { validForm } = useFormValid(formRef); | ||
63 | + | ||
64 | + async function handleLogin() { | ||
65 | + const data = await validForm(); | ||
66 | + if (!data) return; | ||
67 | + console.log(data); | ||
68 | + } | ||
69 | + | ||
70 | + function handleBackLogin() { | ||
71 | + setLoginState(LoginStateEnum.LOGIN); | ||
72 | + } | ||
73 | + | ||
74 | + return { | ||
75 | + t, | ||
76 | + formRef, | ||
77 | + formData, | ||
78 | + getFormRules, | ||
79 | + handleLogin, | ||
80 | + loading, | ||
81 | + handleBackLogin, | ||
82 | + }; | ||
83 | + }, | ||
84 | + }); | ||
85 | +</script> |
src/views/sys/login/QrCodeForm.vue
0 → 100644
1 | +<template> | ||
2 | + <div class="enter-x min-w-64 min-h-64"> | ||
3 | + <QrCode :value="qrCodeUrl" class="enter-x flex justify-center xl:justify-start" :width="280" /> | ||
4 | + <Divider>{{ t('sys.login.scanSign') }}</Divider> | ||
5 | + <Button size="large" block class="mt-4 enter-x" @click="handleBackLogin"> | ||
6 | + {{ t('sys.login.backSignIn') }} | ||
7 | + </Button> | ||
8 | + </div> | ||
9 | +</template> | ||
10 | +<script lang="ts"> | ||
11 | + import { defineComponent } from 'vue'; | ||
12 | + | ||
13 | + import { Button, Divider } from 'ant-design-vue'; | ||
14 | + | ||
15 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
16 | + import { LoginStateEnum, useLoginState } from './useLogin'; | ||
17 | + import { QrCode } from '/@/components/Qrcode/index'; | ||
18 | + const qrCodeUrl = 'https://vvbin.cn/next/login'; | ||
19 | + export default defineComponent({ | ||
20 | + name: 'QrCodeForm', | ||
21 | + components: { | ||
22 | + Button, | ||
23 | + QrCode, | ||
24 | + Divider, | ||
25 | + }, | ||
26 | + setup() { | ||
27 | + const { t } = useI18n(); | ||
28 | + const { setLoginState } = useLoginState(); | ||
29 | + | ||
30 | + function handleBackLogin() { | ||
31 | + setLoginState(LoginStateEnum.LOGIN); | ||
32 | + } | ||
33 | + return { | ||
34 | + t, | ||
35 | + handleBackLogin, | ||
36 | + qrCodeUrl, | ||
37 | + }; | ||
38 | + }, | ||
39 | + }); | ||
40 | +</script> |
src/views/sys/login/RegisterForm.vue
0 → 100644
1 | +<template> | ||
2 | + <Form class="p-4" :model="formData" :rules="getFormRules" ref="formRef"> | ||
3 | + <FormItem name="account" class="enter-x"> | ||
4 | + <Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" /> | ||
5 | + </FormItem> | ||
6 | + <FormItem name="mobile" class="enter-x"> | ||
7 | + <Input size="large" v-model:value="formData.mobile" :placeholder="t('sys.login.mobile')" /> | ||
8 | + </FormItem> | ||
9 | + <FormItem name="sms" class="enter-x"> | ||
10 | + <CountdownInput | ||
11 | + size="large" | ||
12 | + v-model:value="formData.sms" | ||
13 | + :placeholder="t('sys.login.smsCode')" | ||
14 | + /> | ||
15 | + </FormItem> | ||
16 | + <FormItem name="password" class="enter-x"> | ||
17 | + <StrengthMeter | ||
18 | + size="large" | ||
19 | + v-model:value="formData.password" | ||
20 | + :placeholder="t('sys.login.password')" | ||
21 | + /> | ||
22 | + </FormItem> | ||
23 | + <FormItem name="confirmPassword" class="enter-x"> | ||
24 | + <InputPassword | ||
25 | + size="large" | ||
26 | + visibilityToggle | ||
27 | + v-model:value="formData.confirmPassword" | ||
28 | + :placeholder="t('sys.login.confirmPassword')" | ||
29 | + /> | ||
30 | + </FormItem> | ||
31 | + | ||
32 | + <FormItem class="enter-x" name="policy"> | ||
33 | + <!-- No logic, you need to deal with it yourself --> | ||
34 | + <Checkbox v-model:checked="formData.policy" size="small"> | ||
35 | + {{ t('sys.login.policy') }} | ||
36 | + </Checkbox> | ||
37 | + </FormItem> | ||
38 | + | ||
39 | + <Button | ||
40 | + type="primary" | ||
41 | + size="large" | ||
42 | + block | ||
43 | + @click="handleReset" | ||
44 | + :loading="loading" | ||
45 | + class="enter-x" | ||
46 | + > | ||
47 | + {{ t('sys.login.registerButton') }} | ||
48 | + </Button> | ||
49 | + <Button size="large" block class="mt-4 enter-x" @click="handleBackLogin"> | ||
50 | + {{ t('sys.login.backSignIn') }} | ||
51 | + </Button> | ||
52 | + </Form> | ||
53 | +</template> | ||
54 | +<script lang="ts"> | ||
55 | + import { defineComponent, reactive, ref } from 'vue'; | ||
56 | + | ||
57 | + import { Form, Input, Button, Divider, Checkbox } from 'ant-design-vue'; | ||
58 | + import { StrengthMeter } from '/@/components/StrengthMeter'; | ||
59 | + import { CountdownInput } from '/@/components/CountDown'; | ||
60 | + | ||
61 | + import { useI18n } from '/@/hooks/web/useI18n'; | ||
62 | + import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin'; | ||
63 | + | ||
64 | + export default defineComponent({ | ||
65 | + name: 'RegisterPasswordForm', | ||
66 | + components: { | ||
67 | + Button, | ||
68 | + Form, | ||
69 | + FormItem: Form.Item, | ||
70 | + Input, | ||
71 | + Divider, | ||
72 | + InputPassword: Input.Password, | ||
73 | + Checkbox, | ||
74 | + StrengthMeter, | ||
75 | + CountdownInput, | ||
76 | + }, | ||
77 | + setup() { | ||
78 | + const { t } = useI18n(); | ||
79 | + const { setLoginState } = useLoginState(); | ||
80 | + | ||
81 | + const formRef = ref<any>(null); | ||
82 | + const loading = ref(false); | ||
83 | + | ||
84 | + const formData = reactive({ | ||
85 | + account: '', | ||
86 | + password: '', | ||
87 | + confirmPassword: '', | ||
88 | + mobile: '', | ||
89 | + sms: '', | ||
90 | + policy: false, | ||
91 | + }); | ||
92 | + | ||
93 | + const { getFormRules } = useFormRules(formData); | ||
94 | + const { validForm } = useFormValid(formRef); | ||
95 | + | ||
96 | + async function handleReset() { | ||
97 | + const data = await validForm(); | ||
98 | + if (!data) return; | ||
99 | + console.log(data); | ||
100 | + } | ||
101 | + | ||
102 | + function handleBackLogin() { | ||
103 | + setLoginState(LoginStateEnum.LOGIN); | ||
104 | + } | ||
105 | + | ||
106 | + return { | ||
107 | + t, | ||
108 | + formRef, | ||
109 | + formData, | ||
110 | + getFormRules, | ||
111 | + handleReset, | ||
112 | + loading, | ||
113 | + handleBackLogin, | ||
114 | + }; | ||
115 | + }, | ||
116 | + }); | ||
117 | +</script> |
src/views/sys/login/useLogin.ts
0 → 100644
1 | +import { RuleObject } from 'ant-design-vue/lib/form/interface'; | ||
2 | +import { ref, computed, unref, Ref } from 'vue'; | ||
3 | +import { useI18n } from '/@/hooks/web/useI18n'; | ||
4 | + | ||
5 | +export enum LoginStateEnum { | ||
6 | + LOGIN, | ||
7 | + REGISTER, | ||
8 | + RESET_PASSWORD, | ||
9 | + MOBILE, | ||
10 | + QR_CODE, | ||
11 | +} | ||
12 | + | ||
13 | +const currentState = ref(LoginStateEnum.LOGIN); | ||
14 | + | ||
15 | +export function useFormTitle() { | ||
16 | + const { t } = useI18n(); | ||
17 | + | ||
18 | + const getFormTitle = computed(() => { | ||
19 | + const titleObj = { | ||
20 | + [LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'), | ||
21 | + [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'), | ||
22 | + [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'), | ||
23 | + [LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'), | ||
24 | + [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'), | ||
25 | + }; | ||
26 | + return titleObj[unref(currentState)]; | ||
27 | + }); | ||
28 | + return { getFormTitle }; | ||
29 | +} | ||
30 | + | ||
31 | +export function useLoginState() { | ||
32 | + function setLoginState(state: LoginStateEnum) { | ||
33 | + currentState.value = state; | ||
34 | + } | ||
35 | + | ||
36 | + const getLoginState = computed(() => currentState.value); | ||
37 | + | ||
38 | + return { setLoginState, getLoginState }; | ||
39 | +} | ||
40 | + | ||
41 | +export function useShowLoginForm() { | ||
42 | + const getShowLogin = computed(() => unref(currentState) === LoginStateEnum.LOGIN); | ||
43 | + const getShowResetPassword = computed( | ||
44 | + () => unref(currentState) === LoginStateEnum.RESET_PASSWORD | ||
45 | + ); | ||
46 | + const getShowRegister = computed(() => unref(currentState) === LoginStateEnum.REGISTER); | ||
47 | + const getShowMobile = computed(() => unref(currentState) === LoginStateEnum.MOBILE); | ||
48 | + const getShowQrCode = computed(() => unref(currentState) === LoginStateEnum.QR_CODE); | ||
49 | + | ||
50 | + return { getShowLogin, getShowResetPassword, getShowRegister, getShowMobile, getShowQrCode }; | ||
51 | +} | ||
52 | + | ||
53 | +export function useFormValid<T extends Object = any>(formRef: Ref<any>) { | ||
54 | + async function validForm() { | ||
55 | + const form = unref(formRef); | ||
56 | + if (!form) return; | ||
57 | + const data = await form.validate(); | ||
58 | + return data as T; | ||
59 | + } | ||
60 | + | ||
61 | + return { validForm }; | ||
62 | +} | ||
63 | + | ||
64 | +export function useFormRules(formData?: Recordable) { | ||
65 | + const { t } = useI18n(); | ||
66 | + | ||
67 | + const getAccountFormRule = computed(() => createRule(t('sys.login.accountPlaceholder'))); | ||
68 | + const getPasswordFormRule = computed(() => createRule(t('sys.login.passwordPlaceholder'))); | ||
69 | + const getSmsFormRule = computed(() => createRule(t('sys.login.smsPlaceholder'))); | ||
70 | + const getMobileFormRule = computed(() => createRule(t('sys.login.mobilePlaceholder'))); | ||
71 | + | ||
72 | + const validatePolicy = async (_: RuleObject, value: boolean) => { | ||
73 | + return !value ? Promise.reject(t('sys.login.policyPlaceholder')) : Promise.resolve(); | ||
74 | + }; | ||
75 | + | ||
76 | + const validateConfirmPassword = (password: string) => { | ||
77 | + return async (_: RuleObject, value: string) => { | ||
78 | + if (!value) { | ||
79 | + return Promise.reject(t('sys.login.passwordPlaceholder')); | ||
80 | + } | ||
81 | + if (value !== password) { | ||
82 | + return Promise.reject(t('sys.login.diffPwd')); | ||
83 | + } | ||
84 | + return Promise.resolve(); | ||
85 | + }; | ||
86 | + }; | ||
87 | + | ||
88 | + const getFormRules = computed(() => { | ||
89 | + const accountFormRule = unref(getAccountFormRule); | ||
90 | + const passwordFormRule = unref(getPasswordFormRule); | ||
91 | + const smsFormRule = unref(getSmsFormRule); | ||
92 | + const mobileFormRule = unref(getMobileFormRule); | ||
93 | + | ||
94 | + const mobileRule = { | ||
95 | + sms: smsFormRule, | ||
96 | + mobile: mobileFormRule, | ||
97 | + }; | ||
98 | + switch (unref(currentState)) { | ||
99 | + case LoginStateEnum.REGISTER: | ||
100 | + return { | ||
101 | + account: accountFormRule, | ||
102 | + password: passwordFormRule, | ||
103 | + confirmPassword: [ | ||
104 | + { validator: validateConfirmPassword(formData?.password), trigger: 'change' }, | ||
105 | + ], | ||
106 | + policy: [{ validator: validatePolicy, trigger: 'change' }], | ||
107 | + ...mobileRule, | ||
108 | + }; | ||
109 | + case LoginStateEnum.RESET_PASSWORD: | ||
110 | + return { | ||
111 | + account: accountFormRule, | ||
112 | + ...mobileRule, | ||
113 | + }; | ||
114 | + case LoginStateEnum.MOBILE: | ||
115 | + return mobileRule; | ||
116 | + default: | ||
117 | + return { | ||
118 | + account: accountFormRule, | ||
119 | + password: passwordFormRule, | ||
120 | + }; | ||
121 | + } | ||
122 | + }); | ||
123 | + return { getFormRules }; | ||
124 | +} | ||
125 | + | ||
126 | +function createRule(message: string) { | ||
127 | + return [ | ||
128 | + { | ||
129 | + required: true, | ||
130 | + message, | ||
131 | + trigger: 'change', | ||
132 | + }, | ||
133 | + ]; | ||
134 | +} |
tailwind.config.js deleted
100644 → 0
1 | -/* eslint-disable @typescript-eslint/no-var-requires */ | ||
2 | -const colors = require('windicss/colors'); | ||
3 | -const defaultTheme = require('windicss/defaultTheme'); | ||
4 | -module.exports = { | ||
5 | - darkMode: 'class', | ||
6 | - plugins: [ | ||
7 | - require('windicss/plugin/forms'), | ||
8 | - require('windicss/plugin/typography'), | ||
9 | - require('windicss/plugin/line-clamp'), | ||
10 | - require('windicss/plugin/aspect-ratio'), | ||
11 | - ], | ||
12 | - theme: { | ||
13 | - extend: { | ||
14 | - colors, | ||
15 | - fontFamily: { | ||
16 | - sans: ['Righteous', ...defaultTheme.fontFamily.sans], | ||
17 | - }, | ||
18 | - }, | ||
19 | - }, | ||
20 | -}; |
windi.config.ts
0 → 100644
1 | +import lineClamp from 'windicss/plugin/line-clamp'; | ||
2 | +import colors from 'windicss/colors'; | ||
3 | + | ||
4 | +import { defineConfig } from 'vite-plugin-windicss'; | ||
5 | + | ||
6 | +export default defineConfig({ | ||
7 | + darkMode: 'class', | ||
8 | + plugins: [lineClamp, createEnterPlugin()], | ||
9 | + theme: { | ||
10 | + extend: { | ||
11 | + colors, | ||
12 | + }, | ||
13 | + | ||
14 | + // screen: { | ||
15 | + // sm: '576px', | ||
16 | + // md: '768px', | ||
17 | + // lg: '992px', | ||
18 | + // xl: '1200px', | ||
19 | + // '2xl': '1600px', | ||
20 | + // }, | ||
21 | + }, | ||
22 | +}); | ||
23 | + | ||
24 | +/** | ||
25 | + * Used for animation when the element is displayed | ||
26 | + * @param maxOutput The larger the maxOutput output, the larger the generated css volume | ||
27 | + */ | ||
28 | +function createEnterPlugin(maxOutput = 10) { | ||
29 | + const createCss = (index: number, d = 'x') => { | ||
30 | + const upd = d.toUpperCase(); | ||
31 | + return { | ||
32 | + [`*> .enter-${d}:nth-child(${index})`]: { | ||
33 | + transform: `translate${upd}(50px)`, | ||
34 | + }, | ||
35 | + [`*> .-enter-${d}:nth-child(${index})`]: { | ||
36 | + transform: `translate${upd}(-50px)`, | ||
37 | + }, | ||
38 | + [`* > .enter-${d}:nth-child(${index}),* > .-enter-${d}:nth-child(${index})`]: { | ||
39 | + 'z-index': `${10 - index}`, | ||
40 | + opacity: '0', | ||
41 | + animation: `enter-${d}-animation 0.4s ease-in-out 0.3s`, | ||
42 | + 'animation-fill-mode': 'forwards', | ||
43 | + 'animation-delay': `${(index * 1) / 10}s`, | ||
44 | + }, | ||
45 | + }; | ||
46 | + }; | ||
47 | + const handler = ({ addBase }) => { | ||
48 | + for (let index = 1; index < maxOutput; index++) { | ||
49 | + addBase({ | ||
50 | + ...createCss(index, 'x'), | ||
51 | + ...createCss(index, 'y'), | ||
52 | + }); | ||
53 | + } | ||
54 | + | ||
55 | + addBase({ | ||
56 | + [`@keyframes enter-x-animation`]: { | ||
57 | + to: { | ||
58 | + opacity: '1', | ||
59 | + transform: 'translateX(0)', | ||
60 | + }, | ||
61 | + }, | ||
62 | + [`@keyframes enter-y-animation`]: { | ||
63 | + to: { | ||
64 | + opacity: '1', | ||
65 | + transform: 'translateY(0)', | ||
66 | + }, | ||
67 | + }, | ||
68 | + }); | ||
69 | + }; | ||
70 | + return { handler }; | ||
71 | +} |
yarn.lock
@@ -1936,14 +1936,16 @@ | @@ -1936,14 +1936,16 @@ | ||
1936 | dependencies: | 1936 | dependencies: |
1937 | vue-demi latest | 1937 | vue-demi latest |
1938 | 1938 | ||
1939 | -"@windicss/plugin-utils@0.3.12": | ||
1940 | - version "0.3.12" | ||
1941 | - resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.3.12.tgz#69b55be1ffb45753e6f01aa236f5ecd8df7a92ee" | ||
1942 | - integrity sha512-XA+xeyu5KM322dIp+EEHeXnAPuK+KxuWyoGvJnxXi9U50nIp0QraqXAH7xl9ghIkVHvVrb8pmm8vHpzFvsqF2A== | 1939 | +"@windicss/plugin-utils@0.4.3": |
1940 | + version "0.4.3" | ||
1941 | + resolved "https://registry.npmjs.org/@windicss/plugin-utils/-/plugin-utils-0.4.3.tgz#84e85fd3cd6eaf54ca72cae276f9cf0610f45e56" | ||
1942 | + integrity sha512-ilddLED+sZQIA9rOwE5eYwdBEBWKREvAVkkiAOOTNf7oDcP/a1cxT3f/nE4tgfhz+MC/FKcy7NkfrqfXRdEQaQ== | ||
1943 | dependencies: | 1943 | dependencies: |
1944 | + esbuild "^0.8.49" | ||
1945 | + esbuild-register "^2.0.0" | ||
1944 | fast-glob "^3.2.5" | 1946 | fast-glob "^3.2.5" |
1945 | micromatch "^4.0.2" | 1947 | micromatch "^4.0.2" |
1946 | - windicss "^2.1.12" | 1948 | + windicss "^2.1.15" |
1947 | 1949 | ||
1948 | "@zxcvbn-ts/core@^0.2.0": | 1950 | "@zxcvbn-ts/core@^0.2.0": |
1949 | version "0.2.0" | 1951 | version "0.2.0" |
@@ -3870,6 +3872,11 @@ esbuild@^0.8.48: | @@ -3870,6 +3872,11 @@ esbuild@^0.8.48: | ||
3870 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.48.tgz#a57e4dde84ec56da1c6ecaefee97e9da6c5b00b5" | 3872 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.48.tgz#a57e4dde84ec56da1c6ecaefee97e9da6c5b00b5" |
3871 | integrity sha512-lrH8lA8wWQ6Lpe1z6C7ZZaFSmRsUlcQAqe16nf7ITySQ7MV4+vI7qAqQlT/u+c3+9AL3VXmT4MXTxV2e63pO4A== | 3873 | integrity sha512-lrH8lA8wWQ6Lpe1z6C7ZZaFSmRsUlcQAqe16nf7ITySQ7MV4+vI7qAqQlT/u+c3+9AL3VXmT4MXTxV2e63pO4A== |
3872 | 3874 | ||
3875 | +esbuild@^0.8.49: | ||
3876 | + version "0.8.49" | ||
3877 | + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.49.tgz#3d33f71b3966611f822cf4c838115f3fbd16def2" | ||
3878 | + integrity sha512-itiFVYv5UZz4NooO7/Y0bRGVDGz/M/cxKbl6zyNI5pnKaz1mZjvZXAFhhDVz6rGCmcdTKj5oag6rh8DaaSSmfQ== | ||
3879 | + | ||
3873 | escalade@^3.1.1: | 3880 | escalade@^3.1.1: |
3874 | version "3.1.1" | 3881 | version "3.1.1" |
3875 | resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" | 3882 | resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" |
@@ -8454,10 +8461,10 @@ stylelint-order@^4.1.0: | @@ -8454,10 +8461,10 @@ stylelint-order@^4.1.0: | ||
8454 | postcss "^7.0.31" | 8461 | postcss "^7.0.31" |
8455 | postcss-sorting "^5.0.1" | 8462 | postcss-sorting "^5.0.1" |
8456 | 8463 | ||
8457 | -stylelint@^13.10.0: | ||
8458 | - version "13.10.0" | ||
8459 | - resolved "https://registry.npmjs.org/stylelint/-/stylelint-13.10.0.tgz#67b0c6f378c3fa61aa569a55d38feb8570b0b587" | ||
8460 | - integrity sha512-eDuLrL0wzPKbl5/TbNGZcbw0lTIGbDEr5W6lCODvb1gAg0ncbgCRt7oU0C2VFDvbrcY0A3MFZOwltwTRmc0XCw== | 8464 | +stylelint@^13.11.0: |
8465 | + version "13.11.0" | ||
8466 | + resolved "https://registry.npmjs.org/stylelint/-/stylelint-13.11.0.tgz#591981fbdd68c9d3d3e6147a0cd6a07539fc216d" | ||
8467 | + integrity sha512-DhrKSWDWGZkCiQMtU+VroXM6LWJVC8hSK24nrUngTSQvXGK75yZUq4yNpynqrxD3a/fzKMED09V+XxO4z4lTbw== | ||
8461 | dependencies: | 8468 | dependencies: |
8462 | "@stylelint/postcss-css-in-js" "^0.37.2" | 8469 | "@stylelint/postcss-css-in-js" "^0.37.2" |
8463 | "@stylelint/postcss-markdown" "^0.36.2" | 8470 | "@stylelint/postcss-markdown" "^0.36.2" |
@@ -9262,17 +9269,17 @@ vite-plugin-purge-icons@^0.7.0: | @@ -9262,17 +9269,17 @@ vite-plugin-purge-icons@^0.7.0: | ||
9262 | "@purge-icons/generated" "^0.7.0" | 9269 | "@purge-icons/generated" "^0.7.0" |
9263 | rollup-plugin-purge-icons "^0.7.0" | 9270 | rollup-plugin-purge-icons "^0.7.0" |
9264 | 9271 | ||
9265 | -vite-plugin-pwa@^0.5.1: | ||
9266 | - version "0.5.1" | ||
9267 | - resolved "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.5.1.tgz#7f94b8c4092ba0bba0a1bceb690e7420b18071e7" | ||
9268 | - integrity sha512-hf8BgyH0XLNEJUoMsk7ywMoE+OoQelK/+4RQoftQomZhlKXgsTWrfshFGOV7sKUbLsxMh0cVoh1DmAulQmRzKQ== | 9272 | +vite-plugin-pwa@^0.5.2: |
9273 | + version "0.5.2" | ||
9274 | + resolved "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.5.2.tgz#48131ebadc0c98c34a543dbf1bb1c86aeef532e0" | ||
9275 | + integrity sha512-4SHKxYhd5sCF/ebbgxGYlN91UHlylzh7C32a5+Y2c2vbrWzw5x62ZxsYzolQzBosdOim4Ez+e/dX4hmP3BCmow== | ||
9269 | dependencies: | 9276 | dependencies: |
9270 | debug "^4.3.2" | 9277 | debug "^4.3.2" |
9271 | fast-glob "^3.2.5" | 9278 | fast-glob "^3.2.5" |
9272 | pretty-bytes "^5.5.0" | 9279 | pretty-bytes "^5.5.0" |
9273 | workbox-build "^6.1.0" | 9280 | workbox-build "^6.1.0" |
9274 | 9281 | ||
9275 | -vite-plugin-style-import@^0.7.2: | 9282 | +vite-plugin-style-import@^0.7.3: |
9276 | version "0.7.3" | 9283 | version "0.7.3" |
9277 | resolved "https://registry.npmjs.org/vite-plugin-style-import/-/vite-plugin-style-import-0.7.3.tgz#4a9fb08bf5f2fc4796391c9be9a587ecb5c97e9e" | 9284 | resolved "https://registry.npmjs.org/vite-plugin-style-import/-/vite-plugin-style-import-0.7.3.tgz#4a9fb08bf5f2fc4796391c9be9a587ecb5c97e9e" |
9278 | integrity sha512-oKM6vOl7iWaB5U1HcR5oM1oPRhT1n5yJt7h4h9jwpMPCD5ckHPcSjjSU7ZlOJkoS/IWEnDkQqoZi162bOs1rTQ== | 9285 | integrity sha512-oKM6vOl7iWaB5U1HcR5oM1oPRhT1n5yJt7h4h9jwpMPCD5ckHPcSjjSU7ZlOJkoS/IWEnDkQqoZi162bOs1rTQ== |
@@ -9295,13 +9302,13 @@ vite-plugin-theme@^0.4.3: | @@ -9295,13 +9302,13 @@ vite-plugin-theme@^0.4.3: | ||
9295 | es-module-lexer "^0.3.26" | 9302 | es-module-lexer "^0.3.26" |
9296 | tinycolor2 "^1.4.2" | 9303 | tinycolor2 "^1.4.2" |
9297 | 9304 | ||
9298 | -vite-plugin-windicss@0.3.12: | ||
9299 | - version "0.3.12" | ||
9300 | - resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.3.12.tgz#5503b4ee738268a37c857c0cf55cea41f28fa3e6" | ||
9301 | - integrity sha512-NuzIjSrqBQKvpbLJoU9qi8PIWBBXCqBmuLg9Dl/cFl4MB/vAHIOB6sZYJatCBFTU39Kw4UU0GhAjDBSNqzTn0w== | 9305 | +vite-plugin-windicss@0.4.3: |
9306 | + version "0.4.3" | ||
9307 | + resolved "https://registry.npmjs.org/vite-plugin-windicss/-/vite-plugin-windicss-0.4.3.tgz#f86e5a3b78882caa3cdd50cba2b08770e2d627c8" | ||
9308 | + integrity sha512-Lnv6OhcYzcJvecTs4LIpMSfo54rSewkHrW85IVwy8hacR0krY319jXr5nwiDpSTp6HM3QJhoJ4zxHF+t5Q+Nwg== | ||
9302 | dependencies: | 9309 | dependencies: |
9303 | - "@windicss/plugin-utils" "0.3.12" | ||
9304 | - windicss "^2.1.12" | 9310 | + "@windicss/plugin-utils" "0.4.3" |
9311 | + windicss "^2.1.15" | ||
9305 | 9312 | ||
9306 | vite@2.0.1: | 9313 | vite@2.0.1: |
9307 | version "2.0.1" | 9314 | version "2.0.1" |
@@ -9356,10 +9363,10 @@ vue-i18n@9.0.0-rc.2: | @@ -9356,10 +9363,10 @@ vue-i18n@9.0.0-rc.2: | ||
9356 | "@intlify/shared" "9.0.0-rc.2" | 9363 | "@intlify/shared" "9.0.0-rc.2" |
9357 | "@vue/devtools-api" "^6.0.0-beta.3" | 9364 | "@vue/devtools-api" "^6.0.0-beta.3" |
9358 | 9365 | ||
9359 | -vue-router@^4.0.3: | ||
9360 | - version "4.0.3" | ||
9361 | - resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.3.tgz#8b26050c88b2dec7e27a88835f71046b365823ec" | ||
9362 | - integrity sha512-AD1OjtVPyQHTSpoRsEGfPpxRQwhAhxcacOYO3zJ3KNkYP/r09mileSp6kdMQKhZWP2cFsPR3E2M3PZguSN5/ww== | 9366 | +vue-router@^4.0.4: |
9367 | + version "4.0.4" | ||
9368 | + resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.4.tgz#ad9b4b7bbdad622407b4ff189b1646f48c1e9053" | ||
9369 | + integrity sha512-uN6PDEaYdU9aRO7mU+Dkr1uaY49hV3fucEDG/Vre/Qj8ct3RoJS16vcPrvKVzn69zDDjBV5b9Xw7fZA9r6b/Iw== | ||
9363 | 9370 | ||
9364 | vue-types@^3.0.0: | 9371 | vue-types@^3.0.0: |
9365 | version "3.0.1" | 9372 | version "3.0.1" |
@@ -9434,10 +9441,10 @@ which@^2.0.1: | @@ -9434,10 +9441,10 @@ which@^2.0.1: | ||
9434 | dependencies: | 9441 | dependencies: |
9435 | isexe "^2.0.0" | 9442 | isexe "^2.0.0" |
9436 | 9443 | ||
9437 | -windicss@^2.1.12: | ||
9438 | - version "2.1.12" | ||
9439 | - resolved "https://registry.npmjs.org/windicss/-/windicss-2.1.12.tgz#840b963f03af7a3e31b989d2b51de52dcd57a37a" | ||
9440 | - integrity sha512-VC057iG65zlvdqUI+1ynzOuKikalvYg6XqPGbG17HEAfwQ0sg1dACTk2plEp1QAEQNtKU3BnLnueWa4oKlltEQ== | 9444 | +windicss@^2.1.15: |
9445 | + version "2.1.15" | ||
9446 | + resolved "https://registry.npmjs.org/windicss/-/windicss-2.1.15.tgz#0a5bf1a56711ab53de8093a3c855764d93ffac00" | ||
9447 | + integrity sha512-gBihXNJPzv/kBaelOlXvbrmWsWuv98OPSf/yUYjc8EnRGCOxDOQIRin4FYPTWCmZi91PZThh7nMjzQZiBV+MYg== | ||
9441 | 9448 | ||
9442 | wmf@~1.0.1: | 9449 | wmf@~1.0.1: |
9443 | version "1.0.2" | 9450 | version "1.0.2" |