add 新增 密码登录支持OTP验证
This commit is contained in:
parent
3c2d9100b5
commit
b28a3d0b12
@ -39,6 +39,7 @@
|
|||||||
"jsencrypt": "3.3.2",
|
"jsencrypt": "3.3.2",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"pinia": "2.1.7",
|
"pinia": "2.1.7",
|
||||||
|
"qrcode.vue": "3.4.1",
|
||||||
"screenfull": "6.0.2",
|
"screenfull": "6.0.2",
|
||||||
"vue": "3.4.34",
|
"vue": "3.4.34",
|
||||||
"vue-cropper": "1.1.1",
|
"vue-cropper": "1.1.1",
|
||||||
|
@ -72,6 +72,21 @@ export const delUser = (userId: Array<string | number> | string | number) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户OTP秘钥重置
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
export const resetUserOptSecret = (userId: string | number) => {
|
||||||
|
return request({
|
||||||
|
url: '/system/user/resetOtpSecret/' + userId,
|
||||||
|
method: 'put',
|
||||||
|
headers: {
|
||||||
|
isEncrypt: true,
|
||||||
|
repeatSubmit: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户密码重置
|
* 用户密码重置
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
@ -217,6 +232,7 @@ export default {
|
|||||||
updateUser,
|
updateUser,
|
||||||
delUser,
|
delUser,
|
||||||
resetUserPwd,
|
resetUserPwd,
|
||||||
|
resetUserOptSecret,
|
||||||
changeUserStatus,
|
changeUserStatus,
|
||||||
getUserProfile,
|
getUserProfile,
|
||||||
updateUserProfile,
|
updateUserProfile,
|
||||||
|
@ -39,6 +39,8 @@ export interface UserVO extends BaseEntity {
|
|||||||
delFlag: string;
|
delFlag: string;
|
||||||
loginIp: string;
|
loginIp: string;
|
||||||
loginDate: string;
|
loginDate: string;
|
||||||
|
otpSecret: string;
|
||||||
|
otpUrl: string;
|
||||||
remark: string;
|
remark: string;
|
||||||
deptName: string;
|
deptName: string;
|
||||||
roles: RoleVO[];
|
roles: RoleVO[];
|
||||||
|
@ -18,6 +18,7 @@ export interface LoginData {
|
|||||||
tenantId?: string;
|
tenantId?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
|
otpCode?: number;
|
||||||
rememberMe?: boolean;
|
rememberMe?: boolean;
|
||||||
socialCode?: string;
|
socialCode?: string;
|
||||||
socialState?: string;
|
socialState?: string;
|
||||||
|
@ -18,6 +18,11 @@
|
|||||||
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item prop="otpCode">
|
||||||
|
<el-input v-model="loginForm.otpCode" type="number" size="large" auto-complete="off" placeholder="OTP验证码" @keyup.enter="handleLogin">
|
||||||
|
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item v-if="captchaEnabled" prop="code">
|
<el-form-item v-if="captchaEnabled" prop="code">
|
||||||
<el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
|
<el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
|
||||||
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
||||||
@ -76,6 +81,7 @@ const loginForm = ref<LoginData>({
|
|||||||
tenantId: '000000',
|
tenantId: '000000',
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: 'admin123',
|
password: 'admin123',
|
||||||
|
otpCode: NaN,
|
||||||
rememberMe: false,
|
rememberMe: false,
|
||||||
code: '',
|
code: '',
|
||||||
uuid: ''
|
uuid: ''
|
||||||
@ -85,6 +91,7 @@ const loginRules: ElFormRules = {
|
|||||||
tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
|
tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
|
||||||
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
|
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
|
||||||
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
|
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
|
||||||
|
otpCode: [{ required: true, trigger: 'blur', message: '请输入您的OTP验证码' }],
|
||||||
code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
|
code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,13 +91,14 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
|
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange" @cell-click="cellClickFunc">
|
||||||
<el-table-column type="selection" width="50" align="center" />
|
<el-table-column type="selection" width="50" align="center" />
|
||||||
<el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
|
<el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
|
||||||
<el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
|
<el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
|
||||||
<el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
|
<el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
|
||||||
<el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
|
<el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
|
||||||
<el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
|
<el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
|
||||||
|
<el-table-column v-if="columns[7].visible" key="otpSecret" label="OTP秘钥" align="center" prop="otpSecret" width="120" />
|
||||||
<el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
|
<el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
|
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
|
||||||
@ -123,6 +124,16 @@
|
|||||||
<el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button>
|
<el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="重置OTP秘钥" placement="top">
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:user:resetPwd']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
icon="Lock"
|
||||||
|
@click="handleResetOtpSecret(scope.row)"
|
||||||
|
></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top">
|
<el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top">
|
||||||
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button>
|
<el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@ -281,6 +292,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="optSecretVisible" title="OTP秘钥绑定" width="500" append-to-body>
|
||||||
|
<div style="display: flex; justify-content: center">
|
||||||
|
<qrcode-vue :value="otpUrl" :size="150" level="H" />
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -294,6 +311,7 @@ import { treeselect } from '@/api/system/dept';
|
|||||||
import { globalHeaders } from '@/utils/request';
|
import { globalHeaders } from '@/utils/request';
|
||||||
import { to } from 'await-to-js';
|
import { to } from 'await-to-js';
|
||||||
import { optionselect } from '@/api/system/post';
|
import { optionselect } from '@/api/system/post';
|
||||||
|
import QrcodeVue from 'qrcode.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
@ -334,7 +352,8 @@ const columns = ref<FieldOption[]>([
|
|||||||
{ key: 3, label: `部门`, visible: true, children: [] },
|
{ key: 3, label: `部门`, visible: true, children: [] },
|
||||||
{ key: 4, label: `手机号码`, visible: true, children: [] },
|
{ key: 4, label: `手机号码`, visible: true, children: [] },
|
||||||
{ key: 5, label: `状态`, visible: true, children: [] },
|
{ key: 5, label: `状态`, visible: true, children: [] },
|
||||||
{ key: 6, label: `创建时间`, visible: true, children: [] }
|
{ key: 6, label: `创建时间`, visible: true, children: [] },
|
||||||
|
{ key: 7, label: `OTP秘钥`, visible: true, children: [] }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const deptTreeRef = ref<ElTreeInstance>();
|
const deptTreeRef = ref<ElTreeInstance>();
|
||||||
@ -431,6 +450,18 @@ watchEffect(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const optSecretVisible = ref(false);
|
||||||
|
const otpUrl = ref(undefined);
|
||||||
|
|
||||||
|
const cellClickFunc = (row, column) => {
|
||||||
|
if (column?.rawColumnKey === 'otpSecret') {
|
||||||
|
otpUrl.value = row.otpUrl;
|
||||||
|
if (otpUrl.value) {
|
||||||
|
optSecretVisible.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** 查询部门下拉树结构 */
|
/** 查询部门下拉树结构 */
|
||||||
const getTreeSelect = async () => {
|
const getTreeSelect = async () => {
|
||||||
const res = await api.deptTreeSelect();
|
const res = await api.deptTreeSelect();
|
||||||
@ -495,6 +526,23 @@ const handleAuthRole = (row: UserVO) => {
|
|||||||
router.push('/system/user-auth/role/' + userId);
|
router.push('/system/user-auth/role/' + userId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 重置谷歌密钥按钮操作 */
|
||||||
|
const handleResetOtpSecret = async (row: UserVO) => {
|
||||||
|
const [err, res] = await to(
|
||||||
|
ElMessageBox.confirm('请确认是否重置OTP密钥?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
closeOnClickModal: false
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (!err && res) {
|
||||||
|
await api.resetUserOptSecret(row.userId);
|
||||||
|
proxy?.$modal.msgSuccess('密钥重置成功');
|
||||||
|
await getList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** 重置密码按钮操作 */
|
/** 重置密码按钮操作 */
|
||||||
const handleResetPwd = async (row: UserVO) => {
|
const handleResetPwd = async (row: UserVO) => {
|
||||||
const [err, res] = await to(
|
const [err, res] = await to(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user