jcjy-components 0.0.61 → 0.0.63
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/components/auth-gateway/bind-org-dialog.vue +128 -0
- package/dist/components/auth-gateway/img/bg.jpg +0 -0
- package/dist/components/auth-gateway/img/bind-bg.jpg +0 -0
- package/dist/components/auth-gateway/img/logo.png +0 -0
- package/dist/components/auth-gateway/img/wechat.png +0 -0
- package/dist/components/auth-gateway/index.vue +291 -0
- package/dist/components/auth-gateway/interface.ts +81 -0
- package/dist/components/auth-gateway/login-form.vue +115 -0
- package/dist/components/auth-gateway/register-form.vue +161 -0
- package/dist/components/auth-gateway/util.ts +146 -0
- package/dist/components/auth-gateway/wechat-form.vue +81 -0
- package/dist/components/auth-gateway/wechat-scan-view.vue +75 -0
- package/dist/components/file-manager/index.vue +3 -0
- package/{src → dist}/index.ts +0 -3
- package/package.json +3 -4
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-dialog
|
|
3
|
+
v-model="isShow"
|
|
4
|
+
fullscreen
|
|
5
|
+
:show-close="false"
|
|
6
|
+
body-class="!p-0"
|
|
7
|
+
header-class="hidden"
|
|
8
|
+
class="!p-0"
|
|
9
|
+
>
|
|
10
|
+
<header class="h-100 relative">
|
|
11
|
+
<img
|
|
12
|
+
src="./img/bind-bg.jpg"
|
|
13
|
+
class="w-full h-full"
|
|
14
|
+
style="object-fit: cover"
|
|
15
|
+
/>
|
|
16
|
+
<div
|
|
17
|
+
class="absolute top-0 left-0 right-0 bottom-0 bg-black/40 w-full h-full"
|
|
18
|
+
></div>
|
|
19
|
+
</header>
|
|
20
|
+
<div class="text-center -mt-100 relative z-1 pt-20">
|
|
21
|
+
<div class="text-white font-bold text-2xl">验证身份信息</div>
|
|
22
|
+
<div class="font-400 text-lg text-white my-2">
|
|
23
|
+
我们需要验证你的身份信息,以便为你提供更好的服务
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<el-card class="w-4/5 lg:w-1/2 mx-auto">
|
|
27
|
+
<el-form
|
|
28
|
+
:model="model"
|
|
29
|
+
:rules="rules"
|
|
30
|
+
ref="formRef"
|
|
31
|
+
size="large"
|
|
32
|
+
label-position="top"
|
|
33
|
+
@keydown.enter="handleConfirm"
|
|
34
|
+
>
|
|
35
|
+
<el-form-item prop="orgName" label="学校">
|
|
36
|
+
<el-input
|
|
37
|
+
v-model="model.orgName"
|
|
38
|
+
placeholder="请输入学校"
|
|
39
|
+
:model-modifiers="{ trim: true }"
|
|
40
|
+
/>
|
|
41
|
+
</el-form-item>
|
|
42
|
+
<el-form-item prop="name" label="姓名">
|
|
43
|
+
<el-input
|
|
44
|
+
v-model="model.name"
|
|
45
|
+
placeholder="请输入姓名"
|
|
46
|
+
:model-modifiers="{ trim: true }"
|
|
47
|
+
/>
|
|
48
|
+
</el-form-item>
|
|
49
|
+
<el-form-item prop="code" label="学号/工号">
|
|
50
|
+
<el-input
|
|
51
|
+
v-model="model.code"
|
|
52
|
+
placeholder="请输入学号/工号"
|
|
53
|
+
:model-modifiers="{ trim: true }"
|
|
54
|
+
/>
|
|
55
|
+
</el-form-item>
|
|
56
|
+
|
|
57
|
+
<el-button class="w-full my-4" type="primary" @click="handleConfirm"
|
|
58
|
+
>验证</el-button
|
|
59
|
+
>
|
|
60
|
+
<div></div>
|
|
61
|
+
<el-button type="primary" class="w-full" plain @click="handleClose"
|
|
62
|
+
>返回</el-button
|
|
63
|
+
>
|
|
64
|
+
</el-form>
|
|
65
|
+
</el-card>
|
|
66
|
+
</div>
|
|
67
|
+
</el-dialog>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { ref } from "vue";
|
|
72
|
+
import {
|
|
73
|
+
ElDialog,
|
|
74
|
+
ElForm,
|
|
75
|
+
ElFormItem,
|
|
76
|
+
ElCard,
|
|
77
|
+
ElButton,
|
|
78
|
+
ElInput,
|
|
79
|
+
} from "element-plus";
|
|
80
|
+
const emits = defineEmits<{
|
|
81
|
+
(e: "confirm", v: any): void;
|
|
82
|
+
}>();
|
|
83
|
+
const isShow = ref(false);
|
|
84
|
+
function open() {
|
|
85
|
+
isShow.value = true;
|
|
86
|
+
}
|
|
87
|
+
function close() {
|
|
88
|
+
isShow.value = false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const formRef = ref();
|
|
92
|
+
const model = ref({
|
|
93
|
+
orgName: "",
|
|
94
|
+
name: "",
|
|
95
|
+
code: "",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const rules = {
|
|
99
|
+
orgName: {
|
|
100
|
+
required: true,
|
|
101
|
+
message: "请输入学校",
|
|
102
|
+
trigger: "",
|
|
103
|
+
},
|
|
104
|
+
name: {
|
|
105
|
+
required: true,
|
|
106
|
+
message: "请输入姓名",
|
|
107
|
+
trigger: "",
|
|
108
|
+
},
|
|
109
|
+
code: {
|
|
110
|
+
required: true,
|
|
111
|
+
message: "请输入学号/工号",
|
|
112
|
+
trigger: "",
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function handleClose() {
|
|
117
|
+
close();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function handleConfirm() {
|
|
121
|
+
await formRef.value.validate();
|
|
122
|
+
emits("confirm", model.value);
|
|
123
|
+
}
|
|
124
|
+
defineExpose({
|
|
125
|
+
open,
|
|
126
|
+
close,
|
|
127
|
+
});
|
|
128
|
+
</script>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="auth-gateway w-screen h-screen overflow-hidden"
|
|
4
|
+
:class="rootClass"
|
|
5
|
+
:style="rootStyle"
|
|
6
|
+
>
|
|
7
|
+
<div v-if="isInited" class="auth-gateway-card" v-loading="confirmLoading">
|
|
8
|
+
<transition name="zoom-in-center" mode="out-in">
|
|
9
|
+
<el-card
|
|
10
|
+
class="w-[90vw] lg:w-105 max-w-130"
|
|
11
|
+
v-if="currentTab === TabKey.LOGIN"
|
|
12
|
+
shadow="hover"
|
|
13
|
+
>
|
|
14
|
+
<LoginForm
|
|
15
|
+
key="login"
|
|
16
|
+
:hide-login-methods="isHideWx"
|
|
17
|
+
@toggle="setCurrentTab"
|
|
18
|
+
@submit="(e) => _login(e, 'normal')"
|
|
19
|
+
/>
|
|
20
|
+
</el-card>
|
|
21
|
+
<el-card
|
|
22
|
+
class="w-[90vw] lg:w-105 max-w-130"
|
|
23
|
+
v-else-if="currentTab === TabKey.REGISTER"
|
|
24
|
+
shadow="hover"
|
|
25
|
+
>
|
|
26
|
+
<RegisterForm
|
|
27
|
+
:strict="props.strictRegister"
|
|
28
|
+
@back="setCurrentTab(TabKey.LOGIN)"
|
|
29
|
+
@submit="_register"
|
|
30
|
+
/>
|
|
31
|
+
</el-card>
|
|
32
|
+
<WechatScanView
|
|
33
|
+
v-else-if="currentTab === TabKey.WECHAT_SCAN"
|
|
34
|
+
@back="setCurrentTab(TabKey.LOGIN)"
|
|
35
|
+
@submit="(e: any) => _login(e, 'wechat')"
|
|
36
|
+
/>
|
|
37
|
+
<WechatForm
|
|
38
|
+
v-else-if="currentTab === TabKey.WECHAT"
|
|
39
|
+
:info="wechatUserInfo"
|
|
40
|
+
class="w-[90vw] lg:w-175"
|
|
41
|
+
@back="handleWechatBack"
|
|
42
|
+
/>
|
|
43
|
+
</transition>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<footer
|
|
47
|
+
class="fixed bottom-3 left-0 right-0 text-center text-white"
|
|
48
|
+
v-html="footerText"
|
|
49
|
+
></footer>
|
|
50
|
+
|
|
51
|
+
<BindOrgDialog ref="bindOrgDialogRef" @confirm="_binding" />
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
<script lang="ts" setup>
|
|
55
|
+
import { ref, computed, onMounted, provide, nextTick } from "vue";
|
|
56
|
+
import { setThemeByUrl, setTitle } from "./util";
|
|
57
|
+
import LoginForm from "./login-form.vue";
|
|
58
|
+
import { type AuthGatewayProps, DEFAULT_CONFIG, TabKey } from "./interface";
|
|
59
|
+
import BindOrgDialog from "./bind-org-dialog.vue";
|
|
60
|
+
import RegisterForm from "./register-form.vue";
|
|
61
|
+
import WechatForm from "./wechat-form.vue";
|
|
62
|
+
import WechatScanView from "./wechat-scan-view.vue";
|
|
63
|
+
import { ElMessage, ElLoading, ElCard } from "element-plus";
|
|
64
|
+
|
|
65
|
+
const emits = defineEmits<{
|
|
66
|
+
(e: "success", v: any): void;
|
|
67
|
+
}>();
|
|
68
|
+
const props = withDefaults(defineProps<AuthGatewayProps>(), {
|
|
69
|
+
strictRegister: false,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const config = computed(() => {
|
|
73
|
+
return {
|
|
74
|
+
...DEFAULT_CONFIG,
|
|
75
|
+
...props.config,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const bindOrgDialogRef = ref();
|
|
80
|
+
const currentTab = ref<TabKey>(TabKey.LOGIN);
|
|
81
|
+
const isInited = ref(false);
|
|
82
|
+
const rootClass = computed(() => {
|
|
83
|
+
const { layout } = config.value;
|
|
84
|
+
return layout;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const isShowBg = computed(() => {
|
|
88
|
+
const { nb } = config.value;
|
|
89
|
+
|
|
90
|
+
return +(nb || 0) !== 1;
|
|
91
|
+
});
|
|
92
|
+
const rootStyle = computed(() => {
|
|
93
|
+
const { loginBackground } = config.value;
|
|
94
|
+
|
|
95
|
+
if (isShowBg.value) {
|
|
96
|
+
return {
|
|
97
|
+
background: `url('${loginBackground}') no-repeat center/100% 100%`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const isNeedBind = computed(() => {
|
|
104
|
+
return config.value.skipBind === "0";
|
|
105
|
+
});
|
|
106
|
+
const isHideWx = computed(() => {
|
|
107
|
+
return !!config.value.hideWx;
|
|
108
|
+
});
|
|
109
|
+
async function init() {
|
|
110
|
+
try {
|
|
111
|
+
const { loginBackground, favicon, title } = config.value;
|
|
112
|
+
setTitle(title, favicon);
|
|
113
|
+
if (isShowBg.value) {
|
|
114
|
+
await setThemeByUrl(loginBackground);
|
|
115
|
+
}
|
|
116
|
+
await nextTick();
|
|
117
|
+
} finally {
|
|
118
|
+
isInited.value = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const footerText = computed(() => {
|
|
123
|
+
const hide = +(config.value.hf || 0) === 1;
|
|
124
|
+
if (hide) {
|
|
125
|
+
return "";
|
|
126
|
+
} else {
|
|
127
|
+
return config.value.cf;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
function setCurrentTab(key: TabKey) {
|
|
132
|
+
currentTab.value = key;
|
|
133
|
+
}
|
|
134
|
+
const userInfo = ref<any>();
|
|
135
|
+
const wechatUserInfo = ref<any>();
|
|
136
|
+
const confirmLoading = ref(false);
|
|
137
|
+
|
|
138
|
+
async function _binding(body: { orgName: string; name: string; code: string }) {
|
|
139
|
+
const loading = ElLoading.service({
|
|
140
|
+
lock: true,
|
|
141
|
+
text: "正在验证...",
|
|
142
|
+
background: "rgba(0, 0, 0, 0.7)",
|
|
143
|
+
});
|
|
144
|
+
try {
|
|
145
|
+
await props.binding(body, {
|
|
146
|
+
token: userInfo.value.token,
|
|
147
|
+
});
|
|
148
|
+
ElMessage.success("验证成功!");
|
|
149
|
+
emits("success", userInfo.value);
|
|
150
|
+
} finally {
|
|
151
|
+
loading.close();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function _register(body: {
|
|
156
|
+
userName: string;
|
|
157
|
+
passwd: string;
|
|
158
|
+
weChatOpenId?: string;
|
|
159
|
+
}) {
|
|
160
|
+
try {
|
|
161
|
+
confirmLoading.value = true;
|
|
162
|
+
const res = await props.register(body);
|
|
163
|
+
userInfo.value = res;
|
|
164
|
+
if (isNeedBind.value) {
|
|
165
|
+
ElMessage.success("注册成功!请验证身份信息");
|
|
166
|
+
setCurrentTab(TabKey.LOGIN);
|
|
167
|
+
bindOrgDialogRef.value.open();
|
|
168
|
+
} else {
|
|
169
|
+
ElMessage.success("注册成功!");
|
|
170
|
+
emits("success", userInfo.value);
|
|
171
|
+
}
|
|
172
|
+
} finally {
|
|
173
|
+
confirmLoading.value = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function _login(
|
|
177
|
+
body: {
|
|
178
|
+
weChatCode?: string;
|
|
179
|
+
userName?: string;
|
|
180
|
+
passwd?: string;
|
|
181
|
+
weChatOpenId?: string;
|
|
182
|
+
},
|
|
183
|
+
from: "wechat" | "normal"
|
|
184
|
+
) {
|
|
185
|
+
try {
|
|
186
|
+
confirmLoading.value = true;
|
|
187
|
+
|
|
188
|
+
const res = await props.login(body);
|
|
189
|
+
if (from === "wechat") {
|
|
190
|
+
if (res.err === 4004) {
|
|
191
|
+
ElMessage.error(res.msg);
|
|
192
|
+
wechatUserInfo.value = res.weChatUserDetail;
|
|
193
|
+
currentTab.value = TabKey.WECHAT;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
userInfo.value = res;
|
|
199
|
+
|
|
200
|
+
if (!res.type && isNeedBind.value) {
|
|
201
|
+
ElMessage.success("登录成功!请验证身份信息");
|
|
202
|
+
bindOrgDialogRef.value.open();
|
|
203
|
+
} else {
|
|
204
|
+
emits("success", userInfo.value);
|
|
205
|
+
}
|
|
206
|
+
} finally {
|
|
207
|
+
confirmLoading.value = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function handleWechatBack() {
|
|
212
|
+
wechatUserInfo.value = null;
|
|
213
|
+
setCurrentTab(TabKey.LOGIN);
|
|
214
|
+
}
|
|
215
|
+
onMounted(init);
|
|
216
|
+
|
|
217
|
+
provide("contextProps", { ...props, config: config.value });
|
|
218
|
+
provide("_login", _login);
|
|
219
|
+
provide("_register", _register);
|
|
220
|
+
</script>
|
|
221
|
+
|
|
222
|
+
<style lang="scss">
|
|
223
|
+
.auth-gateway {
|
|
224
|
+
* {
|
|
225
|
+
box-sizing: border-box;
|
|
226
|
+
}
|
|
227
|
+
/* 进入动画:缩放 + 淡入 */
|
|
228
|
+
.zoom-in-center-enter-active {
|
|
229
|
+
transition: all 0.2s ease;
|
|
230
|
+
}
|
|
231
|
+
.zoom-in-center-enter-from {
|
|
232
|
+
transform: scale(0.7);
|
|
233
|
+
opacity: 0;
|
|
234
|
+
}
|
|
235
|
+
.zoom-in-center-enter-to {
|
|
236
|
+
transform: scale(1);
|
|
237
|
+
opacity: 1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* 离开动画:缩放 + 淡出 */
|
|
241
|
+
.zoom-in-center-leave-active {
|
|
242
|
+
transition: all 0.2s ease;
|
|
243
|
+
}
|
|
244
|
+
.zoom-in-center-leave-from {
|
|
245
|
+
transform: scale(1);
|
|
246
|
+
opacity: 1;
|
|
247
|
+
}
|
|
248
|
+
.zoom-in-center-leave-to {
|
|
249
|
+
transform: scale(0.7);
|
|
250
|
+
opacity: 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
&.default {
|
|
254
|
+
display: flex;
|
|
255
|
+
align-items: center;
|
|
256
|
+
justify-content: center;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@media (min-width: 1024px) {
|
|
260
|
+
&.default {
|
|
261
|
+
justify-content: flex-end;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
&.default .auth-gateway-card {
|
|
266
|
+
margin-right: 6rem; /* lg:mr-24 = 24 * 0.25rem = 6rem */
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
&.center {
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
justify-content: center;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
&.left {
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
justify-content: center;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@media (min-width: 1024px) {
|
|
282
|
+
&.left {
|
|
283
|
+
justify-content: flex-start;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
&.left .auth-gateway-card {
|
|
288
|
+
margin-left: 6rem; /* lg:ml-24 = 24 * 0.25rem = 6rem */
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
</style>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import IMG_LOGO from "./img/logo.png";
|
|
2
|
+
import IMG_BG from "./img/bg.jpg";
|
|
3
|
+
|
|
4
|
+
export const TabKey = {
|
|
5
|
+
LOGIN: 0,
|
|
6
|
+
REGISTER: 1,
|
|
7
|
+
WECHAT: 2,
|
|
8
|
+
WECHAT_SCAN: 3,
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type TabKey = (typeof TabKey)[keyof typeof TabKey];
|
|
12
|
+
|
|
13
|
+
export interface AuthGatewayProps {
|
|
14
|
+
config: AuthGatewayConfig;
|
|
15
|
+
strictRegister?: boolean;
|
|
16
|
+
binding: (
|
|
17
|
+
v: { orgName: string; name: string; code: string },
|
|
18
|
+
headers: { token: string }
|
|
19
|
+
) => Promise<any>;
|
|
20
|
+
register: (params: {
|
|
21
|
+
userName: string;
|
|
22
|
+
passwd: string;
|
|
23
|
+
weChatOpenId?: string;
|
|
24
|
+
}) => Promise<any>;
|
|
25
|
+
login: (params: {
|
|
26
|
+
weChatCode?: string;
|
|
27
|
+
userName?: string;
|
|
28
|
+
passwd?: string;
|
|
29
|
+
weChatOpenId?: string;
|
|
30
|
+
}) => Promise<
|
|
31
|
+
| {
|
|
32
|
+
err: number;
|
|
33
|
+
msg: string;
|
|
34
|
+
weChatUserDetail: any;
|
|
35
|
+
}
|
|
36
|
+
| any
|
|
37
|
+
>;
|
|
38
|
+
wechatConfig?: {
|
|
39
|
+
appId: string;
|
|
40
|
+
redirectUrl: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface AuthGatewayConfig {
|
|
44
|
+
title?: string;
|
|
45
|
+
// redirect: string;
|
|
46
|
+
// args: string;
|
|
47
|
+
// loginUrl: string; // 登录页url
|
|
48
|
+
favicon?: string; // icon url
|
|
49
|
+
loginBackground?: string; // 登录页背景 url
|
|
50
|
+
skipBind?: "0" | "1"; // 是否绑定 0 :默认绑定 1: 不需要绑定
|
|
51
|
+
hf?: "0" | "1"; // 是否隐藏登录页页脚 (0:显示 1:隐藏)
|
|
52
|
+
nb?: "0" | "1"; // 使用无logo的背景(0:显示,1:隐藏)
|
|
53
|
+
cf?: string; // 自定义页脚内容
|
|
54
|
+
from?: "inline"; // 来源 inline 则不自动跳转
|
|
55
|
+
hideWx?: string; // 隐藏微信登录 有数据就隐藏
|
|
56
|
+
layout?: "default" | "center" | "left";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const DEFAULT_CONFIG: AuthGatewayConfig = {
|
|
60
|
+
title: "AIGC实训平台",
|
|
61
|
+
favicon: IMG_LOGO,
|
|
62
|
+
loginBackground: IMG_BG,
|
|
63
|
+
layout: "default",
|
|
64
|
+
skipBind: "0",
|
|
65
|
+
nb: "0",
|
|
66
|
+
hf: "0",
|
|
67
|
+
cf: `
|
|
68
|
+
<div style="font-size:14px;color:white;">
|
|
69
|
+
<a
|
|
70
|
+
href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=31010702009439"
|
|
71
|
+
target="_blank"
|
|
72
|
+
style="text-decoration: none;color:white;"
|
|
73
|
+
>
|
|
74
|
+
沪公网安备 31010702009439 号
|
|
75
|
+
</a>
|
|
76
|
+
|
|
|
77
|
+
<a href="https://beian.miit.gov.cn/" style="text-decoration: none;color:white;">沪ICP备2020031101号-3</a>
|
|
78
|
+
| © 2025 晶程甲宇科技(上海)有限公司 版权所有
|
|
79
|
+
</div>
|
|
80
|
+
`,
|
|
81
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-form
|
|
3
|
+
ref="formRef"
|
|
4
|
+
:model="model"
|
|
5
|
+
:rules="rules"
|
|
6
|
+
label-position="top"
|
|
7
|
+
@keydown.enter="confirm"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
v-if="contextProps.config?.favicon || contextProps.config?.title"
|
|
11
|
+
class="flex items-center justify-center gap-3 mb-4"
|
|
12
|
+
>
|
|
13
|
+
<img
|
|
14
|
+
v-if="contextProps.config.favicon"
|
|
15
|
+
:src="contextProps.config.favicon"
|
|
16
|
+
class="w-7"
|
|
17
|
+
/>
|
|
18
|
+
<b class="text-xl">
|
|
19
|
+
{{ contextProps.config.title }}
|
|
20
|
+
</b>
|
|
21
|
+
</div>
|
|
22
|
+
<el-form-item label="邮箱" prop="email">
|
|
23
|
+
<el-input
|
|
24
|
+
v-model="model.email"
|
|
25
|
+
placeholder="请输入邮箱"
|
|
26
|
+
:model-modifiers="{
|
|
27
|
+
trim: true,
|
|
28
|
+
}"
|
|
29
|
+
/>
|
|
30
|
+
</el-form-item>
|
|
31
|
+
<el-form-item label="密码" prop="password">
|
|
32
|
+
<el-input
|
|
33
|
+
v-model="model.password"
|
|
34
|
+
placeholder="请输入密码"
|
|
35
|
+
type="password"
|
|
36
|
+
:model-modifiers="{
|
|
37
|
+
trim: true,
|
|
38
|
+
}"
|
|
39
|
+
show-password
|
|
40
|
+
/>
|
|
41
|
+
</el-form-item>
|
|
42
|
+
<el-button class="w-full mt-2" type="primary" @click="confirm">
|
|
43
|
+
{{ props.confirmText || "登 录" }}
|
|
44
|
+
</el-button>
|
|
45
|
+
|
|
46
|
+
<div class="text-center mt-4">
|
|
47
|
+
<el-link class="!font-400" @click="handleToggle(TabKey.REGISTER)"
|
|
48
|
+
>注册账号</el-link
|
|
49
|
+
>
|
|
50
|
+
|
|
51
|
+
<template v-if="!hideLoginMethods">
|
|
52
|
+
<el-divider
|
|
53
|
+
><span class="text-xs text-black/40"> 其他登录方式 </span></el-divider
|
|
54
|
+
>
|
|
55
|
+
|
|
56
|
+
<el-button link @click="handleToggle(TabKey.WECHAT_SCAN)">
|
|
57
|
+
<img src="./img/wechat.png" alt="" class="size-6" />
|
|
58
|
+
</el-button>
|
|
59
|
+
</template>
|
|
60
|
+
</div>
|
|
61
|
+
</el-form>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup lang="ts">
|
|
65
|
+
import { ref, inject } from "vue";
|
|
66
|
+
import { type AuthGatewayProps, TabKey } from "./interface";
|
|
67
|
+
import { transformModel } from "./util";
|
|
68
|
+
import {
|
|
69
|
+
ElForm,
|
|
70
|
+
ElFormItem,
|
|
71
|
+
ElButton,
|
|
72
|
+
ElInput,
|
|
73
|
+
ElDivider,
|
|
74
|
+
ElLink,
|
|
75
|
+
} from "element-plus";
|
|
76
|
+
const contextProps = inject<AuthGatewayProps>("contextProps")!;
|
|
77
|
+
const emits = defineEmits<{
|
|
78
|
+
(e: "toggle", v: TabKey): void;
|
|
79
|
+
(e: "submit", v: any): void;
|
|
80
|
+
}>();
|
|
81
|
+
const props = defineProps<{
|
|
82
|
+
confirmText?: string;
|
|
83
|
+
hideLoginMethods?: boolean;
|
|
84
|
+
}>();
|
|
85
|
+
const model = ref({
|
|
86
|
+
email: "",
|
|
87
|
+
password: "",
|
|
88
|
+
});
|
|
89
|
+
const rules = {
|
|
90
|
+
email: [
|
|
91
|
+
{
|
|
92
|
+
required: true,
|
|
93
|
+
message: "请输入邮箱",
|
|
94
|
+
trigger: "blur",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
password: [
|
|
98
|
+
{
|
|
99
|
+
required: true,
|
|
100
|
+
message: "请输入密码",
|
|
101
|
+
trigger: "blur",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const formRef = ref();
|
|
107
|
+
async function confirm() {
|
|
108
|
+
await formRef.value.validate();
|
|
109
|
+
emits("submit", transformModel(model.value));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function handleToggle(key: TabKey) {
|
|
113
|
+
emits("toggle", key);
|
|
114
|
+
}
|
|
115
|
+
</script>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-form
|
|
3
|
+
ref="formRef"
|
|
4
|
+
:model="model"
|
|
5
|
+
:rules="rules"
|
|
6
|
+
label-position="top"
|
|
7
|
+
class="py-2"
|
|
8
|
+
@keydown.enter="confirm"
|
|
9
|
+
>
|
|
10
|
+
<div class="text-lg font-bold mb-3">注册邮箱账号</div>
|
|
11
|
+
<el-form-item label="邮箱" prop="email">
|
|
12
|
+
<el-input
|
|
13
|
+
v-model="model.email"
|
|
14
|
+
:model-modifiers="{
|
|
15
|
+
trim: true,
|
|
16
|
+
}"
|
|
17
|
+
placeholder="请输入邮箱"
|
|
18
|
+
/>
|
|
19
|
+
</el-form-item>
|
|
20
|
+
<el-form-item label="密码" prop="password">
|
|
21
|
+
<el-input
|
|
22
|
+
v-model="model.password"
|
|
23
|
+
placeholder="请输入密码"
|
|
24
|
+
type="password"
|
|
25
|
+
:model-modifiers="{
|
|
26
|
+
trim: true,
|
|
27
|
+
}"
|
|
28
|
+
autocomplete="new-password"
|
|
29
|
+
show-password
|
|
30
|
+
/>
|
|
31
|
+
</el-form-item>
|
|
32
|
+
<el-form-item label="再次输入密码" prop="password2">
|
|
33
|
+
<el-input
|
|
34
|
+
v-model="model.password2"
|
|
35
|
+
placeholder="请再次输入密码"
|
|
36
|
+
:model-modifiers="{
|
|
37
|
+
trim: true,
|
|
38
|
+
}"
|
|
39
|
+
autocomplete="new-password"
|
|
40
|
+
type="password"
|
|
41
|
+
show-password
|
|
42
|
+
/>
|
|
43
|
+
</el-form-item>
|
|
44
|
+
|
|
45
|
+
<el-button class="w-full mt-2" type="primary" @click="confirm">
|
|
46
|
+
{{ props.confirmText || "注 册" }}
|
|
47
|
+
</el-button>
|
|
48
|
+
<div></div>
|
|
49
|
+
<el-button @click="emits('back')" type="primary" plain class="w-full mt-4">
|
|
50
|
+
返 回
|
|
51
|
+
</el-button>
|
|
52
|
+
</el-form>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
import { ref, inject, computed } from "vue";
|
|
57
|
+
import type { FormItemRule } from "element-plus";
|
|
58
|
+
import { passwordValidator, transformModel } from "./util";
|
|
59
|
+
import type { AuthGatewayProps } from "./interface";
|
|
60
|
+
import { ElForm, ElFormItem, ElButton, ElInput } from "element-plus";
|
|
61
|
+
const contextProps = inject<AuthGatewayProps>("contextProps");
|
|
62
|
+
|
|
63
|
+
const emits = defineEmits<{
|
|
64
|
+
(e: "back"): void;
|
|
65
|
+
(e: "submit", v: any): void;
|
|
66
|
+
}>();
|
|
67
|
+
const props = defineProps<{
|
|
68
|
+
confirmText?: string;
|
|
69
|
+
}>();
|
|
70
|
+
const model = ref({
|
|
71
|
+
email: "",
|
|
72
|
+
password: "",
|
|
73
|
+
password2: "",
|
|
74
|
+
});
|
|
75
|
+
const baseRules: Record<string, FormItemRule[]> = {
|
|
76
|
+
email: [
|
|
77
|
+
{
|
|
78
|
+
required: true,
|
|
79
|
+
message: "请输入邮箱",
|
|
80
|
+
trigger: "blur",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
password: [
|
|
84
|
+
{
|
|
85
|
+
required: true,
|
|
86
|
+
message: "请输入密码",
|
|
87
|
+
trigger: "blur",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
password2: [
|
|
91
|
+
{
|
|
92
|
+
required: true,
|
|
93
|
+
message: "请再次输入密码",
|
|
94
|
+
trigger: "blur",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
validator: (_rule: any, value: string, callback: Function) => {
|
|
98
|
+
if (value === "") {
|
|
99
|
+
callback(new Error("请再次输入密码"));
|
|
100
|
+
} else if (value !== model.value.password) {
|
|
101
|
+
callback(new Error("两次输入密码不一致"));
|
|
102
|
+
} else {
|
|
103
|
+
callback();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
trigger: "blur",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const strictRules: Record<string, FormItemRule[]> = {
|
|
112
|
+
email: [
|
|
113
|
+
{
|
|
114
|
+
required: true,
|
|
115
|
+
message: "请输入邮箱",
|
|
116
|
+
trigger: "blur",
|
|
117
|
+
type: "email",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
password: [
|
|
121
|
+
{
|
|
122
|
+
required: true,
|
|
123
|
+
message: "请输入密码",
|
|
124
|
+
trigger: "blur",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
validator: passwordValidator(),
|
|
128
|
+
trigger: ["change", "blur"],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
password2: [
|
|
132
|
+
{
|
|
133
|
+
required: true,
|
|
134
|
+
message: "请再次输入密码",
|
|
135
|
+
trigger: "blur",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
validator: (_rule: any, value: string, callback: Function) => {
|
|
139
|
+
if (value === "") {
|
|
140
|
+
callback(new Error("请再次输入密码"));
|
|
141
|
+
} else if (value !== model.value.password) {
|
|
142
|
+
callback(new Error("两次输入密码不一致"));
|
|
143
|
+
} else {
|
|
144
|
+
callback();
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
trigger: "blur",
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const rules = computed(() => {
|
|
153
|
+
return contextProps?.strictRegister ? strictRules : baseRules;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const formRef = ref();
|
|
157
|
+
async function confirm() {
|
|
158
|
+
await formRef.value.validate();
|
|
159
|
+
emits("submit", transformModel(model.value));
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Vibrant } from "node-vibrant/browser";
|
|
2
|
+
import { Md5 } from "ts-md5";
|
|
3
|
+
|
|
4
|
+
export function transformModel(v: { email: string; password: string }) {
|
|
5
|
+
const { email, password } = v;
|
|
6
|
+
const md5 = new Md5();
|
|
7
|
+
return {
|
|
8
|
+
userName: email,
|
|
9
|
+
passwd: md5.appendAsciiStr(password).end(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export async function setThemeByUrl(src?: string) {
|
|
13
|
+
if (!src) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const palette = await Vibrant.from(src).getPalette();
|
|
17
|
+
const { r, g, b } = palette.Vibrant!;
|
|
18
|
+
setTheme([r, g, b]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setTheme(colors: any[]) {
|
|
22
|
+
const [r, g, b] = colors;
|
|
23
|
+
const root = document.documentElement;
|
|
24
|
+
const color = `${r} ${g} ${b}`;
|
|
25
|
+
const elColor = `rgb(${r},${g},${b})`;
|
|
26
|
+
root.style.setProperty("--color-primary", color);
|
|
27
|
+
root.style.setProperty("--el-color-primary", elColor);
|
|
28
|
+
|
|
29
|
+
for (let i = 1; i <= 10; i++) {
|
|
30
|
+
root.style.setProperty(
|
|
31
|
+
`--el-color-primary-light-${i}`,
|
|
32
|
+
generateLightColors(i, { r, g, b })
|
|
33
|
+
);
|
|
34
|
+
root.style.setProperty(
|
|
35
|
+
`--el-color-primary-dark-${i}`,
|
|
36
|
+
generateLightColors(i, { r, g, b }, { r: 0, g: 0, b: 0 })
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function setTitle(title?: string, favicon?: string) {
|
|
42
|
+
if (title) document.title = title;
|
|
43
|
+
|
|
44
|
+
if (favicon) {
|
|
45
|
+
let iconEl = document.querySelector("link[rel='icon']");
|
|
46
|
+
|
|
47
|
+
if (!iconEl) {
|
|
48
|
+
iconEl = document.createElement("link");
|
|
49
|
+
iconEl.setAttribute("rel", "icon");
|
|
50
|
+
document.head.appendChild(iconEl);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 防止缓存 & 兼容 https
|
|
54
|
+
const href = favicon.startsWith("http")
|
|
55
|
+
? favicon
|
|
56
|
+
: `${window.location.origin}${favicon}`;
|
|
57
|
+
iconEl.setAttribute("href", `${href}?v=${Date.now()}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* RGB 颜色混合函数
|
|
62
|
+
* @param mixColor 混合颜色 {r,g,b}
|
|
63
|
+
* @param baseColor 基础颜色 {r,g,b}
|
|
64
|
+
* @param ratio 混合比例 0~1,越大越偏 mixColor
|
|
65
|
+
* @returns {r,g,b}
|
|
66
|
+
*/
|
|
67
|
+
function mixColors(
|
|
68
|
+
mixColor: { r: number; g: number; b: number },
|
|
69
|
+
baseColor: { r: number; g: number; b: number },
|
|
70
|
+
ratio: number
|
|
71
|
+
) {
|
|
72
|
+
const r = Math.round(baseColor.r + (mixColor.r - baseColor.r) * ratio);
|
|
73
|
+
const g = Math.round(baseColor.g + (mixColor.g - baseColor.g) * ratio);
|
|
74
|
+
const b = Math.round(baseColor.b + (mixColor.b - baseColor.b) * ratio);
|
|
75
|
+
return { r, g, b };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* RGB 转 CSS rgba 字符串
|
|
80
|
+
*/
|
|
81
|
+
function rgbToCss({ r, g, b }: { r: number; g: number; b: number }, alpha = 1) {
|
|
82
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 生成浅色系列
|
|
87
|
+
* @param baseColor 主色 {r,g,b}
|
|
88
|
+
* @param mixColor 混合颜色 {r,g,b},默认为白色
|
|
89
|
+
*/
|
|
90
|
+
function generateLightColors(
|
|
91
|
+
level: number,
|
|
92
|
+
baseColor: { r: number; g: number; b: number },
|
|
93
|
+
mixColor = { r: 255, g: 255, b: 255 }
|
|
94
|
+
) {
|
|
95
|
+
const ratio = level / 10; // 1~9 -> 0.1~0.9
|
|
96
|
+
const mixed = mixColors(mixColor, baseColor, ratio);
|
|
97
|
+
return rgbToCss(mixed);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// validators/password.ts
|
|
101
|
+
export const passwordValidator = (
|
|
102
|
+
config?: Partial<{
|
|
103
|
+
minLength: number;
|
|
104
|
+
lowercase: number;
|
|
105
|
+
uppercase: number;
|
|
106
|
+
numbers: number;
|
|
107
|
+
symbols: number;
|
|
108
|
+
}>
|
|
109
|
+
) => {
|
|
110
|
+
const defaultConfig: any = {
|
|
111
|
+
minLength: 8,
|
|
112
|
+
lowercase: 1,
|
|
113
|
+
uppercase: 1,
|
|
114
|
+
numbers: 1,
|
|
115
|
+
symbols: 1,
|
|
116
|
+
...config,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const rulesMap: any = {
|
|
120
|
+
lowercase: { reg: /[a-z]/g, msg: "小写字母" },
|
|
121
|
+
uppercase: { reg: /[A-Z]/g, msg: "大写字母" },
|
|
122
|
+
numbers: { reg: /[0-9]/g, msg: "数字" },
|
|
123
|
+
symbols: { reg: /[^a-zA-Z0-9]/g, msg: "特殊符号" },
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return (_rule: any, value: string, callback: any) => {
|
|
127
|
+
if (!value) {
|
|
128
|
+
return callback(new Error("请输入密码"));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (value.length < defaultConfig.minLength) {
|
|
132
|
+
return callback(new Error(`密码至少 ${defaultConfig.minLength} 位`));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const key in rulesMap) {
|
|
136
|
+
const count = (value.match(rulesMap[key].reg) || []).length;
|
|
137
|
+
if (count < defaultConfig[key]) {
|
|
138
|
+
return callback(
|
|
139
|
+
new Error(`密码至少包含 ${defaultConfig[key]} 个${rulesMap[key].msg}`)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
callback();
|
|
145
|
+
};
|
|
146
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-card
|
|
3
|
+
shadow="hover"
|
|
4
|
+
class="h-105"
|
|
5
|
+
body-class="relative flex h-full items-center"
|
|
6
|
+
>
|
|
7
|
+
<el-link
|
|
8
|
+
type="primary"
|
|
9
|
+
class="!font-400 !absolute top-4 left-4"
|
|
10
|
+
@click="emits('back')"
|
|
11
|
+
>返 回</el-link
|
|
12
|
+
>
|
|
13
|
+
<div class="flex flex-col flex-1 items-center justify-center gap-5 px-4">
|
|
14
|
+
<img
|
|
15
|
+
v-if="props.info?.headimgurl"
|
|
16
|
+
:src="props.info?.headimgurl"
|
|
17
|
+
class="size-24 rounded-full shadow-2xl"
|
|
18
|
+
/>
|
|
19
|
+
<div v-else class="size-24 rounded-full bg-gray-200 shadow-2xl"></div>
|
|
20
|
+
<div>{{ props.info?.nickname }}</div>
|
|
21
|
+
<div class="text-sm text-gray-500">
|
|
22
|
+
<div>已有账号?输入账号信息登录立即绑定</div>
|
|
23
|
+
<div class="font-bold">如没有账号,注册后登录即绑定</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<transition name="zoom-in-center" mode="out-in">
|
|
27
|
+
<LoginForm
|
|
28
|
+
v-if="currentTab === TabKey.LOGIN"
|
|
29
|
+
class="w-90"
|
|
30
|
+
hideLoginMethods
|
|
31
|
+
confirmText="登录并绑定"
|
|
32
|
+
@toggle="setCurrentTab"
|
|
33
|
+
@submit="handleLoginSubmit"
|
|
34
|
+
/>
|
|
35
|
+
<RegisterForm
|
|
36
|
+
v-else-if="currentTab === TabKey.REGISTER"
|
|
37
|
+
class="w-90"
|
|
38
|
+
confirmText="注册并绑定"
|
|
39
|
+
@back="setCurrentTab(TabKey.LOGIN)"
|
|
40
|
+
@submit="handleRegisterSubmit"
|
|
41
|
+
/>
|
|
42
|
+
</transition>
|
|
43
|
+
</el-card>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
import { ref, inject } from "vue";
|
|
48
|
+
import { TabKey } from "./interface";
|
|
49
|
+
import LoginForm from "./login-form.vue";
|
|
50
|
+
import RegisterForm from "./register-form.vue";
|
|
51
|
+
import { ElCard, ElLink } from "element-plus";
|
|
52
|
+
const _login = inject<any>("_login");
|
|
53
|
+
const _register = inject<any>("_register");
|
|
54
|
+
const props = defineProps<{
|
|
55
|
+
info: any;
|
|
56
|
+
}>();
|
|
57
|
+
const emits = defineEmits<{
|
|
58
|
+
(e: "back"): void;
|
|
59
|
+
}>();
|
|
60
|
+
const currentTab = ref<TabKey>(TabKey.LOGIN);
|
|
61
|
+
function setCurrentTab(key: TabKey) {
|
|
62
|
+
currentTab.value = key;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleLoginSubmit(e: { userName: string; passwd: string }) {
|
|
66
|
+
_login(
|
|
67
|
+
{
|
|
68
|
+
...e,
|
|
69
|
+
weChatOpenId: props.info.openid,
|
|
70
|
+
},
|
|
71
|
+
"normal"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handleRegisterSubmit(e: { userName: string; passwd: string }) {
|
|
76
|
+
_register({
|
|
77
|
+
...e,
|
|
78
|
+
weChatOpenId: props.info.openid,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-card
|
|
3
|
+
shadow="hover"
|
|
4
|
+
class="size-90"
|
|
5
|
+
body-class="flex flex-col items-center justify-center h-full box-border"
|
|
6
|
+
>
|
|
7
|
+
<div :id="DOM_ID" class="size-40"></div>
|
|
8
|
+
<b class="my-4">请使用微信扫码登录</b>
|
|
9
|
+
<el-link class="!font-400" @click="emits('back')">
|
|
10
|
+
返 回
|
|
11
|
+
</el-link>
|
|
12
|
+
</el-card>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script lang="ts" setup>
|
|
16
|
+
import { inject, onUnmounted } from "vue";
|
|
17
|
+
import { ElCard, ElLink } from "element-plus";
|
|
18
|
+
const DOM_ID = "login_container";
|
|
19
|
+
import type { AuthGatewayProps } from "./interface";
|
|
20
|
+
|
|
21
|
+
const contextProps = inject<AuthGatewayProps>("contextProps");
|
|
22
|
+
|
|
23
|
+
const emits = defineEmits<{
|
|
24
|
+
(e: "back"): void;
|
|
25
|
+
(e: "submit", v: { weChatCode: string }): void;
|
|
26
|
+
}>();
|
|
27
|
+
function init() {
|
|
28
|
+
const scriptEl = document.createElement("script");
|
|
29
|
+
scriptEl.type = "text/javascript";
|
|
30
|
+
scriptEl.src =
|
|
31
|
+
"https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js";
|
|
32
|
+
const wxElement = document.body.appendChild(scriptEl);
|
|
33
|
+
wxElement.onload = () => {
|
|
34
|
+
const config = {
|
|
35
|
+
self_redirect: true,
|
|
36
|
+
id: DOM_ID,
|
|
37
|
+
appid: contextProps?.wechatConfig?.appId,
|
|
38
|
+
scope: "snsapi_login",
|
|
39
|
+
redirect_uri: encodeURIComponent(
|
|
40
|
+
`${contextProps?.wechatConfig?.redirectUrl}`
|
|
41
|
+
),
|
|
42
|
+
state: "ssssss",
|
|
43
|
+
href: "",
|
|
44
|
+
stylelite: 1,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
new (window as any).WxLogin(config);
|
|
48
|
+
|
|
49
|
+
window.addEventListener("message", handleMessage);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleMessage(e: any) {
|
|
54
|
+
if (e.data.type === "router_data") {
|
|
55
|
+
emits("submit", {
|
|
56
|
+
weChatCode: e.data.data.query?.code,
|
|
57
|
+
});
|
|
58
|
+
window.removeEventListener("message", handleMessage);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onUnmounted(() => {
|
|
63
|
+
window.removeEventListener("message", handleMessage);
|
|
64
|
+
});
|
|
65
|
+
init();
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<style lang="scss">
|
|
69
|
+
#login_container {
|
|
70
|
+
iframe {
|
|
71
|
+
width: 160px;
|
|
72
|
+
height: 160px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
</style>
|
package/{src → dist}/index.ts
RENAMED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { App } from "vue";
|
|
2
2
|
import AuthGateway from "./components/auth-gateway/index.vue";
|
|
3
|
-
import { ElLoadingDirective } from "element-plus";
|
|
4
3
|
import { setTheme, setThemeByUrl } from "./components/auth-gateway/util";
|
|
5
4
|
export const theme = {
|
|
6
5
|
setTheme,
|
|
@@ -14,8 +13,6 @@ export interface AuthGatewayOptions {
|
|
|
14
13
|
|
|
15
14
|
export default {
|
|
16
15
|
install(app: App) {
|
|
17
|
-
// 只注册自定义组件,Element Plus 组件由父项目提供
|
|
18
|
-
app.directive("loading", ElLoadingDirective);
|
|
19
16
|
app.component("AuthGateway", AuthGateway);
|
|
20
17
|
},
|
|
21
18
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jcjy-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.63",
|
|
4
4
|
"description": "Vue 3 组件库",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"
|
|
9
|
-
"src/index.ts",
|
|
8
|
+
"dist",
|
|
10
9
|
"package.json"
|
|
11
10
|
],
|
|
12
11
|
"keywords": [
|
|
@@ -46,6 +45,6 @@
|
|
|
46
45
|
"build": "vue-tsc -b && vite build",
|
|
47
46
|
"preview": "vite preview",
|
|
48
47
|
"build:types": "vue-tsc -b && vite build --mode build-types",
|
|
49
|
-
"push": "pnpm publish"
|
|
48
|
+
"push:npm": "node build.js && pnpm publish"
|
|
50
49
|
}
|
|
51
50
|
}
|