@lambo-design/login-form 1.0.0-beta.4 → 1.0.0-beta.41

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/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import LamboLoginForm from './src/login-form';
2
2
  import LamboLoginPage from './src/login-page';
3
+ import LoginFormQr from './src/login-qr';
3
4
  export default LamboLoginForm;
4
5
  export {
5
6
  LamboLoginForm,
6
- LamboLoginPage
7
+ LamboLoginPage,
8
+ LoginFormQr
7
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lambo-design/login-form",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.41",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "author": "lambo",
@@ -9,9 +9,16 @@
9
9
  "access": "public",
10
10
  "registry": "https://registry.npmjs.org/"
11
11
  },
12
- "dependencies": {
13
- "@lambo-design/core": "^4.7.1-beta.39",
14
- "@lambo-design/shared": "^1.0.0-beta.18"
12
+ "devDependencies": {
13
+ "@lambo-design/core": "^4.7.1-beta.180",
14
+ "@lambo-design/shared": "^1.0.0-beta.364"
15
15
  },
16
- "scripts": {}
16
+ "scripts": {
17
+ "release": "pnpm release-beta && git push --follow-tags && pnpm re-publish",
18
+ "release-major": "standard-version --release-as major",
19
+ "release-minor": "standard-version --release-as minor",
20
+ "release-patch": "standard-version --release-as patch",
21
+ "release-beta": "standard-version --prerelease beta",
22
+ "re-publish": "pnpm publish --access public --no-git-checks"
23
+ }
17
24
  }
@@ -1,34 +1,75 @@
1
1
  <template>
2
2
  <Form ref="loginForm" class="lambo-login-form"
3
- :model="form" :rules="rules"
3
+ :model="form" :rules="rules" :label-width="0"
4
4
  @keydown.enter.native="handleSubmit">
5
5
  <FormItem prop="userName">
6
- <Input v-model="form.userName" placeholder="请输入用户名">
6
+ <Input v-model="form.userName" :placeholder="t('login-form.box.form-user-name')">
7
7
  <span slot="prepend">
8
8
  <Icon :size="16" type="ios-person"></Icon>
9
9
  </span>
10
10
  </Input>
11
11
  </FormItem>
12
12
  <FormItem prop="password">
13
- <Input v-model="form.password" placeholder="请输入密码" type="password">
13
+ <Input v-model="form.password" password :placeholder="t('login-form.box.form-user-pas')" type="password">
14
14
  <span slot="prepend">
15
15
  <Icon :size="14" type="md-lock"></Icon>
16
16
  </span>
17
17
  </Input>
18
18
  </FormItem>
19
- <FormItem prop="validCode">
20
- <Input v-model="form.validCode" class="captcha" placeholder="请输入验证码">
19
+ <FormItem prop="validCode" class="captcha-container" v-if="captchaMethod === t('login-form.other.captcha-method-image')">
20
+ <Input v-model="form.validCode" class="captcha" :placeholder="t('login-form.box.form-user-valid-code')">
21
21
  <template slot="prepend">
22
- <img class="prefix" src="./styles/images/captcha.svg">
22
+ <img class="prefix" src="@/assets/images/captcha.svg">
23
23
  </template>
24
24
  </Input>
25
25
  <div class="Imgbox"><img :src="captchaUrl" class="captchaImg" @click="getCaptcha"></div>
26
26
  </FormItem>
27
+ <FormItem prop="validCode" v-if="captchaMethod === t('login-form.other.captcha-method-sms')">
28
+ <Input
29
+ search
30
+ :enter-button="ValidNumText"
31
+ :placeholder="t('login-form.box.form-user-sms-code')"
32
+ v-model="form.validCode"
33
+ @on-search="getVaildCode">
34
+ <template slot="prepend">
35
+ <img class="prefix" src="@/assets/images/captcha.svg">
36
+ </template>
37
+ </Input>
38
+ </FormItem>
39
+ <FormItem prop="validCode" v-if="captchaMethod === t('login-form.other.captcha-method-mail')">
40
+ <Input
41
+ search
42
+ :enter-button="ValidNumText"
43
+ :placeholder="t('login-form.box.form-user-mail-code')"
44
+ v-model="form.validCode"
45
+ @on-search="getVaildCode">
46
+ <template slot="prepend">
47
+ <img class="prefix" src="@/assets/images/captcha.svg">
48
+ </template>
49
+ </Input>
50
+ </FormItem>
51
+ <FormItem v-if="captchaMethod === t('login-form.other.captcha-method-slide-image')">
52
+ <Button long :disabled="isDisabled" @click="showSlideVerify">{{ buttonText }}</Button>
53
+ </FormItem>
54
+ <FormItem v-if="captchaMethod === '文字点选' || captchaMethod === '语序点选' || captchaMethod === '旋转验证码'">
55
+ <Button long :disabled="isDisabled" @click="showWordClickVerify">{{ buttonText }}</Button>
56
+ </FormItem>
57
+ <FormItem v-if="captchaMethod === t('login-form.other.captcha-method-message-center') && channelDataList.length > 0 && channelDataList.length !== 1">
58
+ <span class="login-method-label">请选择登录验证方式</span>
59
+ <ButtonGroup style="display: flex;">
60
+ <Button v-for="channel in channelDataList"
61
+ :key="channel.channelName" long :type="selectedLoginMethod === channel.channelName ? 'primary' : 'default'"
62
+ @click="selectLoginMethod(channel.channelName)">{{ channel.channelName }}
63
+ </Button>
64
+ </ButtonGroup>
65
+ </FormItem>
27
66
  <FormItem>
28
- <Checkbox v-model="rememberMe"><span class="remember">记住用户名</span></Checkbox>
67
+ <Checkbox v-if="showRememberMe === '1'" v-model="rememberMe"><span class="remember">{{ t('login-form.box.form-remember') }}</span></Checkbox>
68
+ <label v-if="showForgetPassword === '1'" style="float: right;"><router-link to="/forgetPasswordForget">{{ t('login-form.box.form-forget') }}</router-link></label>
29
69
  </FormItem>
30
70
  <FormItem>
31
- <Button :loading="loading" long type="primary" @click="handleSubmit">登录</Button>
71
+ <Button v-if="!isNeedBind" long type="primary" @click="handleSubmit">{{ t('login-form.box.form-login') }}</Button>
72
+ <Button v-else long type="primary" @click="handleSubmit">{{ t('login-form.box.form-rule-bind') }}</Button>
32
73
  </FormItem>
33
74
  </Form>
34
75
  </template>
@@ -37,31 +78,40 @@ import config from "@lambo-design/shared/config/config";
37
78
  import {getLocalStorage, removeLocalStorage, setLocalStorage} from "@lambo-design/shared/utils/platform";
38
79
  import {guid} from "@lambo-design/shared/utils/number";
39
80
  import crypto from "@lambo-design/shared/utils/crypto"
81
+ import ajax from "@lambo-design/shared/utils/ajax";
82
+ import Locale from "@lambo-design/core/src/mixins/locale";
40
83
 
41
84
  export default {
42
85
  name: 'LoginForm',
86
+ mixins: [Locale],
87
+ components:{
88
+ },
43
89
  props: {
44
90
  userNameRules: {
45
91
  type: Array,
46
- default: () => {
92
+ default: function() {
47
93
  return [
48
- {required: true, message: '账号不能为空', trigger: 'blur'}
94
+ {required: true, message: this.$t('login-form.box.form-rule-user-name'), trigger: 'blur'}
49
95
  ]
50
96
  }
51
97
  },
98
+ isNeedBind:{
99
+ type: Boolean,
100
+ default: false
101
+ },
52
102
  passwordRules: {
53
103
  type: Array,
54
- default: () => {
104
+ default: function() {
55
105
  return [
56
- {required: true, message: '密码不能为空', trigger: 'blur'}
106
+ {required: true, message: this.$t('login-form.box.form-rule-user-pas'), trigger: 'blur'}
57
107
  ]
58
108
  }
59
109
  },
60
110
  validCodeRules: {
61
111
  type: Array,
62
- default: () => {
112
+ default: function() {
63
113
  return [
64
- {required: true, message: '验证码不能为空', trigger: 'blur'}
114
+ {required: true, message: this.$t('login-form.box.form-rule-valid-code'), trigger: 'blur'}
65
115
  ]
66
116
  }
67
117
  },
@@ -76,17 +126,47 @@ export default {
76
126
  pasCryptor: {
77
127
  type: String,
78
128
  default: 'sm3'
129
+ },
130
+ captchaMethod:{type: String},
131
+ showForgetPassword:{type: String},
132
+ showRememberMe:{
133
+ type: String,
134
+ default: '1'
135
+ },
136
+ passwordRetrievalMethod:{type: Array},
137
+ smConfigId:{type: String},
138
+ templateId:{type: String},
139
+ mailConfigId:{type: String},
140
+ isSlideVerify:{type: Boolean},
141
+ isWordClickVerify:{type: Boolean},
142
+ buttonText: {type:String}, // 初始按钮文本
143
+ isDisabled: {type:Boolean},
144
+ channelDataList:{
145
+ type: Array,
79
146
  }
80
147
  },
81
148
  data() {
82
149
  return {
150
+ ValidNumText: this.t('login-form.other.get-valid-code'),
151
+ selectedLoginMethod: null,
83
152
  form: {
84
153
  userName: '',
85
154
  password: '',
86
155
  validCode: ''
87
156
  },
88
157
  rememberMe: false,
89
- validCodeId: ''
158
+ validCodeId: '',
159
+ countDown: 60,
160
+ countDownNum: 60,
161
+ }
162
+ },
163
+ watch: {
164
+ // 监听channelDataList,当有数据时设置默认选中
165
+ channelDataList(newVal) {
166
+ if (newVal && newVal.length > 0 && !this.selectedLoginMethod) {
167
+ this.selectedLoginMethod = newVal[0].channelName;
168
+ this.$emit('login-method-change', this.selectedLoginMethod);
169
+ }
90
170
  }
91
171
  },
92
172
  computed: {
@@ -112,6 +192,11 @@ export default {
112
192
  }
113
193
  },
114
194
  methods: {
195
+ //添加切换选中逻辑
196
+ selectLoginMethod(method) {
197
+ this.selectedLoginMethod = method;
198
+ this.$emit('login-method-change', this.selectedLoginMethod);
199
+ },
115
200
  getCryptorPassword(password, validCode , cryptoMethod) {
116
201
  return crypto.encrypt(
117
202
  crypto.encrypt(password, cryptoMethod, true) + validCode,
@@ -119,9 +204,136 @@ export default {
119
204
  false
120
205
  )
121
206
  },
207
+ getVaildCode() {
208
+ let self = this;
209
+ if (self.ValidNumText == self.t('login-form.other.get-valid-code')) {
210
+ self.ValidNumText = self.countDown + self.t('login-form.other.get-valid-code-later');
211
+ self.clock = window.setInterval(() => {
212
+ self.countDown--;
213
+ self.ValidNumText = self.countDown + self.t('login-form.other.get-valid-code-later');
214
+ if (self.countDown <= 0) {
215
+ window.clearInterval(self.clock);
216
+ self.ValidNumText = self.t('login-form.other.get-valid-code');
217
+ self.countDown = self.countDownNum;
218
+ }
219
+ }, 1000);
220
+ let param
221
+ let url
222
+ if(self.captchaMethod === self.t('login-form.other.captcha-method-mail')){
223
+ url = config.upmsServerContext + "/anon/user/sendMailForLogin"
224
+ param = {
225
+ userId: self.form.userName,
226
+ //mailAddress: self.form.mailAddress
227
+ validCodeId: self.validCodeId,
228
+ mailConfigId: self.mailConfigId
229
+ };
230
+ }
231
+ if(self.captchaMethod === self.t('login-form.other.captcha-method-sms')){
232
+ url = config.upmsServerContext + "/anon/user/sendSmsForLogin"
233
+ param = {
234
+ userId: self.form.userName,
235
+ //phoneNumber: self.form.phoneNumber
236
+ validCodeId: self.validCodeId,
237
+ smConfigId: self.smConfigId,
238
+ templateId: self.templateId
239
+ }
240
+ }
241
+ if (self.form.userName && self.form.userName.trim().length > 0){
242
+ ajax
243
+ .post(url, param)
244
+ .then(function(resp) {
245
+ if (resp.data.code == 200) {
246
+ self.$Message.success({
247
+ content: self.t('login-form.other.send-success'),})
248
+ } else {
249
+ self.$Message.error(resp.data.data);
250
+ window.clearInterval(self.clock);
251
+ self.ValidNumText = self.t('login-form.other.get-valid-code');
252
+ self.countDown = self.countDownNum;
253
+ }
254
+ }
255
+ );
256
+ }else{
257
+ self.$Message.error(self.t('login-form.box.form-user-name'));
258
+ window.clearInterval(self.clock);
259
+ self.ValidNumText = self.t('login-form.other.get-valid-code');
260
+ self.countDown = self.countDownNum;
261
+ }
262
+ }
263
+ },
264
+ showSlideVerify(){
265
+ if (this.form.userName && this.form.userName.trim().length > 0){
266
+ let params = {
267
+ userName: this.form.userName,
268
+ password: this.form.password,
269
+ validCodeInput: this.form.validCode,
270
+ validCodeId: this.validCodeId
271
+ }
272
+ if (this.pasCryptor) {
273
+ params.pasCryptor = this.pasCryptor;
274
+ params.pasEncode = this.getCryptorPassword(this.form.password,this.form.validCode,this.pasCryptor);
275
+ } else {
276
+ params.pasMd5 = this.getCryptorPassword(this.form.password,this.form.validCode,'md5');
277
+ params.pasSm3 = this.getCryptorPassword(this.form.password,this.form.validCode,'sm3');
278
+ }
279
+ this.$emit('show-slide-verify', true,params)
280
+
281
+ this.setLocalUserId(this.form.userName)
282
+ if (this.rememberMe == true) {
283
+ this.setLocalStorage(this.form.userName)
284
+ }
285
+ if (this.rememberMe == false) {
286
+ this.clearLocalStorage();
287
+ }
288
+ }else{
289
+ this.$Message.error(this.t('login-form.box.form-user-name'));
290
+ }
291
+ },
292
+ showWordClickVerify(){
293
+ if (this.form.userName && this.form.userName.trim().length > 0){
294
+ let params = {
295
+ userName: this.form.userName,
296
+ password: this.form.password,
297
+ validCodeInput: this.form.validCode,
298
+ validCodeId: this.validCodeId
299
+ }
300
+ if (this.pasCryptor) {
301
+ params.pasCryptor = this.pasCryptor;
302
+ params.pasEncode = this.getCryptorPassword(this.form.password,this.form.validCode,this.pasCryptor);
303
+ } else {
304
+ params.pasMd5 = this.getCryptorPassword(this.form.password,this.form.validCode,'md5');
305
+ params.pasSm3 = this.getCryptorPassword(this.form.password,this.form.validCode,'sm3');
306
+ }
307
+ this.$emit('show-word-click-verify', true, params, this.captchaMethod)
308
+
309
+ this.setLocalUserId(this.form.userName)
310
+ if (this.rememberMe == true) {
311
+ this.setLocalStorage(this.form.userName)
312
+ }
313
+ if (this.rememberMe == false) {
314
+ this.clearLocalStorage();
315
+ }
316
+ }else{
317
+ this.$Message.error(this.t('login-form.box.form-user-name'));
318
+ }
319
+ },
122
320
  handleSubmit() {
123
321
  this.$refs.loginForm.validate((valid) => {
124
- if (valid) {
322
+ if (this.captchaMethod === this.t('login-form.other.captcha-method-message-center') && !this.selectedLoginMethod) {
323
+ this.$Message.error(this.t('login-form.box.form-rule-choose-verify-method'));
324
+ return
325
+ }
326
+ if(valid && this.captchaMethod == this.t('login-form.other.captcha-method-slide-image')
327
+ && !this.isSlideVerify){
328
+ this.$Message.error(this.t('login-form.box.form-rule-click-login'));
329
+ }
330
+ const isWordClickMethod = this.captchaMethod === '文字点选' || this.captchaMethod === '语序点选' || this.captchaMethod === '旋转验证码'
331
+ if(valid && isWordClickMethod && !this.isWordClickVerify){
332
+ this.$Message.error(this.t('login-form.box.form-rule-click-login'));
333
+ }
334
+ if (valid && ((this.captchaMethod == this.t('login-form.other.captcha-method-slide-image')
335
+ && this.isSlideVerify) || (isWordClickMethod && this.isWordClickVerify)
336
+ || (!isWordClickMethod && this.captchaMethod != this.t('login-form.other.captcha-method-slide-image')))) {
125
337
  let params = {
126
338
  userName: this.form.userName,
127
339
  password: this.form.password,
@@ -136,6 +348,7 @@ export default {
136
348
  params.pasSm3 = this.getCryptorPassword(this.form.password,this.form.validCode,'sm3');
137
349
  }
138
350
  this.$emit('on-success-valid', params)
351
+ this.setLocalUserId(this.form.userName)
139
352
  if (this.rememberMe == true) {
140
353
  this.setLocalStorage(this.form.userName)
141
354
  }
@@ -154,6 +367,9 @@ export default {
154
367
  setLocalStorage(name) {
155
368
  setLocalStorage("username", name);
156
369
  },
370
+ setLocalUserId(name){
371
+ setLocalStorage('userId',name)
372
+ },
157
373
  clearLocalStorage() {
158
374
  removeLocalStorage("username");
159
375
  }
@@ -161,9 +377,14 @@ export default {
161
377
  mounted() {
162
378
  this.getCaptcha();
163
379
  this.getLocalStorage();
380
+ // 新增:初始化时检查 channelDataList
381
+ if (this.channelDataList && this.channelDataList.length > 0 && !this.selectedLoginMethod) {
382
+ this.selectedLoginMethod = this.channelDataList[0].channelName;
383
+ this.$emit('login-method-change', this.selectedLoginMethod);
384
+ }
164
385
  }
165
386
  }
166
387
  </script>
167
388
  <style lang="less">
168
- @import 'styles/css/login-form';
389
+ @import 'styles/css/login-form';
169
390
  </style>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="lambo-login" :style="backGroundStyle">
3
3
  <div class="lambo-login-con" :style="formStyle">
4
- <Card :bordered="false" icon="log-in" title="欢迎登录">
4
+ <Card :bordered="false" icon="log-in" :title="t('login-page.box.welcome-login')">
5
5
  <div class="lambo-form-con">
6
6
  <LamboLoginForm ref="login" :loading="formLoading"
7
7
  :captcha-img-url="formCaptchaImgUrl"
@@ -15,9 +15,11 @@
15
15
 
16
16
  <script>
17
17
  import LamboLoginForm from './login-form'
18
+ import Locale from "@lambo-design/core/src/mixins/locale";
18
19
 
19
20
  export default {
20
21
  name: 'LamboLoginPage',
22
+ mixins: [Locale],
21
23
  components: {
22
24
  LamboLoginForm
23
25
  },
@@ -0,0 +1,559 @@
1
+ <template>
2
+ <div class="lambo-login-qrcode" :class="wxQrClass">
3
+ <!-- 二维码加载状态 -->
4
+ <div v-if="loading" class="qrcode-loading">
5
+ <Icon type="ios-loading" size="24" spin></Icon>
6
+ <span>{{ t('login-form.other.loading') }}</span>
7
+ </div>
8
+ <!-- 二维码显示区域 -->
9
+ <div class="qrcode-img-wrapper">
10
+ <!-- 企业微信登录 -->
11
+ <div v-if="currentLoginType === 'wechat'" class="qrcode-item">
12
+ <div id="ww_login"></div>
13
+ </div>
14
+ <!-- 钉钉登录 -->
15
+ <div v-if="currentLoginType === 'dingtalk'" class="qrcode-item">
16
+ <div id="dingtalk_login"></div>
17
+ </div>
18
+ </div>
19
+ <div class="login-type-switch">
20
+ <img
21
+ v-if="isWx"
22
+ class="switch-item"
23
+ :src="wxImg"
24
+ :class="{ active: currentLoginType === 'wechat' }"
25
+ @click="switchLoginType('wechat')"
26
+ alt="企微登录"
27
+ >
28
+ <img
29
+ v-if="isDd"
30
+ class="switch-item"
31
+ :src="ddImg"
32
+ :class="{ active: currentLoginType === 'dingtalk' }"
33
+ @click="switchLoginType('dingtalk')"
34
+ alt="钉钉登录"
35
+ >
36
+ </div>
37
+ <!-- <div class="qrcode-refresh">
38
+ <Icon type="ios-refresh" size="16" @click="refreshWeChatQrCode"></Icon>
39
+ <span>{{ t('login-form.other.refresh-qrcode') }}</span>
40
+ </div>-->
41
+ </div>
42
+ </template>
43
+ <script>
44
+ import Locale from "@lambo-design/core/src/mixins/locale";
45
+ import ajax from '@lambo-design/shared/utils/ajax';
46
+ import config from "@lambo-design/shared/config/config";
47
+ import _ from 'lodash'
48
+ import wxImg from './styles/images/wx-img.png'
49
+ import ddImg from './styles/images/dd-img.png'
50
+ export default {
51
+ name: 'LoginFormQr',
52
+ mixins: [Locale],
53
+ components: {
54
+ },
55
+ props: {
56
+ loading: {
57
+ type: Boolean,
58
+ default: false
59
+ },
60
+ qrType:{
61
+ type: Array,
62
+ default: ['0','1']
63
+ },
64
+ // 企业微信登录模式: 'saas' (SaaS版) 或 'private' (私有化版)
65
+ wxLoginMode: {
66
+ type: String,
67
+ default: 'saas',
68
+ validator: (value) => ['saas', 'private'].includes(value)
69
+ },
70
+ // 私有化版本配置(当 wxLoginMode 为 'private' 时使用)
71
+ privateWxConfig: {
72
+ type: Object,
73
+ default: () => ({
74
+ appId: '',
75
+ agentId: '',
76
+ redirectUri: '',
77
+ sdkUrl: ''
78
+ })
79
+ }
80
+ },
81
+ data() {
82
+ return {
83
+ bindToken:'',
84
+ ddClientId: '',
85
+ wwLoginInstance: null,
86
+ dingTalkLoginInstance: null,
87
+ currentLoginType: '',
88
+ LoginConfig: {},
89
+ wxImg,
90
+ ddImg
91
+ }
92
+ },
93
+ computed:{
94
+ isWx(){
95
+ return _.some(this.qrType, (item) => item ==='0');
96
+ },
97
+ isDd(){
98
+ return _.some(this.qrType, (item) => item ==='1');
99
+ },
100
+ wxQrClass() {
101
+ return this.wxLoginMode === 'private' ? 'wx-private' : 'wx-saas'
102
+ }
103
+ },
104
+ mounted() {
105
+ this.currentLoginType = this.qrType.includes('0') ? 'wechat' : 'dingtalk';
106
+ this.initLoginInstance();
107
+ },
108
+ beforeDestroy() {
109
+ this.destroyWwLogin();
110
+ this.destroyDingTalkLogin();
111
+ },
112
+ methods: {
113
+ loadWxWorkSdk() {
114
+ return new Promise((resolve) => {
115
+ if (typeof ww !== 'undefined' || typeof WwLogin !== 'undefined') {
116
+ resolve();
117
+ return;
118
+ }
119
+ const script = document.createElement('script');
120
+ // 根据登录模式选择不同的SDK
121
+ if (this.wxLoginMode === 'private') {
122
+ // 私有化版本SDK,优先使用传入的地址
123
+ script.src = this.privateWxConfig.sdkUrl;
124
+ } else {
125
+ // SaaS版本SDK
126
+ script.src = 'https://wwcdn.weixin.qq.com/node/open/js/wecom-jssdk-2.3.3.js';
127
+ }
128
+ script.onload = resolve;
129
+ document.head.appendChild(script);
130
+ });
131
+ },
132
+ // 加载钉钉SDK - 改写成Promise写法
133
+ loadDingTalkSdk() {
134
+ return new Promise((resolve) => {
135
+ if (typeof window.DTFrameLogin !== 'undefined') {
136
+ console.log('钉钉SDK已存在,直接使用');
137
+ resolve();
138
+ return;
139
+ }
140
+ console.log('开始加载钉钉登录脚本...');
141
+ const script = document.createElement('script');
142
+ script.src = 'https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js';
143
+ script.onload = () => {
144
+ console.log('钉钉登录脚本加载成功');
145
+ resolve();
146
+ };
147
+ document.head.appendChild(script);
148
+ });
149
+ },
150
+ async initWwLogin() {
151
+ // 确保容器存在且实例未创建
152
+ if (!document.getElementById('ww_login') || this.wwLoginInstance) {
153
+ return;
154
+ }
155
+
156
+ if (this.wxLoginMode === 'private') {
157
+ // 私有化版本初始化
158
+ await this.initPrivateWwLogin();
159
+ } else {
160
+ // SaaS版本初始化
161
+ await this.initSaasWwLogin();
162
+ }
163
+ },
164
+
165
+ // 私有化版本企业微信登录初始化
166
+ async initPrivateWwLogin() {
167
+ try {
168
+ this.wwLoginInstance = window.WwLogin({
169
+ "id": "ww_login",
170
+ "appid": this.privateWxConfig.appId,
171
+ "agentid": this.privateWxConfig.agentId,
172
+ "redirect_uri": this.privateWxConfig.redirectUri,
173
+ "state": 'state_' + Date.now(),
174
+ "href": ""
175
+ });
176
+ console.log('✅ 私有化企业微信登录组件初始化成功');
177
+ } catch (error) {
178
+ console.error('❌ 初始化私有化企业微信登录组件失败:', error);
179
+ }
180
+ },
181
+
182
+ // SaaS版本企业微信登录初始化
183
+ async initSaasWwLogin() {
184
+ let appId;
185
+ let agentId;
186
+ let res = await ajax.get(config.authServerContext + '/sso/wxcp/getWxInfo');
187
+ if (res.data.code === 1) {
188
+ appId = res.data.data.appId;
189
+ agentId = res.data.data.agentId;
190
+ }
191
+ this.LoginConfig = {
192
+ login_type: 'CorpApp',
193
+ appid: appId,
194
+ agentid: agentId,
195
+ redirect_uri: window.location.origin + config.authServerContext +'/sso/wxcp/getCode',
196
+ state: 'state_' + Date.now(),
197
+ redirect_type: 'callback',
198
+ quick_login: false,
199
+ panel_size:'small'
200
+ };
201
+ try {
202
+ this.wwLoginInstance = ww.createWWLoginPanel({
203
+ el: '#ww_login',
204
+ params: this.LoginConfig,
205
+ onLoginSuccess: this.handleLoginSuccess,
206
+ onLoginFail: this.handleLoginFail
207
+ });
208
+ console.log('✅ SaaS企业微信登录组件初始化成功');
209
+ } catch (error) {
210
+ console.error('❌ 初始化SaaS企业微信登录组件失败:', error);
211
+ }
212
+ },
213
+ handleLoginSuccess({ code }) {
214
+ console.log('获取到授权码 code:', code);
215
+ ajax.get(config.authServerContext+'/sso/wxcp/getCode?code='+code +'&state='+this.LoginConfig.state+'&corpId='+this.LoginConfig.appid+'&agentId='+this.LoginConfig.agentid)
216
+ .then(response => {
217
+ if(response.data.code === 1){
218
+ let token = response.data.token;
219
+ if (!token) {
220
+ this.$Message.error('登录失败,token为空');
221
+ return;
222
+ }
223
+ this.$emit('login-success', { token });
224
+ }else if(response.data.code === 119){
225
+ this.$emit('need-bind', {bindToken: response.data.token ,bindType:'wx'});
226
+ }else {
227
+ this.$Message.error(response.data.msg);
228
+ }
229
+ }).catch(e => {
230
+ console.error(e);
231
+ this.$Message.error('登录失败');
232
+ });
233
+ },
234
+ handleLoginFail(err) {
235
+ console.error('扫码登录失败:', err);
236
+ // 可以根据 err.errCode 给用户友好提示
237
+ this.$Message.error(err);
238
+ },
239
+ destroyWwLogin() {
240
+ if (this.wwLoginInstance) {
241
+ const container = document.getElementById('ww_login');
242
+ if (container) {
243
+ container.innerHTML = ''; // 清空容器
244
+ }
245
+ this.wwLoginInstance = null;
246
+ console.log('企业微信登录实例已销毁');
247
+ }
248
+
249
+ },
250
+ destroyDingTalkLogin() {
251
+ const container = document.getElementById('dingtalk_login');
252
+ if (container) {
253
+ container.innerHTML = '';
254
+ }
255
+
256
+ this.dingTalkLoginInstance = null;
257
+ console.log('钉钉登录实例已销毁');
258
+ },
259
+
260
+ async initDingTalkLogin() {
261
+ const container = document.getElementById('dingtalk_login');
262
+ if (!container || this.dingTalkLoginInstance) {
263
+ console.error('容器不存在或实例已创建');
264
+ return;
265
+ }
266
+
267
+ try {
268
+ console.log('开始初始化钉钉登录组件...');
269
+ let res = await ajax.get(config.authServerContext + '/sso/dd/getClientId')
270
+ if (res && res.data && res.data.code === 1) {
271
+ this.ddClientId = res.data.data;
272
+ }
273
+ // 获取完整的URL地址
274
+ const fullUrl = window.location.origin+'/ibp/homeIndex';
275
+
276
+ container.innerHTML = '';
277
+ this.dingTalkLoginInstance = window.DTFrameLogin(
278
+ {
279
+ id: 'dingtalk_login',
280
+ width: 260,
281
+ height: 260, // 正方形二维码更美观
282
+ },
283
+ {
284
+ redirect_uri: encodeURIComponent(fullUrl),
285
+ client_id: this.ddClientId,
286
+ scope: 'openid',
287
+ response_type: 'code',
288
+ state: '1',
289
+ prompt: 'consent',
290
+ },
291
+ (loginResult) => {
292
+ console.log('钉钉登录成功,结果:', loginResult);
293
+ const {redirectUrl, authCode, state } = loginResult;
294
+ if (authCode) {
295
+ this.handleDingTalkLoginSuccess(redirectUrl, authCode, state);
296
+ } else {
297
+ console.error('钉钉登录成功但未返回authCode');
298
+ this.$Message.error('钉钉登录失败:未获取到授权码');
299
+ }
300
+ },
301
+ (errors) => {
302
+ console.error('钉钉登录失败:', errors);
303
+ this.$Message.error(`钉钉登录失败: ${errors}`);
304
+ }
305
+ );
306
+
307
+ console.log('✅ 钉钉登录组件初始化成功,实例:', this.dingTalkLoginInstance);
308
+ } catch (error) {
309
+ console.error('❌ 初始化钉钉登录组件失败:', error);
310
+ console.error('错误详情:', error.stack);
311
+ this.$Message.error('初始化钉钉登录组件失败,请刷新页面重试');
312
+ }
313
+ },
314
+ handleDingTalkLoginSuccess(redirectUrl, authCode, state) {
315
+ const urlParams = new URLSearchParams(window.location.search);
316
+ const redirect_uri = urlParams.get('redirect_uri');
317
+ const finalRedirectUrl = redirect_uri ? decodeURIComponent(redirect_uri) : (window.location.origin + '/ibp/homeIndex');
318
+
319
+ ajax.get(config.authServerContext + '/sso/dd/ddLogin?code=' + authCode + '&redirect_uri=' + encodeURIComponent(finalRedirectUrl)).then((resp) => {
320
+ if (resp.data.code === 1) {
321
+ let token = resp.data.data;
322
+ if (!token) {
323
+ this.$Message.error('登录失败,token为空');
324
+ return;
325
+ }
326
+ this.$emit('login-success', {token});
327
+ } else if (resp.data.code === 119) {
328
+ this.$emit('need-bind', {bindToken: resp.data.data, bindType: 'dd'});
329
+ } else {
330
+ this.$Message.error(resp.data.msg);
331
+ }
332
+ }).catch(e => {
333
+ console.error(e);
334
+ this.$Message.error('登录失败');
335
+ });
336
+ },
337
+
338
+
339
+ initLoginInstance() {
340
+ // 先销毁所有实例
341
+ this.destroyAllInstances();
342
+
343
+ // 根据当前登录类型初始化
344
+ if (this.currentLoginType === 'wechat') {
345
+ this.loadWxWorkSdk().then(() => {
346
+ this.initWwLogin();
347
+ });
348
+ } else if (this.currentLoginType === 'dingtalk') {
349
+ // 使用新的Promise方法加载钉钉SDK
350
+ this.loadDingTalkSdk().then(() => {
351
+ this.initDingTalkLogin();
352
+ });
353
+ }
354
+ },
355
+
356
+ // 销毁所有实例
357
+ destroyAllInstances() {
358
+ this.destroyWwLogin();
359
+ this.destroyDingTalkLogin();
360
+ },
361
+
362
+ // 切换登录类型
363
+ switchLoginType(type) {
364
+ if (this.currentLoginType !== type) {
365
+ this.currentLoginType = type;
366
+ this.initLoginInstance();
367
+ }
368
+ },
369
+
370
+ // handleBind({ userName, password, validCodeInput, validCodeId, bindToken }) {
371
+ // const self = this;
372
+ // console.log("跳转绑定")
373
+ // return new Promise((resolve, reject) => {
374
+ // ajax.post(config.authServerContext+'/sso/wxcp/bindAndLogin', {
375
+ // bindToken:bindToken,
376
+ // mmrm: userName,
377
+ // mcmm: password,
378
+ // validCodeId: validCodeId,
379
+ // validCodeInput: validCodeInput
380
+ // }).then(response => {
381
+ // if (response.data.code === 1) {
382
+ // self.$Message.success('绑定成功');
383
+ // window.location.href = response.data.data;
384
+ // } else {
385
+ // self.$Message.error(response.data.message || '绑定失败');
386
+ // reject(new Error(response.data.message || '绑定失败'));
387
+ // }
388
+ // }).catch(e => {
389
+ // console.error(e);
390
+ // self.$Message.error('绑定失败');
391
+ // reject(e);
392
+ // });
393
+ // });
394
+ // },
395
+ // initQrCode(){
396
+ // // 初始化登录组件
397
+ // const fullUrl = "http://37fe3689.r9.cpolar.cn/ibp-auth-server/sso/wxcp/getCode";
398
+ // const wwLogin = ww.createWWLoginPanel({
399
+ // el: '#ww_login',
400
+ // params: {
401
+ // login_type: 'CorpApp',
402
+ // appid: 'ww107f970a51840bcc',
403
+ // agentid: '1000006',
404
+ // redirect_uri: fullUrl,
405
+ // panel_size: 'small',
406
+ // state: 'login_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
407
+ // redirect_type: 'callback',
408
+ // },
409
+ // onCheckWeComLogin({ isWeComLogin }) {
410
+ // console.log(isWeComLogin)
411
+ // },
412
+ // onLoginSuccess({ code }) {
413
+ // console.log({ code })
414
+ // // 发送登录成功事件,传递code给父组件
415
+ // this.$emit('login-success', { code })
416
+ // },
417
+ // onLoginFail(err) {
418
+ // console.log(err)
419
+ // // 发送登录失败事件
420
+ // this.$emit('login-fail', err)
421
+ // },
422
+ // })
423
+ // },
424
+ }
425
+ }
426
+ </script>
427
+ <style scoped>
428
+ .lambo-login-qrcode {
429
+ display: flex;
430
+ flex-direction: column;
431
+ align-items: center;
432
+ padding: 0;
433
+ text-align: center;
434
+ width: 100%;
435
+ overflow: hidden;
436
+ }
437
+
438
+ .qrcode-loading {
439
+ display: flex;
440
+ flex-direction: column;
441
+ align-items: center;
442
+ justify-content: center;
443
+ height: 300px;
444
+ color: #00000073;
445
+ }
446
+
447
+ .qrcode-img-wrapper {
448
+ height: 260px !important;
449
+ min-height: 260px !important;
450
+ display: flex;
451
+ justify-content: center;
452
+ align-items: center;
453
+ overflow: hidden;
454
+ width: 100%;
455
+ position: relative;
456
+ }
457
+
458
+ .qrcode-item {
459
+ display: flex;
460
+ flex-direction: column;
461
+ align-items: center;
462
+ margin: 0 10px;
463
+ }
464
+
465
+ /* 登录类型切换样式 */
466
+ .login-type-switch {
467
+ display: flex;
468
+ justify-content: center;
469
+ margin-bottom: 15px;
470
+ border-radius: 4px;
471
+ background-color: transparent; /* 去掉灰色背景 */
472
+ padding: 0; /* 移除内边距 */
473
+ width: fit-content;
474
+ margin: 0px auto 15px; /* 向上平移10px */
475
+ }
476
+
477
+ .switch-item {
478
+ width: 40px;
479
+ height: 40px;
480
+ object-fit: contain;
481
+ cursor: pointer;
482
+ transition: all 0.3s;
483
+ user-select: none;
484
+ margin: 0 10px;
485
+ border: 2px solid transparent;
486
+ }
487
+
488
+ .switch-item.active {
489
+ border-color: #1890ff;
490
+ box-shadow: none;
491
+ }
492
+
493
+
494
+ /* 企业微信登录组件容器 */
495
+ .wx-saas {
496
+ #ww_login {
497
+ width: 100% !important;
498
+ max-width: 220px !important;
499
+ margin: 0 auto !important;
500
+ display: block !important;
501
+ }
502
+ .lambo-login-qrcode ::v-deep #ww_login iframe,
503
+ .qrcode-img-wrapper ::v-deep iframe {
504
+ position: relative;
505
+ top: -60px !important;
506
+ height: 360px !important;
507
+ border: none !important;
508
+ width: 100% !important;
509
+ overflow: hidden !important;
510
+ }
511
+ }
512
+ .wx-private {
513
+ #ww_login {
514
+ width: 100% !important;
515
+ margin: 0 auto !important;
516
+ display: block !important;
517
+ }
518
+ .lambo-login-qrcode ::v-deep #ww_login iframe,
519
+ .qrcode-img-wrapper ::v-deep iframe {
520
+ position: relative;
521
+ top: 25px !important;
522
+ height: 400px !important;
523
+ border: none !important;
524
+ width: 100% !important;
525
+ overflow: hidden !important;
526
+ }
527
+
528
+ .qrcode-img-wrapper {
529
+ height: 400px !important;
530
+ min-height: 260px !important;
531
+ display: flex;
532
+ justify-content: center;
533
+ align-items: center;
534
+ overflow: hidden;
535
+ width: 100%;
536
+ position: relative;
537
+ }
538
+ }
539
+ /* 钉钉登录组件容器 */
540
+ #dingtalk_login {
541
+ width: 100% !important;
542
+ max-width: 260px !important;
543
+ height: 260px !important;
544
+ margin: 0 auto !important;
545
+ display: block !important;
546
+ overflow: hidden;
547
+ }
548
+
549
+ /* 钉钉iframe样式 - 减少上方留白 */
550
+ .lambo-login-qrcode ::v-deep #dingtalk_login iframe,
551
+ .qrcode-img-wrapper ::v-deep iframe {
552
+ position: relative;
553
+ top: -60px !important;
554
+ height: 360px !important;
555
+ border: none !important;
556
+ width: 100% !important;
557
+ overflow: hidden !important;
558
+ }
559
+ </style>
@@ -1,3 +1,10 @@
1
+ .login-method-label {
2
+ font-family: Source Han Sans CN;
3
+ color: #333333;
4
+ opacity: 0.4;
5
+ display: block;
6
+ margin-bottom: 8px;
7
+ }
1
8
  .lambo-login-form {
2
9
  .ivu-input-group-prepend {
3
10
  .prefix {
@@ -5,15 +12,23 @@
5
12
  height: 13px;
6
13
  }
7
14
  }
15
+ .captcha-container {
16
+ .ivu-form-item-content {
17
+ display: flex;
18
+ justify-content: space-between;
19
+ width: 100%;
20
+ align-items: center;
21
+ }
22
+ }
8
23
  .captcha {
9
24
  width: calc(100% - 100px);
10
25
  }
11
26
  .Imgbox {
12
27
  width: 90px;
13
- height: 33px;
14
- float: right;
15
- padding: 2px;
16
- margin-top: -33px;
28
+ height: var(--input-height-base, 33px);
29
+ //float: right;
30
+ padding: 1px;
31
+ //margin-top: -33px;
17
32
  background-color: #f8f8f9;
18
33
  .captchaImg {
19
34
  width: 100%;
Binary file
Binary file