create-pubinfo 2.0.0-beta.9 → 2.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +352 -306
- package/package.json +13 -13
- package/templates/pubinfo-template/.browserslistrc +5 -0
- package/templates/pubinfo-template/.editorconfig +10 -0
- package/templates/pubinfo-template/.env +5 -0
- package/templates/pubinfo-template/.env.development +6 -0
- package/templates/pubinfo-template/.env.production +10 -0
- package/templates/pubinfo-template/_gitignore +37 -0
- package/templates/pubinfo-template/_npmrc +5 -0
- package/templates/pubinfo-template/eslint.config.ts +3 -0
- package/templates/pubinfo-template/index.html +47 -0
- package/templates/pubinfo-template/openapi.config.ts +33 -0
- package/templates/pubinfo-template/package.json +52 -0
- package/templates/pubinfo-template/pubinfo.config.ts +9 -0
- package/templates/pubinfo-template/public/browser_upgrade/chrome.png +0 -0
- package/templates/pubinfo-template/public/browser_upgrade/edge.png +0 -0
- package/templates/pubinfo-template/public/browser_upgrade/index.css +49 -0
- package/templates/pubinfo-template/public/loading.css +92 -0
- package/templates/pubinfo-template/src/App.vue +7 -0
- package/templates/pubinfo-template/src/api/modules/auth/index.ts +3 -0
- package/templates/pubinfo-template/src/api/modules/auth/renzhengfuwu.ts +145 -0
- package/templates/pubinfo-template/src/api/modules/auth/typings.d.ts +97 -0
- package/templates/pubinfo-template/src/api/request.ts +125 -0
- package/templates/pubinfo-template/src/assets/icons/logo.svg +1 -0
- package/templates/pubinfo-template/src/assets/icons/process-management.svg +1 -0
- package/templates/pubinfo-template/src/assets/icons/workbench.svg +1 -0
- package/templates/pubinfo-template/src/assets/images/login-bg.webp +0 -0
- package/templates/pubinfo-template/src/assets/images/login-bg_dark.webp +0 -0
- package/templates/pubinfo-template/src/assets/images/login-small.png +0 -0
- package/templates/pubinfo-template/src/assets/images/login-small_dark.webp +0 -0
- package/templates/pubinfo-template/src/components/UIProvider/index.vue +51 -0
- package/templates/pubinfo-template/src/layouts/index.vue +38 -0
- package/templates/pubinfo-template/src/main.ts +22 -0
- package/templates/pubinfo-template/src/modules/auth.ts +20 -0
- package/templates/pubinfo-template/src/modules/rbac.ts +10 -0
- package/templates/pubinfo-template/src/routes/index.ts +71 -0
- package/templates/pubinfo-template/src/routes/modules/demo/breadcrumb.example.ts +62 -0
- package/templates/pubinfo-template/src/routes/modules/demo/link.ts +15 -0
- package/templates/pubinfo-template/src/routes/modules/demo/multilevel.menu.example.ts +68 -0
- package/templates/pubinfo-template/src/routes/modules/demo/other.page.ts +37 -0
- package/templates/pubinfo-template/src/routes/modules/demo/single.ts +14 -0
- package/templates/pubinfo-template/src/settings.ts +8 -0
- package/templates/pubinfo-template/src/stores/index.ts +2 -0
- package/templates/pubinfo-template/src/stores/modules/conter.ts +16 -0
- package/templates/pubinfo-template/src/views/demo/breadcrumb_example/detail1.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/breadcrumb_example/detail2.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/breadcrumb_example/list1.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/breadcrumb_example/list2.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/level2/level3/page1.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/level2/level3/page2.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/level2/page.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/multilevel_menu_example/page.vue +11 -0
- package/templates/pubinfo-template/src/views/demo/other_page/des.vue +13 -0
- package/templates/pubinfo-template/src/views/demo/other_page/index.vue +25 -0
- package/templates/pubinfo-template/src/views/demo/preview-empty/index.vue +17 -0
- package/templates/pubinfo-template/src/views/demo/single/index.vue +13 -0
- package/templates/pubinfo-template/src/views/system/index.vue +5 -0
- package/templates/pubinfo-template/src/views/system/login/components/LoginForm.vue +29 -0
- package/templates/pubinfo-template/src/views/system/login/components/LoginWithPhone.vue +213 -0
- package/templates/pubinfo-template/src/views/system/login/components/PasswordLogin.vue +194 -0
- package/templates/pubinfo-template/src/views/system/login/components/Savephone.vue +17 -0
- package/templates/pubinfo-template/src/views/system/login/components/Useragreement.vue +26 -0
- package/templates/pubinfo-template/src/views/system/login/composables.ts +84 -0
- package/templates/pubinfo-template/src/views/system/login/index.vue +142 -0
- package/templates/pubinfo-template/stylelint.config.js +3 -0
- package/templates/pubinfo-template/tsconfig.json +3 -0
- package/templates/pubinfo-template/uno.config.ts +17 -0
- package/dist/index.cjs +0 -364
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { PageMain } from 'pubinfo';
|
|
3
|
+
|
|
4
|
+
defineOptions({
|
|
5
|
+
name: 'OtherPage',
|
|
6
|
+
});
|
|
7
|
+
/* =============================================
|
|
8
|
+
= 逻辑代码 =
|
|
9
|
+
============================================= */
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
function jump() {
|
|
12
|
+
router.push({
|
|
13
|
+
name: 'OtherPageDetail',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<PageMain>
|
|
20
|
+
列表页
|
|
21
|
+
<a-button type="primary" @click="jump">
|
|
22
|
+
点击跳转入详情页
|
|
23
|
+
</a-button>
|
|
24
|
+
</PageMain>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/* =============================================
|
|
3
|
+
= Vue SFC Options 配置 =
|
|
4
|
+
============================================= */
|
|
5
|
+
defineOptions({
|
|
6
|
+
name: 'PreviewEmpty',
|
|
7
|
+
});
|
|
8
|
+
/* =============================================
|
|
9
|
+
= 逻辑代码 =
|
|
10
|
+
============================================= */
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<div>
|
|
15
|
+
空白展示页面
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useLoginTabs } from '../composables';
|
|
3
|
+
|
|
4
|
+
defineOptions({
|
|
5
|
+
name: 'LoginFrom',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const { tabs, initialTab, changeActiveTab } = useLoginTabs();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div class="flex flex-col w-456px [background:transparent]">
|
|
13
|
+
<div class="flex flex-row overflow-hidden border border-solid border-[#e7e8e9] rounded-1">
|
|
14
|
+
<div
|
|
15
|
+
v-for="(tab, index) in tabs"
|
|
16
|
+
:key="tab.title"
|
|
17
|
+
class="flex flex-row items-center justify-center h-10 text-base cursor-pointer rounded"
|
|
18
|
+
:class="[initialTab === index ? 'text-[#2f6bff] bg-[#ecf1fd]' : 'text-[#8a8e92] ']"
|
|
19
|
+
:style="{ width: `${100 / tabs.length}%` }"
|
|
20
|
+
@click="changeActiveTab(index)"
|
|
21
|
+
>
|
|
22
|
+
{{ tab.title }}
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="relative min-h-350px">
|
|
26
|
+
<component :is="tabs[initialTab].component" :key="tabs[initialTab].title" :name="tabs[initialTab].title" />
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SelectProps } from 'ant-design-vue';
|
|
3
|
+
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
|
4
|
+
import { useToggle } from '@vueuse/core';
|
|
5
|
+
import { message } from 'ant-design-vue';
|
|
6
|
+
import { useCountdown } from '../composables';
|
|
7
|
+
import Savephone from './Savephone.vue';
|
|
8
|
+
|
|
9
|
+
defineOptions({
|
|
10
|
+
name: 'LoginWithPhone',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
interface FormState {
|
|
14
|
+
phone: string
|
|
15
|
+
verify: string
|
|
16
|
+
checked: false
|
|
17
|
+
}
|
|
18
|
+
const [verifyDisabled, setVerifyDisabled] = useToggle(true);
|
|
19
|
+
const phoneForm = ref<FormInstance>();
|
|
20
|
+
const { count, start, isStart } = useCountdown();
|
|
21
|
+
const regionCode = ref<number>(0);
|
|
22
|
+
const router = useRouter();
|
|
23
|
+
const route = useRoute();
|
|
24
|
+
|
|
25
|
+
const formState = reactive<FormState>({
|
|
26
|
+
phone: '',
|
|
27
|
+
verify: '',
|
|
28
|
+
checked: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const rules: Record<string, Rule[]> = {
|
|
32
|
+
phone: [
|
|
33
|
+
{
|
|
34
|
+
validator: checkPhoneIsCorrect,
|
|
35
|
+
whitespace: true,
|
|
36
|
+
trigger: 'change',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
verify: [
|
|
40
|
+
{
|
|
41
|
+
required: true,
|
|
42
|
+
whitespace: true,
|
|
43
|
+
message: '请输入验证码',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const regionCodeOptins: SelectProps['options'] = [
|
|
49
|
+
{
|
|
50
|
+
value: 0,
|
|
51
|
+
label: '+86 (中国大陆)',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: 1,
|
|
55
|
+
label: '+852 (香港行政区)',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: 2,
|
|
59
|
+
label: '+853 (澳门行政区)',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
value: 3,
|
|
63
|
+
label: '+886 (台湾省)',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
async function checkPhoneIsCorrect(_rule: Rule, value: string) {
|
|
68
|
+
function reject(area: string) {
|
|
69
|
+
setVerifyDisabled(true);
|
|
70
|
+
return Promise.reject(new Error(`请输入正确的${area}手机号`));
|
|
71
|
+
}
|
|
72
|
+
if (!value) {
|
|
73
|
+
setVerifyDisabled(true);
|
|
74
|
+
return Promise.reject(new Error(`请输入手机号`));
|
|
75
|
+
}
|
|
76
|
+
switch (regionCode.value) {
|
|
77
|
+
// 中国大陆
|
|
78
|
+
case 0:
|
|
79
|
+
if (!/^1[3-9]\d{9}$/.test(value)) {
|
|
80
|
+
return reject('中国大陆');
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
// 香港特别行政区
|
|
84
|
+
case 1:
|
|
85
|
+
if (!/^([569])\d{7}$/.test(value)) {
|
|
86
|
+
return reject('香港特别行政区');
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
// 澳门特别行政区
|
|
90
|
+
case 2:
|
|
91
|
+
if (!/^6\d{7}$/.test(value)) {
|
|
92
|
+
return reject('澳门特别行政区');
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
// 台湾省
|
|
96
|
+
case 3:
|
|
97
|
+
if (!/^9\d{8}$/.test(value)) {
|
|
98
|
+
return reject('台湾省');
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
setVerifyDisabled(false);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function areaChange() {
|
|
108
|
+
if (!formState.phone) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
phoneForm.value?.validateFields('phone');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function sendVerifyCode() {
|
|
115
|
+
message.success('验证码已发送,请注意查收');
|
|
116
|
+
start();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function submit() {
|
|
120
|
+
try {
|
|
121
|
+
await phoneForm.value?.validate();
|
|
122
|
+
|
|
123
|
+
// TODO 实现手机号登录逻辑
|
|
124
|
+
|
|
125
|
+
if (formState.checked) {
|
|
126
|
+
localStorage.setItem('pubinfo_web_savedPhone', formState.phone);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
localStorage.removeItem('pubinfo_web_savedPhone');
|
|
130
|
+
}
|
|
131
|
+
message.success('登录成功');
|
|
132
|
+
router.push(route.query.redirect?.toString() ?? '/');
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onMounted(() => {
|
|
139
|
+
const phone = localStorage.getItem('pubinfo_web_savedPhone');
|
|
140
|
+
if (phone) {
|
|
141
|
+
formState.phone = phone;
|
|
142
|
+
setVerifyDisabled(false);
|
|
143
|
+
formState.checked = true as any;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<template>
|
|
149
|
+
<div class="phone-form" mt-40px>
|
|
150
|
+
<a-form
|
|
151
|
+
ref="phoneForm"
|
|
152
|
+
:model="formState"
|
|
153
|
+
autocomplete="off"
|
|
154
|
+
:rules="rules"
|
|
155
|
+
@finish="submit"
|
|
156
|
+
>
|
|
157
|
+
<a-form-item name="phone">
|
|
158
|
+
<a-input-group compact>
|
|
159
|
+
<a-form-item-rest>
|
|
160
|
+
<a-select
|
|
161
|
+
v-model:value="regionCode"
|
|
162
|
+
size="large"
|
|
163
|
+
style="width: 35%;"
|
|
164
|
+
:options="regionCodeOptins"
|
|
165
|
+
@change="areaChange"
|
|
166
|
+
/>
|
|
167
|
+
</a-form-item-rest>
|
|
168
|
+
<a-input
|
|
169
|
+
v-model:value="formState.phone"
|
|
170
|
+
allow-clear
|
|
171
|
+
size="large"
|
|
172
|
+
placeholder="请输入手机号"
|
|
173
|
+
style="width: 65%;"
|
|
174
|
+
/>
|
|
175
|
+
</a-input-group>
|
|
176
|
+
</a-form-item>
|
|
177
|
+
|
|
178
|
+
<a-form-item name="verify">
|
|
179
|
+
<a-input
|
|
180
|
+
v-model:value="formState.verify"
|
|
181
|
+
allow-clear
|
|
182
|
+
size="large"
|
|
183
|
+
placeholder="请输入验证码"
|
|
184
|
+
>
|
|
185
|
+
<template #suffix>
|
|
186
|
+
<a-button
|
|
187
|
+
size="small"
|
|
188
|
+
type="link"
|
|
189
|
+
:disabled="verifyDisabled"
|
|
190
|
+
>
|
|
191
|
+
<span v-if="!isStart" @click="sendVerifyCode">获取短信验证码</span>
|
|
192
|
+
<span v-else text="#8A8E92">{{ count }}秒后重新获取</span>
|
|
193
|
+
</a-button>
|
|
194
|
+
</template>
|
|
195
|
+
</a-input>
|
|
196
|
+
</a-form-item>
|
|
197
|
+
<a-form-item>
|
|
198
|
+
<Savephone v-model:checked="formState.checked" />
|
|
199
|
+
</a-form-item>
|
|
200
|
+
<a-form-item>
|
|
201
|
+
<a-button
|
|
202
|
+
class="h-48px!"
|
|
203
|
+
block
|
|
204
|
+
type="primary"
|
|
205
|
+
html-type="submit"
|
|
206
|
+
:disabled="!formState.checked"
|
|
207
|
+
>
|
|
208
|
+
登录
|
|
209
|
+
</a-button>
|
|
210
|
+
</a-form-item>
|
|
211
|
+
</a-form>
|
|
212
|
+
</div>
|
|
213
|
+
</template>
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
|
3
|
+
import type { UnwrapRef } from 'vue';
|
|
4
|
+
import { useToggle } from '@vueuse/core';
|
|
5
|
+
import { message, Modal } from 'ant-design-vue';
|
|
6
|
+
import { RESPONSE_CODE, useUserStore } from 'pubinfo';
|
|
7
|
+
import { useCaptchas, useFocusElement } from '../composables';
|
|
8
|
+
import Useragreement from './Useragreement.vue';
|
|
9
|
+
|
|
10
|
+
defineOptions({
|
|
11
|
+
name: 'PasswordLogin',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
interface FormState {
|
|
15
|
+
account: string
|
|
16
|
+
password: string
|
|
17
|
+
captchas: string
|
|
18
|
+
captchaHash: string
|
|
19
|
+
checked: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface HttpError {
|
|
23
|
+
code: RESPONSE_CODE
|
|
24
|
+
data: any
|
|
25
|
+
hint: string
|
|
26
|
+
msg: string
|
|
27
|
+
success: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isHttpError(error: unknown): error is HttpError {
|
|
31
|
+
return (error as HttpError).code !== undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const userStore = useUserStore();
|
|
35
|
+
const { send, captcha, captchaHash } = useCaptchas();
|
|
36
|
+
const router = useRouter();
|
|
37
|
+
const route = useRoute();
|
|
38
|
+
const { focusElementDataId } = useFocusElement();
|
|
39
|
+
|
|
40
|
+
const loginForm = ref<FormInstance | null>(null);
|
|
41
|
+
const formState: UnwrapRef<FormState> = reactive({
|
|
42
|
+
account: '',
|
|
43
|
+
password: '',
|
|
44
|
+
captchas: '',
|
|
45
|
+
checked: true,
|
|
46
|
+
captchaHash,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const rules: Record<string, Rule[]> = {
|
|
50
|
+
account: [{ required: true, whitespace: true, message: '请输入账号' }],
|
|
51
|
+
password: [{ required: true, whitespace: true, message: '请输入密码' }],
|
|
52
|
+
captchas: [{ required: true, whitespace: true, message: '请输入验证码' }],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const [loading, setLoading] = useToggle(false);
|
|
56
|
+
|
|
57
|
+
async function onSubmit() {
|
|
58
|
+
if (loading.value) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setLoading(true);
|
|
62
|
+
try {
|
|
63
|
+
await loginForm.value?.validate();
|
|
64
|
+
await userStore.login(formState);
|
|
65
|
+
message.success('登录成功');
|
|
66
|
+
router.push(route.query.redirect?.toString() ?? '/');
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (isHttpError(error)) {
|
|
70
|
+
const code = error.code;
|
|
71
|
+
switch (code) {
|
|
72
|
+
case RESPONSE_CODE.LOGINNAME_PASSWORD_WRONG:
|
|
73
|
+
case RESPONSE_CODE.CODE_OVERTIME:
|
|
74
|
+
case RESPONSE_CODE.CODE_WRONG:
|
|
75
|
+
send();
|
|
76
|
+
break;
|
|
77
|
+
case RESPONSE_CODE.CHANGE_INIT_PASSWORD:
|
|
78
|
+
case RESPONSE_CODE.PASSWORD_EXPIRED:
|
|
79
|
+
createChangePasswordModal(error.msg, error?.data?.accessToken);
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
setLoading(false);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createChangePasswordModal(msg: string, token: string) {
|
|
92
|
+
Modal.confirm({
|
|
93
|
+
title: '提示',
|
|
94
|
+
content() {
|
|
95
|
+
return msg;
|
|
96
|
+
},
|
|
97
|
+
okText: '修改密码',
|
|
98
|
+
cancelText: '暂不登录',
|
|
99
|
+
onOk() {
|
|
100
|
+
router.push({
|
|
101
|
+
name: 'ChangePassword',
|
|
102
|
+
params: {
|
|
103
|
+
changePassWordToken: token,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<template>
|
|
112
|
+
<div class="mt-10">
|
|
113
|
+
<AForm ref="loginForm" :model="formState" :rules="rules" @finish="onSubmit">
|
|
114
|
+
<a-form-item name="account">
|
|
115
|
+
<a-input
|
|
116
|
+
v-model:value="formState.account"
|
|
117
|
+
allow-clear
|
|
118
|
+
size="large"
|
|
119
|
+
placeholder="请输入登录账号"
|
|
120
|
+
data-id="account"
|
|
121
|
+
>
|
|
122
|
+
<template #prefix>
|
|
123
|
+
<PubinfoIcon v-if="focusElementDataId === 'account'" name="i-solar-user-bold" color="#1578FF" />
|
|
124
|
+
<PubinfoIcon v-else name="i-solar-user-broken" color="#AFB0B2" />
|
|
125
|
+
</template>
|
|
126
|
+
</a-input>
|
|
127
|
+
</a-form-item>
|
|
128
|
+
<a-form-item name="password">
|
|
129
|
+
<a-input-password
|
|
130
|
+
v-model:value="formState.password"
|
|
131
|
+
allow-clear
|
|
132
|
+
size="large"
|
|
133
|
+
placeholder="请输入登录密码"
|
|
134
|
+
visibility-toggle
|
|
135
|
+
data-id="password"
|
|
136
|
+
>
|
|
137
|
+
<template #prefix>
|
|
138
|
+
<PubinfoIcon v-if="focusElementDataId === 'password'" name="i-solar-lock-password-unlocked-bold" color="#1578FF" />
|
|
139
|
+
<PubinfoIcon v-else name="i-solar-lock-password-unlocked-broken" color="#AFB0B2" />
|
|
140
|
+
</template>
|
|
141
|
+
</a-input-password>
|
|
142
|
+
</a-form-item>
|
|
143
|
+
<a-form-item name="captchas">
|
|
144
|
+
<a-input
|
|
145
|
+
v-model:value="formState.captchas"
|
|
146
|
+
size="large"
|
|
147
|
+
allow-clear
|
|
148
|
+
placeholder="请输入验证码"
|
|
149
|
+
data-id="captchas"
|
|
150
|
+
>
|
|
151
|
+
<template #prefix>
|
|
152
|
+
<PubinfoIcon v-if="focusElementDataId === 'captchas'" name="i-solar-shield-check-bold" color="#1578FF" />
|
|
153
|
+
<PubinfoIcon v-else name="i-solar-shield-check-broken" color="#AFB0B2" />
|
|
154
|
+
</template>
|
|
155
|
+
<template #addonAfter>
|
|
156
|
+
<div
|
|
157
|
+
w-100px
|
|
158
|
+
h-38px
|
|
159
|
+
cursor-pointer
|
|
160
|
+
@click="send"
|
|
161
|
+
>
|
|
162
|
+
<img :src="captcha">
|
|
163
|
+
</div>
|
|
164
|
+
</template>
|
|
165
|
+
</a-input>
|
|
166
|
+
</a-form-item>
|
|
167
|
+
<a-form-item>
|
|
168
|
+
<Useragreement v-model:checked="formState.checked" />
|
|
169
|
+
</a-form-item>
|
|
170
|
+
<!-- 登录 -->
|
|
171
|
+
<a-form-item>
|
|
172
|
+
<div w-full h-48px>
|
|
173
|
+
<a-button
|
|
174
|
+
class="h-48px!"
|
|
175
|
+
type="primary"
|
|
176
|
+
html-type="submit"
|
|
177
|
+
block
|
|
178
|
+
:disabled="!formState.checked"
|
|
179
|
+
:loading="loading"
|
|
180
|
+
>
|
|
181
|
+
登录
|
|
182
|
+
</a-button>
|
|
183
|
+
</div>
|
|
184
|
+
</a-form-item>
|
|
185
|
+
</AForm>
|
|
186
|
+
</div>
|
|
187
|
+
</template>
|
|
188
|
+
|
|
189
|
+
<style scoped>
|
|
190
|
+
:deep(.ant-input-group-addon) {
|
|
191
|
+
padding: 0 !important;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
}
|
|
194
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineOptions({
|
|
3
|
+
name: 'Savephone',
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
const checked = defineModel(
|
|
7
|
+
'checked',
|
|
8
|
+
{
|
|
9
|
+
type: Boolean, default: false,
|
|
10
|
+
});
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<a-checkbox v-model:checked="checked">
|
|
15
|
+
记住手机号
|
|
16
|
+
</a-checkbox>
|
|
17
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineOptions({
|
|
3
|
+
name: 'Useragreement',
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
const checked = defineModel(
|
|
7
|
+
'checked',
|
|
8
|
+
{
|
|
9
|
+
type: Boolean, default: true,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
function jumpUseragreementPage() {
|
|
13
|
+
// 暂未实现用户协议、隐私政策
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<a-checkbox v-model:checked="checked">
|
|
19
|
+
我已阅读并同意<span
|
|
20
|
+
hover:underline
|
|
21
|
+
decoration-2
|
|
22
|
+
text="#1578FF"
|
|
23
|
+
@click.prevent="jumpUseragreementPage"
|
|
24
|
+
>用户协议、隐私政策</span>
|
|
25
|
+
</a-checkbox>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Component, MaybeRef } from 'vue';
|
|
2
|
+
import { getAuthLoginValidCode } from '@/api/modules/auth';
|
|
3
|
+
import { useActiveElement } from '@vueuse/core';
|
|
4
|
+
import { useRequest } from 'alova/client';
|
|
5
|
+
|
|
6
|
+
interface LoginTabs {
|
|
7
|
+
title: string
|
|
8
|
+
component: Component
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useLoginTabs() {
|
|
12
|
+
const tabs: LoginTabs[] = [
|
|
13
|
+
{
|
|
14
|
+
title: '账号登录',
|
|
15
|
+
component: defineAsyncComponent(() => import('./components/PasswordLogin.vue')),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: '手机号登录',
|
|
19
|
+
component: defineAsyncComponent(() => import('./components/LoginWithPhone.vue')),
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
const initialTab = ref(0);
|
|
23
|
+
|
|
24
|
+
function changeActiveTab(activeTab: number): void {
|
|
25
|
+
initialTab.value = activeTab;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
tabs,
|
|
29
|
+
initialTab,
|
|
30
|
+
changeActiveTab,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useCaptchas() {
|
|
35
|
+
const { send, onSuccess } = useRequest(getAuthLoginValidCode({ size: '100x40' }));
|
|
36
|
+
const captcha = ref('');
|
|
37
|
+
const captchaHash = ref('');
|
|
38
|
+
|
|
39
|
+
onSuccess(({ data }) => {
|
|
40
|
+
const { key, plaintext } = data.data!;
|
|
41
|
+
|
|
42
|
+
const imgUrl = `data:image/png;base64,${plaintext}`;
|
|
43
|
+
captcha.value = imgUrl;
|
|
44
|
+
captchaHash.value = key!;
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
send,
|
|
48
|
+
captcha,
|
|
49
|
+
captchaHash,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useFocusElement() {
|
|
54
|
+
const activeElement = useActiveElement();
|
|
55
|
+
const focusElementDataId = computed(() =>
|
|
56
|
+
activeElement.value?.dataset?.id || 'null',
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
focusElementDataId,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useCountdown(initialTime: MaybeRef<number> = 60) {
|
|
64
|
+
const count = ref(toValue(initialTime));
|
|
65
|
+
const isStart = computed(() => {
|
|
66
|
+
return toValue(count) !== toValue(initialTime);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function start() {
|
|
70
|
+
count.value--;
|
|
71
|
+
const timer = setInterval(() => {
|
|
72
|
+
count.value--;
|
|
73
|
+
if (count.value === 0) {
|
|
74
|
+
clearInterval(timer);
|
|
75
|
+
count.value = toValue(initialTime);
|
|
76
|
+
}
|
|
77
|
+
}, 1000);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
count,
|
|
81
|
+
start,
|
|
82
|
+
isStart,
|
|
83
|
+
};
|
|
84
|
+
}
|