@oinone/kunlun-vue-admin-base 6.3.5 → 6.3.7
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/oinone-kunlun-vue-admin-base.css +1 -1
- package/dist/oinone-kunlun-vue-admin-base.esm.js +2151 -1669
- package/dist/oinone-kunlun-vue-admin-base.scss +1 -1
- package/dist/types/src/basic/BaseI18nRouterWidget.d.ts +1 -1
- package/dist/types/src/icon-manage/view/search/IconSearch.vue.d.ts +20 -0
- package/dist/types/src/provider/RootWidget.d.ts +3 -2
- package/dist/types/src/view/login/BaseLoginWidget.d.ts +58 -0
- package/dist/types/src/view/login/LoginWidget.d.ts +3 -56
- package/dist/types/src/view/login/SSOLogin.vue.d.ts +154 -0
- package/dist/types/src/view/login/SSOLoginWidget.d.ts +12 -0
- package/dist/types/src/view/login/index.d.ts +2 -0
- package/package.json +8 -8
- package/src/basic/BaseI18nRouterWidget.ts +1 -1
- package/src/provider/RootWidget.ts +9 -3
- package/src/view/login/BaseLoginWidget.ts +278 -0
- package/src/view/login/LoginWidget.ts +6 -272
- package/src/view/login/SSOLogin.vue +312 -0
- package/src/view/login/SSOLoginWidget.ts +103 -0
- package/src/view/login/index.ts +2 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="login-page" :style="loginContainerStyle" :key="node_code">
|
|
3
|
+
<video
|
|
4
|
+
ref="videoRef"
|
|
5
|
+
loop
|
|
6
|
+
autoplay
|
|
7
|
+
muted
|
|
8
|
+
class="login-page-strick-video"
|
|
9
|
+
v-if="isVideoBackground && !isStandLayout"
|
|
10
|
+
:controls="false"
|
|
11
|
+
:src="backgroundImage"
|
|
12
|
+
></video>
|
|
13
|
+
|
|
14
|
+
<!-- logo在页面左上角 -->
|
|
15
|
+
<div class="login-page-container-img" v-if="logoInLeft" :style="logoStyle">
|
|
16
|
+
<img v-if="loginPageLogo" :src="loginPageLogo" alt="logo" />
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="login-page-container" @keyup.enter="login" :style="loginBlockStyle">
|
|
20
|
+
<div v-if="enableI18n && currentLanguage && currentLanguage.name" class="login-language-wrapper">
|
|
21
|
+
<a-dropdown>
|
|
22
|
+
<div class="login-language-current-row">
|
|
23
|
+
<oio-icon icon="oinone-a-yuyanxuanze4x" size="14" color="var(--oio-text-color-secondary)" />
|
|
24
|
+
<span class="login-language-current">{{ currentLanguage.name }}</span>
|
|
25
|
+
<oio-icon icon="oinone-zhutixiala" size="10" color="var(--oio-text-color-secondary)" />
|
|
26
|
+
</div>
|
|
27
|
+
<template #overlay>
|
|
28
|
+
<a-menu>
|
|
29
|
+
<a-menu-item v-for="language in languages" :key="language.code" @click="() => onLanguageChange(language)">
|
|
30
|
+
{{ language.name }}
|
|
31
|
+
</a-menu-item>
|
|
32
|
+
</a-menu>
|
|
33
|
+
</template>
|
|
34
|
+
</a-dropdown>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- 账号登录 -->
|
|
38
|
+
<div class="login-page-title">{{ translateValueByKey(accountLoginLabel) }}</div>
|
|
39
|
+
<oio-form :data="authForm" class="login-page-form">
|
|
40
|
+
<oio-form-item
|
|
41
|
+
name="login"
|
|
42
|
+
:help="error['login']"
|
|
43
|
+
:validateStatus="error['login'] || error['password'] ? 'error' : ''"
|
|
44
|
+
>
|
|
45
|
+
<oio-input
|
|
46
|
+
class="login-input"
|
|
47
|
+
:placeholder="translateValueByKey(accountPlaceholder)"
|
|
48
|
+
v-model:value="authForm.login"
|
|
49
|
+
@blur="loginBlur"
|
|
50
|
+
@change="clearErrorMessage"
|
|
51
|
+
>
|
|
52
|
+
<template #prefix>
|
|
53
|
+
<user-outlined />
|
|
54
|
+
</template>
|
|
55
|
+
</oio-input>
|
|
56
|
+
</oio-form-item>
|
|
57
|
+
<oio-form-item
|
|
58
|
+
name="password"
|
|
59
|
+
:help="error['password']"
|
|
60
|
+
:validateStatus="error['login'] || error['password'] ? 'error' : ''"
|
|
61
|
+
>
|
|
62
|
+
<a-input-password
|
|
63
|
+
class="login-input"
|
|
64
|
+
:placeholder="translateValueByKey(passwordPlaceholder)"
|
|
65
|
+
v-model:value="authForm.password"
|
|
66
|
+
@change="clearErrorMessage"
|
|
67
|
+
>
|
|
68
|
+
<template #prefix>
|
|
69
|
+
<lock-outlined />
|
|
70
|
+
</template>
|
|
71
|
+
</a-input-password>
|
|
72
|
+
</oio-form-item>
|
|
73
|
+
</oio-form>
|
|
74
|
+
<oio-button type="primary" block @click="login">{{ translateValueByKey(loginLabel) }}</oio-button>
|
|
75
|
+
</div>
|
|
76
|
+
<div
|
|
77
|
+
class="login-page-ugly-bg"
|
|
78
|
+
v-if="isStandLayout"
|
|
79
|
+
:style="{
|
|
80
|
+
backgroundImage: `url(${backgroundImage})`
|
|
81
|
+
}"
|
|
82
|
+
>
|
|
83
|
+
<video
|
|
84
|
+
ref="videoRef"
|
|
85
|
+
loop
|
|
86
|
+
autoplay
|
|
87
|
+
muted
|
|
88
|
+
class="login-page-video"
|
|
89
|
+
v-if="isVideoBackground"
|
|
90
|
+
:controls="false"
|
|
91
|
+
:src="backgroundImage"
|
|
92
|
+
></video>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div class="login-page-footer" v-if="copyrightStatus">
|
|
96
|
+
<div>
|
|
97
|
+
Copyrights ©{{ copyright.year }}
|
|
98
|
+
<span style="cursor: pointer" @click="onCompanyUrl">{{ translateValueByKey(copyright.company) }}</span>
|
|
99
|
+
</div>
|
|
100
|
+
<div>{{ copyright.icp }}</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</template>
|
|
104
|
+
<script lang="ts" setup>
|
|
105
|
+
import { LockOutlined, UserOutlined } from '@ant-design/icons-vue';
|
|
106
|
+
import {
|
|
107
|
+
genStaticPath,
|
|
108
|
+
getCopyrightStatus,
|
|
109
|
+
OioLoginLogoPosition,
|
|
110
|
+
OioLoginThemeConfig,
|
|
111
|
+
OioLoginThemeName,
|
|
112
|
+
translateValueByKey
|
|
113
|
+
} from '@oinone/kunlun-engine';
|
|
114
|
+
import { OioButton, OioForm, OioFormItem, OioIcon, OioInput, VIDEO_SUFFIX_LIST } from '@oinone/kunlun-vue-ui-antd';
|
|
115
|
+
import { LoginData, RuntimeLanguage } from '@oinone/kunlun-vue-ui-common';
|
|
116
|
+
import {
|
|
117
|
+
Dropdown as ADropdown,
|
|
118
|
+
InputPassword as AInputPassword,
|
|
119
|
+
Menu as AMenu,
|
|
120
|
+
MenuItem as AMenuItem
|
|
121
|
+
} from 'ant-design-vue';
|
|
122
|
+
import { computed, defineProps, nextTick, onActivated, PropType, ref } from 'vue';
|
|
123
|
+
|
|
124
|
+
const props = defineProps({
|
|
125
|
+
loginMethod: { type: String, required: true },
|
|
126
|
+
loginUrl: { type: String, required: true },
|
|
127
|
+
login: { type: Function as PropType<() => void>, required: true },
|
|
128
|
+
loginBlur: { type: Function as PropType<() => void>, required: false },
|
|
129
|
+
error: { type: Object as PropType<LoginData>, required: true },
|
|
130
|
+
clearErrorMessage: { type: Function as PropType<() => void>, required: true },
|
|
131
|
+
authForm: { type: Object as PropType<LoginData>, required: true },
|
|
132
|
+
copyrightYear: { type: [Number, String], required: true },
|
|
133
|
+
currentLoginTheme: { type: Object, required: true },
|
|
134
|
+
systemMajorConfig: { type: Object as PropType<Record<string, any>>, required: true },
|
|
135
|
+
loginLabel: { type: String, required: true },
|
|
136
|
+
accountLoginLabel: { type: String, required: true },
|
|
137
|
+
accountPlaceholder: { type: String, required: true },
|
|
138
|
+
passwordPlaceholder: { type: String, required: true },
|
|
139
|
+
enableI18n: { type: Boolean },
|
|
140
|
+
languages: { type: Array as PropType<RuntimeLanguage[]>, required: true },
|
|
141
|
+
currentLanguage: { type: Object as PropType<RuntimeLanguage>, required: true },
|
|
142
|
+
onLanguageChange: { type: Function as PropType<(language: RuntimeLanguage) => void>, required: true },
|
|
143
|
+
node_code: String
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const videoRef = ref<HTMLVideoElement>();
|
|
147
|
+
|
|
148
|
+
onActivated(() => {
|
|
149
|
+
nextTick(() => {
|
|
150
|
+
if (videoRef.value) {
|
|
151
|
+
videoRef.value.play();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 有大背景图,并且左侧布局
|
|
158
|
+
*/
|
|
159
|
+
const useCenteredKeepLeft = {
|
|
160
|
+
justifyContent: 'flex-start',
|
|
161
|
+
paddingLeft: '14%'
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 有大背景图,并且右侧布局
|
|
166
|
+
*/
|
|
167
|
+
const useCenteredKeepRight = {
|
|
168
|
+
justifyContent: 'flex-end',
|
|
169
|
+
paddingRight: '14%'
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* logo的样式
|
|
174
|
+
*/
|
|
175
|
+
const logoStyle = computed(() => {
|
|
176
|
+
return loginThemeConfig.value.logoPosition === OioLoginLogoPosition.LEFT ? { left: '60px' } : { right: '60px' };
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 背景图
|
|
181
|
+
*/
|
|
182
|
+
const backgroundImage = computed(() => {
|
|
183
|
+
return loginThemeConfig.value.backgroundImage;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* logo
|
|
188
|
+
*/
|
|
189
|
+
const loginPageLogo = computed(() => loginThemeConfig.value.logo || '');
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 当前背景图是不是视频
|
|
193
|
+
*/
|
|
194
|
+
const isVideoBackground = computed(() => {
|
|
195
|
+
if (backgroundImage.value) {
|
|
196
|
+
const isVideo = !!VIDEO_SUFFIX_LIST.find((suffix) => backgroundImage.value.endsWith(suffix));
|
|
197
|
+
|
|
198
|
+
return isVideo;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return false;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 默认背景图样式
|
|
206
|
+
*/
|
|
207
|
+
const useCenteredStyle = computed(() => {
|
|
208
|
+
return {
|
|
209
|
+
display: 'flex',
|
|
210
|
+
justifyContent: 'center',
|
|
211
|
+
alignItems: 'center',
|
|
212
|
+
backgroundImage: isVideoBackground.value ? '' : `url(${backgroundImage.value})`
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 登录页的样式
|
|
218
|
+
*/
|
|
219
|
+
const loginContainerStyle = computed(() => {
|
|
220
|
+
const { name, backgroundColor } = loginThemeConfig.value;
|
|
221
|
+
let style = {
|
|
222
|
+
display: 'flex',
|
|
223
|
+
justifyContent: 'space-between',
|
|
224
|
+
alignItems: 'center',
|
|
225
|
+
backgroundColor
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
switch (name) {
|
|
229
|
+
case 'CENTER_STICK':
|
|
230
|
+
case 'CENTER_STICK_LOGO':
|
|
231
|
+
style = Object.assign(style, useCenteredStyle.value);
|
|
232
|
+
break;
|
|
233
|
+
case 'LEFT_STICK':
|
|
234
|
+
style = Object.assign(style, useCenteredStyle.value, useCenteredKeepLeft);
|
|
235
|
+
break;
|
|
236
|
+
case 'RIGHT_STICK':
|
|
237
|
+
style = Object.assign(style, useCenteredStyle.value, useCenteredKeepRight);
|
|
238
|
+
break;
|
|
239
|
+
case 'STAND_RIGHT':
|
|
240
|
+
style = Object.assign(style, {
|
|
241
|
+
flexDirection: 'row-Reverse'
|
|
242
|
+
});
|
|
243
|
+
break;
|
|
244
|
+
default:
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return style;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* logo 是否显示在页面左侧
|
|
253
|
+
*/
|
|
254
|
+
const logoInLeft = computed(() => {
|
|
255
|
+
return (
|
|
256
|
+
loginThemeConfig.value.logoPosition !== OioLoginLogoPosition.CENTER &&
|
|
257
|
+
loginThemeConfig.value.name !== OioLoginThemeName.CENTER_STICK_LOGO
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const isStandLayout = computed(() => {
|
|
262
|
+
return [OioLoginThemeName.STAND_LEFT, OioLoginThemeName.STAND_RIGHT].includes(loginThemeConfig.value.name);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 登录form表单的样式
|
|
267
|
+
*/
|
|
268
|
+
const loginBlockStyle = computed(() => {
|
|
269
|
+
const style: Record<string, string> = {};
|
|
270
|
+
const { name } = loginThemeConfig.value;
|
|
271
|
+
if ([OioLoginThemeName.STAND_LEFT, OioLoginThemeName.STAND_RIGHT].includes(name)) {
|
|
272
|
+
style.boxShadow = 'none';
|
|
273
|
+
style.margin = '0 150px';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return style;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 登录主题配置
|
|
281
|
+
*/
|
|
282
|
+
const loginThemeConfig = computed<Required<OioLoginThemeConfig>>(() => {
|
|
283
|
+
const name = props.currentLoginTheme.name || OioLoginThemeName.STAND_RIGHT;
|
|
284
|
+
const defaultImage = [OioLoginThemeName.STAND_LEFT, OioLoginThemeName.STAND_RIGHT].includes(name)
|
|
285
|
+
? genStaticPath('login_bg_left.jpg')
|
|
286
|
+
: genStaticPath('login_big_image@2x-1.png');
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
name,
|
|
290
|
+
backgroundImage: props.currentLoginTheme.backgroundImage || defaultImage,
|
|
291
|
+
backgroundColor: props.currentLoginTheme.backgroundColor!,
|
|
292
|
+
logo: props.currentLoginTheme.logo,
|
|
293
|
+
logoPosition: props.currentLoginTheme.logoPosition || OioLoginLogoPosition.LEFT
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const copyrightStatus = getCopyrightStatus();
|
|
298
|
+
|
|
299
|
+
const copyright = computed(() => {
|
|
300
|
+
return {
|
|
301
|
+
year: props.copyrightYear || '',
|
|
302
|
+
company: props.systemMajorConfig?.partnerName || '',
|
|
303
|
+
icp: props.systemMajorConfig?.icpDesc || ''
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const onCompanyUrl = () => {
|
|
308
|
+
if (props.systemMajorConfig.officialWebsite) {
|
|
309
|
+
window.open(props.systemMajorConfig.officialWebsite, '_blank');
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
</script>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { translateValueByKey } from '@oinone/kunlun-engine';
|
|
2
|
+
import { UrlHelper } from '@oinone/kunlun-shared';
|
|
3
|
+
import { SPI } from '@oinone/kunlun-spi';
|
|
4
|
+
import { RouterWidget } from '@oinone/kunlun-vue-router';
|
|
5
|
+
import { Widget } from '@oinone/kunlun-vue-widget';
|
|
6
|
+
import { BaseLoginWidget } from './BaseLoginWidget';
|
|
7
|
+
import SSOLogin from './SSOLogin.vue';
|
|
8
|
+
|
|
9
|
+
@SPI.ClassFactory(
|
|
10
|
+
RouterWidget.Token({
|
|
11
|
+
widget: 'sso-login'
|
|
12
|
+
})
|
|
13
|
+
)
|
|
14
|
+
export class SSOLoginWidget extends BaseLoginWidget {
|
|
15
|
+
public initialize(props) {
|
|
16
|
+
super.initialize(props);
|
|
17
|
+
this.setComponent(SSOLogin);
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected get keyMapping(): Record<string, string> {
|
|
22
|
+
return {
|
|
23
|
+
login: 'username',
|
|
24
|
+
client_id: 'clientId',
|
|
25
|
+
redirect_uri: 'redirectUri'
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected mappingForKey(key: string): string {
|
|
30
|
+
return this.keyMapping[key] || key;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Widget.Reactive()
|
|
34
|
+
protected get loginMethod(): string {
|
|
35
|
+
return 'POST';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Widget.Reactive()
|
|
39
|
+
protected get loginUrl() {
|
|
40
|
+
return UrlHelper.appendBasePath('/pamirs/sso/oauth2/login');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Widget.Method()
|
|
44
|
+
public async login() {
|
|
45
|
+
const rst = await this.beforeClick();
|
|
46
|
+
if (!rst) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { login, password } = this.authForm;
|
|
51
|
+
if (!login || !password) {
|
|
52
|
+
this.error.login = login ? '' : translateValueByKey(this.errorMessages.loginEmpty);
|
|
53
|
+
this.error.password = password ? '' : translateValueByKey(this.errorMessages.passwordEmpty);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const formData: Record<string, string> = {};
|
|
58
|
+
formData[this.mappingForKey('login')] = login;
|
|
59
|
+
formData[this.mappingForKey('password')] = password;
|
|
60
|
+
Object.entries(this.getUrlParameters()).forEach(([key, value]) => {
|
|
61
|
+
if (key === 'error') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
formData[this.mappingForKey(key)] = value;
|
|
65
|
+
});
|
|
66
|
+
this.submitLoginFormData(formData);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected submitLoginFormData(formData: Record<string, string>) {
|
|
70
|
+
// 1. 创建 form 元素
|
|
71
|
+
const form = document.createElement('form');
|
|
72
|
+
form.action = this.loginUrl; // 提交的目标地址(服务端可能返回 302 重定向)
|
|
73
|
+
form.method = this.loginMethod; // 提交方法(POST/GET)
|
|
74
|
+
form.style.display = 'none'; // 隐藏表单
|
|
75
|
+
|
|
76
|
+
// 2. 添加表单数据(键值对)
|
|
77
|
+
Object.entries(formData).forEach(([key, value]) => {
|
|
78
|
+
const input = document.createElement('input');
|
|
79
|
+
input.name = key; // 字段名
|
|
80
|
+
input.value = value; // 字段值
|
|
81
|
+
form.appendChild(input);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 3. 将表单添加到页面并提交
|
|
85
|
+
document.body.appendChild(form);
|
|
86
|
+
form.submit(); // 触发提交,浏览器会自动处理 302 重定向
|
|
87
|
+
|
|
88
|
+
// 4. 清理(可选,提交后页面会跳转,可能无需清理)
|
|
89
|
+
document.body.removeChild(form);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected getUrlParameters(): Record<string, string> {
|
|
93
|
+
return this.matched?.segmentParams['sso-login'] || {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
protected async beforeMount() {
|
|
97
|
+
await super.beforeMount();
|
|
98
|
+
const { error } = this.getUrlParameters();
|
|
99
|
+
if (error) {
|
|
100
|
+
this.error.password = error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
package/src/view/login/index.ts
CHANGED