|
|
@@ -135,7 +135,51 @@
|
|
|
<p class="form-desc">管理员 / 规划师登录</p>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- 登录方式切换:密码登录 / 微信扫码 -->
|
|
|
+ <div class="login-mode-tabs">
|
|
|
+ <span
|
|
|
+ class="login-mode-tab"
|
|
|
+ :class="{ active: loginMode === 'password' }"
|
|
|
+ @click="switchMode('password')"
|
|
|
+ >密码登录</span>
|
|
|
+ <span
|
|
|
+ class="login-mode-tab"
|
|
|
+ :class="{ active: loginMode === 'wechat' }"
|
|
|
+ @click="switchMode('wechat')"
|
|
|
+ >微信扫码登录</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 微信扫码登录面板 -->
|
|
|
+ <div class="wechat-panel" v-if="loginMode === 'wechat'">
|
|
|
+ <div class="wechat-qr-box">
|
|
|
+ <img
|
|
|
+ v-if="wxQrImage"
|
|
|
+ :src="wxQrImage"
|
|
|
+ class="wechat-qr-img"
|
|
|
+ alt="微信扫码登录二维码"
|
|
|
+ />
|
|
|
+ <div v-else class="wechat-qr-placeholder">
|
|
|
+ <i class="el-icon-loading" v-if="wxQrLoading"></i>
|
|
|
+ <span v-else>测试模式:请配置真实小程序码</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="wechat-tip">
|
|
|
+ <span v-if="wxStatus === 'success'">登录成功,正在跳转...</span>
|
|
|
+ <span v-else-if="wxStatus === 'expired'">二维码已过期,请重新获取</span>
|
|
|
+ <span v-else-if="wxStatus === 'error'">扫码失败,请重新获取</span>
|
|
|
+ <span v-else>请使用微信扫描上方二维码登录</span>
|
|
|
+ </div>
|
|
|
+ <div class="wechat-actions">
|
|
|
+ <el-button
|
|
|
+ v-if="wxStatus === 'expired' || wxStatus === 'error'"
|
|
|
+ type="text"
|
|
|
+ @click="startWxScan"
|
|
|
+ >重新获取二维码</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<el-form
|
|
|
+ v-if="loginMode === 'password'"
|
|
|
:model="loginForm"
|
|
|
:rules="rules"
|
|
|
ref="loginForm"
|
|
|
@@ -216,7 +260,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { adminLogin, getAccountRoles } from '@/api/auth'
|
|
|
+import { adminLogin, getAccountRoles, adminWxScanQr, adminWxScanPoll } from '@/api/auth'
|
|
|
import { getRoleHomePage } from '@/utils/permissions'
|
|
|
import { orgMy } from '@/api/org'
|
|
|
import SliderCaptcha from '@/components/SliderCaptcha'
|
|
|
@@ -246,7 +290,14 @@ export default {
|
|
|
identityOptions: [],
|
|
|
identityLoading: false,
|
|
|
lastFetchedPhone: '',
|
|
|
- phoneInputTimer: null
|
|
|
+ phoneInputTimer: null,
|
|
|
+ // 微信扫码登录
|
|
|
+ loginMode: 'password',
|
|
|
+ wxState: '',
|
|
|
+ wxQrImage: '',
|
|
|
+ wxQrLoading: false,
|
|
|
+ wxStatus: '',
|
|
|
+ wxTimer: null
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
@@ -262,6 +313,9 @@ export default {
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
+ beforeDestroy() {
|
|
|
+ this.stopWxPolling()
|
|
|
+ },
|
|
|
methods: {
|
|
|
onCaptchaSuccess() {
|
|
|
this.captchaPassed = true
|
|
|
@@ -330,49 +384,7 @@ export default {
|
|
|
password: this.loginForm.password,
|
|
|
loginRole: this.loginForm.loginRole || undefined
|
|
|
})
|
|
|
- localStorage.setItem('token', res.data.token)
|
|
|
- localStorage.setItem('adminId', res.data.adminId)
|
|
|
- localStorage.setItem('adminName', res.data.realName)
|
|
|
- const role = res.data.role || 'admin'
|
|
|
- localStorage.setItem('role', role)
|
|
|
- const roles = res.data.roles || [role]
|
|
|
- localStorage.setItem('roles', JSON.stringify(roles))
|
|
|
- if (this.$message) {
|
|
|
- this.$message.success('登录成功')
|
|
|
- } else {
|
|
|
- console.log('登录成功')
|
|
|
- }
|
|
|
- await this.$nextTick()
|
|
|
- // A-1 组织管理员(org_admin 且不含平台角色)登录后直跳自身组织的大屏
|
|
|
- const rolesArr = res.data.roles || [role]
|
|
|
- if (rolesArr.indexOf('org_admin') >= 0 && rolesArr.indexOf('admin') < 0
|
|
|
- && rolesArr.indexOf('operator') < 0 && rolesArr.indexOf('finance') < 0) {
|
|
|
- try {
|
|
|
- const orgRes = await orgMy()
|
|
|
- if (orgRes && orgRes.data && orgRes.data.id) {
|
|
|
- this.$router.replace({ path: '/org/' + orgRes.data.id + '/dashboard' })
|
|
|
- } else if (orgRes && this.$message) {
|
|
|
- this.$message.error(orgRes.message || '组织已停用或未关联,无法进入管理端')
|
|
|
- localStorage.removeItem('token')
|
|
|
- localStorage.removeItem('roles')
|
|
|
- localStorage.removeItem('role')
|
|
|
- this.resetCaptcha()
|
|
|
- }
|
|
|
- } catch (eOrg) {
|
|
|
- // 组织已停用/未关联:my-org 返回错误 → 清理登录态回到登录页(错误提示由 request 拦截器弹出)
|
|
|
- localStorage.removeItem('token')
|
|
|
- localStorage.removeItem('roles')
|
|
|
- localStorage.removeItem('role')
|
|
|
- if (this.$message) {
|
|
|
- this.$message.error((eOrg && eOrg.message) ? eOrg.message : '组织已停用,无法进入管理端')
|
|
|
- }
|
|
|
- this.resetCaptcha()
|
|
|
- }
|
|
|
- return
|
|
|
- }
|
|
|
- // B-6 按所选登录身份跳转:入驻角色进个人工作台,平台角色进首页
|
|
|
- const redirectRole = getRoleHomePage(rolesArr)
|
|
|
- this.$router.replace({ path: redirectRole })
|
|
|
+ this.afterLoginSuccess(res)
|
|
|
} catch (e) {
|
|
|
const msg = e && e.message ? e.message : '登录失败,请检查账号密码'
|
|
|
if (this.$message) {
|
|
|
@@ -385,6 +397,133 @@ export default {
|
|
|
this.loading = false
|
|
|
}
|
|
|
})
|
|
|
+ },
|
|
|
+ switchMode(mode) {
|
|
|
+ if (this.loginMode === mode) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (mode === 'wechat') {
|
|
|
+ this.loginMode = 'wechat'
|
|
|
+ this.startWxScan()
|
|
|
+ } else {
|
|
|
+ this.stopWxPolling()
|
|
|
+ this.loginMode = 'password'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 获取小程序码并开始轮询
|
|
|
+ async startWxScan() {
|
|
|
+ this.stopWxPolling()
|
|
|
+ this.wxState = ''
|
|
|
+ this.wxQrImage = ''
|
|
|
+ this.wxStatus = ''
|
|
|
+ this.wxQrLoading = true
|
|
|
+ try {
|
|
|
+ const res = await adminWxScanQr()
|
|
|
+ this.wxQrLoading = false
|
|
|
+ if (!res || !res.data || !res.data.state) {
|
|
|
+ this.wxStatus = 'error'
|
|
|
+ if (this.$message) this.$message.error('获取二维码失败,请重试')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.wxState = res.data.state
|
|
|
+ this.wxQrImage = res.data.qrImage || ''
|
|
|
+ this.wxStatus = 'pending'
|
|
|
+ // 每 3s 轮询扫码结果
|
|
|
+ this.wxTimer = setInterval(() => {
|
|
|
+ this.doWxPoll()
|
|
|
+ }, 3000)
|
|
|
+ } catch (e) {
|
|
|
+ this.wxQrLoading = false
|
|
|
+ this.wxStatus = 'error'
|
|
|
+ const msg = e && e.message ? e.message : '获取二维码失败'
|
|
|
+ if (this.$message) this.$message.error(msg)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async doWxPoll() {
|
|
|
+ if (!this.wxState) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const res = await adminWxScanPoll(this.wxState)
|
|
|
+ const status = res && res.data ? res.data.status : 'expired'
|
|
|
+ if (status === 'success') {
|
|
|
+ this.stopWxPolling()
|
|
|
+ this.wxStatus = 'success'
|
|
|
+ this.afterLoginSuccess(res)
|
|
|
+ } else if (status === 'pending') {
|
|
|
+ this.wxStatus = 'pending'
|
|
|
+ } else if (status === 'expired') {
|
|
|
+ this.stopWxPolling()
|
|
|
+ this.wxStatus = 'expired'
|
|
|
+ } else {
|
|
|
+ // error
|
|
|
+ this.stopWxPolling()
|
|
|
+ this.wxStatus = 'error'
|
|
|
+ const msg = res && res.data && res.data.message ? res.data.message : '扫码登录失败'
|
|
|
+ if (this.$message) this.$message.error(msg)
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.stopWxPolling()
|
|
|
+ this.wxStatus = 'error'
|
|
|
+ const msg = e && e.message ? e.message : '扫码登录失败'
|
|
|
+ if (this.$message) this.$message.error(msg)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ stopWxPolling() {
|
|
|
+ if (this.wxTimer) {
|
|
|
+ clearInterval(this.wxTimer)
|
|
|
+ this.wxTimer = null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 登录成功后的公共跳转逻辑(密码登录与微信扫码登录共用)
|
|
|
+ async afterLoginSuccess(res) {
|
|
|
+ if (!res || !res.data || !res.data.token) {
|
|
|
+ if (this.$message) this.$message.error('登录失败,请重试')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ localStorage.setItem('token', res.data.token)
|
|
|
+ localStorage.setItem('adminId', res.data.adminId)
|
|
|
+ localStorage.setItem('adminName', res.data.realName)
|
|
|
+ const role = res.data.role || 'admin'
|
|
|
+ localStorage.setItem('role', role)
|
|
|
+ const roles = res.data.roles || [role]
|
|
|
+ localStorage.setItem('roles', JSON.stringify(roles))
|
|
|
+ if (this.$message) {
|
|
|
+ this.$message.success('登录成功')
|
|
|
+ } else {
|
|
|
+ console.log('登录成功')
|
|
|
+ }
|
|
|
+ await this.$nextTick()
|
|
|
+ // A-1 组织管理员(org_admin 且不含平台角色)登录后直跳自身组织的大屏
|
|
|
+ const rolesArr = res.data.roles || [role]
|
|
|
+ if (rolesArr.indexOf('org_admin') >= 0 && rolesArr.indexOf('admin') < 0
|
|
|
+ && rolesArr.indexOf('operator') < 0 && rolesArr.indexOf('finance') < 0) {
|
|
|
+ try {
|
|
|
+ const orgRes = await orgMy()
|
|
|
+ if (orgRes && orgRes.data && orgRes.data.id) {
|
|
|
+ this.$router.replace({ path: '/org/' + orgRes.data.id + '/dashboard' })
|
|
|
+ } else if (orgRes && this.$message) {
|
|
|
+ this.$message.error(orgRes.message || '组织已停用或未关联,无法进入管理端')
|
|
|
+ localStorage.removeItem('token')
|
|
|
+ localStorage.removeItem('roles')
|
|
|
+ localStorage.removeItem('role')
|
|
|
+ this.resetCaptcha()
|
|
|
+ }
|
|
|
+ } catch (eOrg) {
|
|
|
+ // 组织已停用/未关联:my-org 返回错误 → 清理登录态回到登录页(错误提示由 request 拦截器弹出)
|
|
|
+ localStorage.removeItem('token')
|
|
|
+ localStorage.removeItem('roles')
|
|
|
+ localStorage.removeItem('role')
|
|
|
+ if (this.$message) {
|
|
|
+ this.$message.error((eOrg && eOrg.message) ? eOrg.message : '组织已停用,无法进入管理端')
|
|
|
+ }
|
|
|
+ this.resetCaptcha()
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // B-6 按所选登录身份跳转:入驻角色进个人工作台,平台角色进首页
|
|
|
+ const redirectRole = getRoleHomePage(rolesArr)
|
|
|
+ this.$router.replace({ path: redirectRole })
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -951,4 +1090,84 @@ export default {
|
|
|
.role-tag-blue { border-color:var(--adm-primary); color:#4096FF; }
|
|
|
.role-tag-pink { border-color:#FF6B9D; color:#FF8CB3; }
|
|
|
.role-tag-green { border-color:#10B981; color:#34D399; }
|
|
|
+
|
|
|
+/* ==================== 登录方式切换 ==================== */
|
|
|
+.login-mode-tabs {
|
|
|
+ display: flex;
|
|
|
+ border-bottom: 1px solid #E2E8F0;
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.login-mode-tab {
|
|
|
+ flex: 1;
|
|
|
+ text-align: center;
|
|
|
+ padding: 10px 0;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #94A3B8;
|
|
|
+ cursor: pointer;
|
|
|
+ border-bottom: 2px solid transparent;
|
|
|
+ transition: all 0.25s;
|
|
|
+ font-family: inherit;
|
|
|
+ letter-spacing: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.login-mode-tab:hover {
|
|
|
+ color: #4A9BD7;
|
|
|
+}
|
|
|
+
|
|
|
+.login-mode-tab.active {
|
|
|
+ color: #3A7CC4;
|
|
|
+ border-bottom-color: #4A9BD7;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+/* ==================== 微信扫码登录面板 ==================== */
|
|
|
+.wechat-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 12px 0 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.wechat-qr-box {
|
|
|
+ width: 200px;
|
|
|
+ height: 200px;
|
|
|
+ border: 1px solid #E2E8F0;
|
|
|
+ border-radius: 12px;
|
|
|
+ background: #F8FAFC;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.wechat-qr-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.wechat-qr-placeholder {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #94A3B8;
|
|
|
+ text-align: center;
|
|
|
+ padding: 0 16px;
|
|
|
+ line-height: 1.8;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.wechat-tip {
|
|
|
+ margin-top: 16px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #64748B;
|
|
|
+ letter-spacing: 1px;
|
|
|
+}
|
|
|
+
|
|
|
+.wechat-actions {
|
|
|
+ margin-top: 8px;
|
|
|
+ min-height: 28px;
|
|
|
+}
|
|
|
</style>
|