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 | 2 | "recommendations": [ |
3 | - "johnsoncodehk.volar", | |
4 | 3 | "octref.vetur", |
5 | 4 | "dbaeumer.vscode-eslint", |
6 | 5 | "stylelint.vscode-stylelint", |
7 | - "DavidAnson.vscode-markdownlint", | |
8 | 6 | "esbenp.prettier-vscode", |
9 | 7 | "mrmlnc.vscode-less", |
10 | 8 | "antfu.i18n-ally", |
11 | - "cpylua.language-postcss", | |
12 | - "Orta.vscode-jest", | |
13 | 9 | "antfu.iconify", |
14 | 10 | "mikestead.dotenv", |
15 | 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
build/config/themeConfig.ts
... | ... | @@ -94,7 +94,7 @@ export function generateModifyVars() { |
94 | 94 | 'disabled-color': 'rgba(0, 0, 0, 0.25)', // Failure color |
95 | 95 | 'heading-color': 'rgba(0, 0, 0, 0.85)', // Title color |
96 | 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 | 98 | 'font-size-base': '14px', // Main font size |
99 | 99 | 'box-shadow-base': '0 2px 8px rgba(0, 0, 0, 0.15)', // Floating shadow |
100 | 100 | 'border-color-base': '#d9d9d9', // Border color, | ... | ... |
package.json
... | ... | @@ -43,7 +43,7 @@ |
43 | 43 | "vditor": "^3.8.1", |
44 | 44 | "vue": "^3.0.5", |
45 | 45 | "vue-i18n": "9.0.0-rc.2", |
46 | - "vue-router": "^4.0.3", | |
46 | + "vue-router": "^4.0.4", | |
47 | 47 | "vue-types": "^3.0.2", |
48 | 48 | "vuex": "^4.0.0", |
49 | 49 | "vuex-module-decorators": "^1.0.1", |
... | ... | @@ -92,7 +92,7 @@ |
92 | 92 | "pretty-quick": "^3.1.0", |
93 | 93 | "rimraf": "^3.0.2", |
94 | 94 | "rollup-plugin-visualizer": "^4.2.0", |
95 | - "stylelint": "^13.10.0", | |
95 | + "stylelint": "^13.11.0", | |
96 | 96 | "stylelint-config-prettier": "^8.0.2", |
97 | 97 | "stylelint-config-standard": "^20.0.0", |
98 | 98 | "stylelint-order": "^4.1.0", |
... | ... | @@ -104,10 +104,10 @@ |
104 | 104 | "vite-plugin-imagemin": "^0.2.6", |
105 | 105 | "vite-plugin-mock": "^2.1.4", |
106 | 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 | 109 | "vite-plugin-theme": "^0.4.3", |
110 | - "vite-plugin-windicss": "0.3.12", | |
110 | + "vite-plugin-windicss": "0.4.3", | |
111 | 111 | "vue-eslint-parser": "^7.5.0", |
112 | 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 | 2 | \ No newline at end of file | ... | ... |
src/components/Application/src/AppLogo.vue
... | ... | @@ -10,8 +10,13 @@ |
10 | 10 | > |
11 | 11 | <img src="../../../assets/images/logo.png" /> |
12 | 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 | 20 | v-show="showTitle" |
16 | 21 | > |
17 | 22 | {{ title }} |
... | ... | @@ -38,6 +43,7 @@ |
38 | 43 | theme: propTypes.oneOf(['light', 'dark']), |
39 | 44 | // Whether to show title |
40 | 45 | showTitle: propTypes.bool.def(true), |
46 | + alwaysShowTitle: propTypes.bool.def(false), | |
41 | 47 | }, |
42 | 48 | setup() { |
43 | 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 | 1 | <template> |
2 | - <div :class="prefixCls"> | |
2 | + <div :class="prefixCls" class="relative"> | |
3 | 3 | <InputPassword |
4 | 4 | v-if="showInput" |
5 | 5 | v-bind="$attrs" |
... | ... | @@ -24,15 +24,14 @@ |
24 | 24 | import { Input } from 'ant-design-vue'; |
25 | 25 | |
26 | 26 | import zxcvbn from '@zxcvbn-ts/core'; |
27 | - import { propTypes } from '/@/utils/propTypes'; | |
28 | 27 | import { useDesign } from '/@/hooks/web/useDesign'; |
28 | + import { propTypes } from '/@/utils/propTypes'; | |
29 | 29 | |
30 | 30 | export default defineComponent({ |
31 | 31 | name: 'StrengthMeter', |
32 | 32 | components: { InputPassword: Input.Password }, |
33 | 33 | props: { |
34 | 34 | value: propTypes.string, |
35 | - | |
36 | 35 | showInput: propTypes.bool.def(true), |
37 | 36 | disabled: propTypes.bool, |
38 | 37 | }, |
... | ... | @@ -43,9 +42,9 @@ |
43 | 42 | |
44 | 43 | const getPasswordStrength = computed(() => { |
45 | 44 | const { disabled } = props; |
46 | - if (disabled) return null; | |
45 | + if (disabled) return -1; | |
47 | 46 | const innerValue = unref(innerValueRef); |
48 | - const score = innerValue ? zxcvbn(unref(innerValueRef)).score : null; | |
47 | + const score = innerValue ? zxcvbn(unref(innerValueRef)).score : -1; | |
49 | 48 | emit('score-change', score); |
50 | 49 | return score; |
51 | 50 | }); |
... | ... | @@ -57,6 +56,7 @@ |
57 | 56 | watchEffect(() => { |
58 | 57 | innerValueRef.value = props.value || ''; |
59 | 58 | }); |
59 | + | |
60 | 60 | watch( |
61 | 61 | () => unref(innerValueRef), |
62 | 62 | (val) => { |
... | ... | @@ -77,14 +77,12 @@ |
77 | 77 | @prefix-cls: ~'@{namespace}-strength-meter'; |
78 | 78 | |
79 | 79 | .@{prefix-cls} { |
80 | - position: relative; | |
81 | - | |
82 | 80 | &-bar { |
83 | 81 | position: relative; |
84 | - height: 4px; | |
82 | + height: 6px; | |
85 | 83 | margin: 10px auto 6px; |
86 | 84 | background: @disabled-color; |
87 | - border-radius: 3px; | |
85 | + border-radius: 6px; | |
88 | 86 | |
89 | 87 | &::before, |
90 | 88 | &::after { | ... | ... |
src/design/ant/index.less
src/design/var/breakpoint.less
... | ... | @@ -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 | 5 | // Small screen / tablet |
10 | -@screen-sm: 576px; | |
11 | -@screen-sm-min: @screen-sm; | |
6 | +@screen-sm: 640px; | |
12 | 7 | |
13 | 8 | // Medium screen / desktop |
14 | 9 | @screen-md: 768px; |
15 | -@screen-md-min: @screen-md; | |
16 | 10 | |
17 | 11 | // Large screen / wide desktop |
18 | -@screen-lg: 992px; | |
19 | -@screen-lg-min: @screen-lg; | |
12 | +@screen-lg: 1024px; | |
20 | 13 | |
21 | 14 | // Extra large screen / full hd |
22 | -@screen-xl: 1200px; | |
23 | -@screen-xl-min: @screen-xl; | |
15 | +@screen-xl: 1280px; | |
24 | 16 | |
25 | 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 | 3 | <span :class="[prefixCls, `${prefixCls}--${theme}`]"> |
4 | 4 | <img :class="`${prefixCls}__header`" :src="headerImg" /> |
5 | 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 | 7 | </span> |
8 | 8 | </span> |
9 | 9 | ... | ... |
src/locales/lang/en/component/countdown.ts
0 → 100644
src/locales/lang/en/sys/login.ts
1 | 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 | 20 | // notify |
7 | 21 | loginSuccessTitle: 'Login successful', |
8 | 22 | loginSuccessDesc: 'Welcome back', |
9 | 23 | |
10 | 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 | 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 | 14 | loginButton: '登录', |
3 | - autoLogin: '自动登录', | |
4 | - forgetPassword: '忘记密码', | |
15 | + registerButton: '注册', | |
16 | + rememberMe: '记住我', | |
17 | + forgetPassword: '忘记密码?', | |
18 | + otherSignIn: '其他登录方式', | |
5 | 19 | |
6 | 20 | // notify |
7 | 21 | loginSuccessTitle: '登录成功', |
... | ... | @@ -10,4 +24,15 @@ export default { |
10 | 24 | // placeholder |
11 | 25 | accountPlaceholder: '请输入账号', |
12 | 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 | 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 | 12 | <LockOutlined /> |
5 | 13 | <span>{{ t('sys.lock.unlock') }}</span> |
6 | 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 | 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 | 25 | </div> |
16 | 26 | </div> |
17 | 27 | <transition name="fade-slide"> |
18 | 28 | <div :class="`${prefixCls}-entry`" v-show="!showDate"> |
19 | 29 | <div :class="`${prefixCls}-entry-content`"> |
20 | - <div :class="`${prefixCls}-entry__header`"> | |
30 | + <div :class="`${prefixCls}-entry__header enter-x`"> | |
21 | 31 | <img :src="headerImg" :class="`${prefixCls}-entry__header-img`" /> |
22 | 32 | <p :class="`${prefixCls}-entry__header-name`"> |
23 | 33 | {{ realName }} |
24 | 34 | </p> |
25 | 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 | 42 | {{ t('sys.lock.alert') }} |
29 | 43 | </span> |
30 | - <div :class="`${prefixCls}-entry__footer`"> | |
44 | + <div :class="`${prefixCls}-entry__footer enter-x`"> | |
31 | 45 | <a-button |
32 | 46 | type="link" |
33 | 47 | size="small" |
34 | - class="mt-2 mr-2" | |
48 | + class="mt-2 mr-2 enter-x" | |
35 | 49 | :disabled="loadingRef" |
36 | 50 | @click="handleShowForm(true)" |
37 | 51 | > |
... | ... | @@ -40,7 +54,7 @@ |
40 | 54 | <a-button |
41 | 55 | type="link" |
42 | 56 | size="small" |
43 | - class="mt-2 mr-2" | |
57 | + class="mt-2 mr-2 enter-x" | |
44 | 58 | :disabled="loadingRef" |
45 | 59 | @click="goLogin" |
46 | 60 | > |
... | ... | @@ -54,11 +68,11 @@ |
54 | 68 | </div> |
55 | 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 | 74 | </div> |
61 | - <div class="date"> {{ year }}/{{ month }}/{{ day }} {{ week }} </div> | |
75 | + <div class="text-2xl"> {{ year }}/{{ month }}/{{ day }} {{ week }} </div> | |
62 | 76 | </div> |
63 | 77 | </div> |
64 | 78 | </template> |
... | ... | @@ -144,125 +158,54 @@ |
144 | 158 | @prefix-cls: ~'@{namespace}-lock-page'; |
145 | 159 | |
146 | 160 | .@{prefix-cls} { |
147 | - position: fixed; | |
148 | - top: 0; | |
149 | - right: 0; | |
150 | - bottom: 0; | |
151 | - left: 0; | |
152 | 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 | 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 | 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 | 167 | &__hour, |
202 | 168 | &__minute { |
203 | 169 | display: flex; |
204 | - width: 40%; | |
205 | - height: 74%; | |
206 | 170 | font-weight: 700; |
207 | 171 | color: #bababa; |
208 | 172 | background: #141313; |
209 | 173 | border-radius: 30px; |
210 | 174 | justify-content: center; |
211 | 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 | 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 | 43 | </div> |
58 | 44 | </div> |
59 | 45 | </div> |
60 | 46 | </div> |
61 | 47 | </template> |
62 | 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 | 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 | 59 | import { useGlobSetting, useProjectSetting } from '/@/hooks/setting'; |
73 | - import logo from '/@/assets/images/logo.png'; | |
74 | 60 | import { useI18n } from '/@/hooks/web/useI18n'; |
61 | + import { useDesign } from '/@/hooks/web/useDesign'; | |
62 | + import { useShowLoginForm, useFormTitle } from './useLogin'; | |
75 | 63 | |
76 | 64 | export default defineComponent({ |
65 | + name: 'Login', | |
77 | 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 | 73 | AppLocalePicker, |
85 | - [Row.name]: Row, | |
86 | - [Col.name]: Col, | |
87 | 74 | }, |
88 | 75 | setup() { |
89 | - const formRef = ref<any>(null); | |
90 | - const autoLoginRef = ref(false); | |
91 | - | |
92 | 76 | const globSetting = useGlobSetting(); |
77 | + const { getFormTitle } = useFormTitle(); | |
78 | + const { prefixCls } = useDesign('login'); | |
93 | 79 | const { locale } = useProjectSetting(); |
94 | - const { notification } = useMessage(); | |
95 | 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 | 82 | return { |
138 | - formRef, | |
139 | - formData, | |
140 | - formState, | |
141 | - formRules, | |
142 | - login: handleLogin, | |
143 | - autoLogin: autoLoginRef, | |
144 | - title: globSetting && globSetting.title, | |
145 | - logo, | |
146 | 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 | 92 | </script> |
152 | 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 | 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 | 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 | 1936 | dependencies: |
1937 | 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 | 1943 | dependencies: |
1944 | + esbuild "^0.8.49" | |
1945 | + esbuild-register "^2.0.0" | |
1944 | 1946 | fast-glob "^3.2.5" |
1945 | 1947 | micromatch "^4.0.2" |
1946 | - windicss "^2.1.12" | |
1948 | + windicss "^2.1.15" | |
1947 | 1949 | |
1948 | 1950 | "@zxcvbn-ts/core@^0.2.0": |
1949 | 1951 | version "0.2.0" |
... | ... | @@ -3870,6 +3872,11 @@ esbuild@^0.8.48: |
3870 | 3872 | resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.48.tgz#a57e4dde84ec56da1c6ecaefee97e9da6c5b00b5" |
3871 | 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 | 3880 | escalade@^3.1.1: |
3874 | 3881 | version "3.1.1" |
3875 | 3882 | resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" |
... | ... | @@ -8454,10 +8461,10 @@ stylelint-order@^4.1.0: |
8454 | 8461 | postcss "^7.0.31" |
8455 | 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 | 8468 | dependencies: |
8462 | 8469 | "@stylelint/postcss-css-in-js" "^0.37.2" |
8463 | 8470 | "@stylelint/postcss-markdown" "^0.36.2" |
... | ... | @@ -9262,17 +9269,17 @@ vite-plugin-purge-icons@^0.7.0: |
9262 | 9269 | "@purge-icons/generated" "^0.7.0" |
9263 | 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 | 9276 | dependencies: |
9270 | 9277 | debug "^4.3.2" |
9271 | 9278 | fast-glob "^3.2.5" |
9272 | 9279 | pretty-bytes "^5.5.0" |
9273 | 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 | 9283 | version "0.7.3" |
9277 | 9284 | resolved "https://registry.npmjs.org/vite-plugin-style-import/-/vite-plugin-style-import-0.7.3.tgz#4a9fb08bf5f2fc4796391c9be9a587ecb5c97e9e" |
9278 | 9285 | integrity sha512-oKM6vOl7iWaB5U1HcR5oM1oPRhT1n5yJt7h4h9jwpMPCD5ckHPcSjjSU7ZlOJkoS/IWEnDkQqoZi162bOs1rTQ== |
... | ... | @@ -9295,13 +9302,13 @@ vite-plugin-theme@^0.4.3: |
9295 | 9302 | es-module-lexer "^0.3.26" |
9296 | 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 | 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 | 9313 | vite@2.0.1: |
9307 | 9314 | version "2.0.1" |
... | ... | @@ -9356,10 +9363,10 @@ vue-i18n@9.0.0-rc.2: |
9356 | 9363 | "@intlify/shared" "9.0.0-rc.2" |
9357 | 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 | 9371 | vue-types@^3.0.0: |
9365 | 9372 | version "3.0.1" |
... | ... | @@ -9434,10 +9441,10 @@ which@^2.0.1: |
9434 | 9441 | dependencies: |
9435 | 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 | 9449 | wmf@~1.0.1: |
9443 | 9450 | version "1.0.2" | ... | ... |