feat: 添加登录,注册,忘记密码功能

This commit is contained in:
2025-02-11 03:02:41 +08:00
parent a5f04356ee
commit e5fe678eb6
83 changed files with 3007 additions and 979 deletions

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import { deviceDetection, hideTextAtIndex } from "@pureadmin/utils";
import { useUserInfo } from "../utils/hooks";
defineOptions({
name: "AccountSafe"
});
const { handleReset, handlePhone, handleEmail, userInfo } = useUserInfo();
</script>
<template>
<div
:class="[
'min-w-[180px]',
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
]"
>
<h3 class="my-8">账户安全</h3>
<div class="flex items-center">
<div class="flex-1">
<p>账号密码</p>
<el-text class="mx-1" type="info" />
</div>
<el-button type="primary" text @click="handleReset(userInfo)"
>修改</el-button
>
</div>
<el-divider />
<div class="flex items-center">
<div class="flex-1">
<p>绑定号码</p>
<el-text class="mx-1" type="info">
已经绑定手机{{
hideTextAtIndex(userInfo.phone, { start: 3, end: 6 })
}}
</el-text>
</div>
<el-button type="primary" text @click="handlePhone(userInfo)"
>修改</el-button
>
</div>
<el-divider />
<div class="flex items-center">
<div class="flex-1">
<p>绑定邮箱</p>
<el-text class="mx-1" type="info"
>已绑定邮箱{{
hideTextAtIndex(userInfo.email, {
start: 3,
end: userInfo.email.indexOf("@") - 1
})
}}
</el-text>
</div>
<el-button type="primary" text @click="handleEmail(userInfo)"
>修改</el-button
>
</div>
<el-divider />
</div>
</template>
<style lang="scss" scoped>
.el-divider--horizontal {
border-top: 0.1px var(--el-border-color) var(--el-border-style);
}
</style>

View File

@@ -0,0 +1,163 @@
<script setup lang="ts">
import { ref } from "vue";
import { message } from "@/utils/message";
import type { FormInstance } from "element-plus";
import ReCropperPreview from "@/components/ReCropperPreview";
import { deviceDetection } from "@pureadmin/utils";
import uploadLine from "@iconify-icons/ri/upload-line";
import { postUploadAvatarAPI, putUpdateBaseUserInfoAPI } from "@/api/user";
import { useUserStoreHook } from "@/store/modules/user";
import { userInfoKey } from "@/utils/auth";
import { storageLocal } from "@pureadmin/utils";
import { useUserInfo } from "../utils/hooks";
defineOptions({
name: "Profile"
});
const { userInfo, getUserInfo } = useUserInfo();
const imgSrc = ref("");
const cropperBlob = ref();
const cropRef = ref();
const uploadRef = ref();
const isShow = ref(false);
const userInfoFormRef = ref<FormInstance>();
const disabledDate = (time: Date) => {
return time.getTime() > Date.now();
};
const onChange = uploadFile => {
const reader = new FileReader();
reader.onload = e => {
imgSrc.value = e.target.result as string;
isShow.value = true;
};
reader.readAsDataURL(uploadFile.raw);
};
const handleClose = () => {
cropRef.value.hidePopover();
uploadRef.value.clearFiles();
isShow.value = false;
};
const onCropper = ({ blob }) => (cropperBlob.value = blob);
const handleSubmitImage = async () => {
const res = await postUploadAvatarAPI(userInfo.id, {
file: cropperBlob.value
});
if (res.code === 200) {
message("更新头像成功", { type: "success" });
userInfo.avatar = `/file/${res.data.id}`;
const user = storageLocal().getItem<object>(userInfoKey);
storageLocal().setItem(userInfoKey, {
...user,
avatar: `/file/${res.data.id}`
});
useUserStoreHook().SET_AVATAR(`/file/${res.data.id}`);
handleClose();
} else {
message("更新头像失败", { type: "error" });
}
};
// 更新信息
const onSubmit = async (formEl: FormInstance) => {
await formEl.validate(async (valid, fields) => {
if (valid) {
let updateForm = {
name: userInfo.nickname,
gender: userInfo.gender
};
const res = await putUpdateBaseUserInfoAPI(updateForm);
if (res.code === 200) {
message(res.msg, { type: "success" });
await getUserInfo();
} else {
message(res.msg, { type: "error" });
}
} else {
console.log("error submit!", fields);
}
});
};
</script>
<template>
<div
:class="[
'min-w-[180px]',
deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
]"
>
<h3 class="my-8">个人信息</h3>
<el-form ref="userInfoFormRef" label-position="top" :model="userInfo">
<el-form-item label="头像">
<el-avatar :size="80" :src="userInfo.avatar" />
<el-upload
ref="uploadRef"
accept="image/*"
action="#"
:limit="1"
:auto-upload="false"
:show-file-list="false"
:on-change="onChange"
>
<el-button plain class="ml-4">
<IconifyIconOffline :icon="uploadLine" />
<span class="ml-2">更新头像</span>
</el-button>
</el-upload>
</el-form-item>
<el-form-item label="账号" prop="userId">
<el-input
v-model="userInfo.username"
placeholder="请输入账号~"
disabled
/>
</el-form-item>
<el-form-item
label="姓名"
prop="name"
:rules="[
{
required: true,
message: '请输入用户姓名~',
trigger: 'blur'
}
]"
>
<el-input v-model="userInfo.nickname" placeholder="请输入姓名~" />
</el-form-item>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="userInfo.gender" class="ml-4">
<el-radio :value="1" size="default"></el-radio>
<el-radio :value="0" size="default"></el-radio>
</el-radio-group>
</el-form-item>
<el-button type="primary" @click="onSubmit(userInfoFormRef)">
更新信息
</el-button>
</el-form>
<el-dialog
v-model="isShow"
width="40%"
title="编辑头像"
destroy-on-close
:closeOnClickModal="false"
:before-close="handleClose"
:fullscreen="deviceDetection()"
>
<ReCropperPreview ref="cropRef" :imgSrc="imgSrc" @cropper="onCropper" />
<template #footer>
<div class="dialog-footer">
<el-button bg text @click="handleClose">取消</el-button>
<el-button bg text type="primary" @click="handleSubmitImage">
确定
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@@ -0,0 +1,176 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import { ref, onBeforeMount, computed } from "vue";
import { ReText } from "@/components/ReText";
import Avatar from "@/assets/user.png";
import Profile from "./components/Profile.vue";
import AccountSafe from "./components/AccountSafe.vue";
import { useGlobal, deviceDetection } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import LaySidebarTopCollapse from "@/layout/components/lay-sidebar/components/SidebarTopCollapse.vue";
import leftLine from "@iconify-icons/ri/arrow-left-s-line";
import ProfileIcon from "@iconify-icons/ri/user-3-line";
import AccountSafeIcon from "@iconify-icons/ri/profile-line";
defineOptions({
name: "AccountSettings"
});
const router = useRouter();
const isOpen = ref(deviceDetection() ? false : true);
const { $storage } = useGlobal<GlobalPropertiesApi>();
onBeforeMount(() => {
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
});
/** 用户名 */
const username = computed(() => {
return useUserStoreHook()?.username;
});
/** 用户头像 */
const userAvatar = computed(() => {
console.log(useUserStoreHook()?.avatar);
if (useUserStoreHook()?.avatar) {
return useUserStoreHook()?.avatar;
} else {
}
return Avatar;
});
/**用户账号 */
const nickname = computed(() => {
return useUserStoreHook()?.nickname;
});
const panes = [
{
key: "profile",
label: "个人信息",
icon: ProfileIcon,
component: Profile
},
{
key: "accountSafe",
label: "账户安全",
icon: AccountSafeIcon,
component: AccountSafe
}
];
const witchPane = ref("profile");
</script>
<template>
<el-container class="h-full">
<el-aside
v-if="isOpen"
class="pure-account-settings overflow-hidden px-2 dark:!bg-[var(--el-bg-color)] border-r-[1px] border-[var(--pure-border-color)]"
:width="deviceDetection() ? '180px' : '240px'"
>
<el-menu :default-active="witchPane" class="pure-account-settings-menu">
<el-menu-item
class="hover:!transition-all hover:!duration-200 hover:!text-base !h-[50px]"
@click="router.go(-1)"
>
<div class="flex items-center">
<IconifyIconOffline :icon="leftLine" />
<span class="ml-2">返回</span>
</div>
</el-menu-item>
<div class="flex items-center ml-8 mt-4 mb-4">
<el-avatar :size="48" :src="userAvatar" />
<div class="ml-4 flex flex-col max-w-[130px]">
<ReText class="font-bold !self-baseline">
{{ username }}
</ReText>
<ReText class="!self-baseline" type="info">
{{ nickname }}
</ReText>
</div>
</div>
<el-menu-item
v-for="item in panes"
:key="item.key"
:index="item.key"
@click="
() => {
witchPane = item.key;
if (deviceDetection()) {
isOpen = !isOpen;
}
}
"
>
<div class="flex items-center z-10">
<el-icon><IconifyIconOffline :icon="item.icon" /></el-icon>
<span>{{ item.label }}</span>
</div>
</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<LaySidebarTopCollapse
v-if="deviceDetection()"
class="px-0"
:is-active="isOpen"
@toggleClick="isOpen = !isOpen"
/>
<component
:is="panes.find(item => item.key === witchPane).component"
:class="[!deviceDetection() && 'ml-[120px]']"
/>
</el-main>
</el-container>
</template>
<style lang="scss">
.pure-account-settings {
background: var(--pure-theme-menu-bg) !important;
}
.pure-account-settings-menu {
background-color: transparent;
border: none;
.el-menu-item {
height: 48px !important;
color: var(--pure-theme-menu-text);
background-color: transparent !important;
transition: color 0.2s;
&:hover {
color: var(--pure-theme-menu-title-hover) !important;
}
&.is-active {
color: #fff !important;
&:hover {
color: #fff !important;
}
&::before {
position: absolute;
inset: 0 8px;
margin: 4px 0;
clear: both;
content: "";
background: var(--el-color-primary);
border-radius: 3px;
}
}
}
}
</style>
<style lang="scss" scoped>
body[layout] {
.el-menu--vertical .is-active {
color: #fff !important;
transition: color 0.2s;
&:hover {
color: #fff !important;
}
}
}
</style>

View File

@@ -0,0 +1,349 @@
import { message } from "@/utils/message";
import { addDialog } from "@/components/ReDialog";
import { reactive, ref, onMounted, watch } from "vue";
import { ElForm, ElFormItem, ElInput, ElProgress } from "element-plus";
import type { UserInfo } from "types/user";
import { getUserInfoAPI } from "@/api/login";
import {
putUpdateEmailAPI,
putUpdatePasswordAPI,
putUpdatePhoneAPI
} from "@/api/user";
import { isAllEmpty, isEmail, isPhone, storageLocal } from "@pureadmin/utils";
import { zxcvbn } from "@zxcvbn-ts/core";
import { setUserInfo, userInfoKey } from "@/utils/auth";
export const useUserInfo = () => {
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
const REGEXP_PWD =
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
const pwdProgress = [
{ color: "#e74242", text: "非常弱" },
{ color: "#EFBD47", text: "弱" },
{ color: "#ffa500", text: "一般" },
{ color: "#1bbf1b", text: "强" },
{ color: "#008000", text: "非常强" }
];
// 当前密码强度0-4
const curScore = ref();
const ruleFormRef = ref();
/**密码表单 */
const passwordForm = reactive({
oldPassword: "",
newPassword: ""
});
/**联系方式表单 */
const phoneForm = reactive({
password: "",
phone: ""
});
/**邮箱*/
const emailForm = reactive({
password: "",
email: ""
});
const userInfo = reactive<UserInfo>({
id: "",
username: "",
gender: 1,
avatar: "",
email: "",
nickname: "",
phone: "",
status: 0,
create_time: "",
update_time: "",
roles: [],
permissions: []
});
/**获取个人信息 */
const getUserInfo = async () => {
const res = await getUserInfoAPI();
if (res.success) {
Object.assign(userInfo, res.data);
const user = storageLocal().getItem<object>(userInfoKey);
storageLocal().setItem(userInfoKey, {
...user,
avatar: res.data.avatar,
nickname: res.data.nickname
});
setUserInfo(res.data);
}
};
/**组件挂载执行 */
onMounted(async () => {
await getUserInfo();
});
watch(
passwordForm,
({ newPassword }) =>
(curScore.value = isAllEmpty(newPassword)
? -1
: zxcvbn(newPassword).score)
);
/**密码校验规则 */
const passwordRule = [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error("请输入新密码~"));
} else if (!REGEXP_PWD.test(value)) {
callback(
new Error("密码格式应为8-18位数字、字母、符号的任意两种组合~")
);
} else {
callback();
}
},
trigger: "blur"
}
];
/**手机好校验规则 */
const phoneRule = [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error("请输入手机号码~"));
} else if (!isPhone(value)) {
callback(new Error("请输入正确的手机号码格式~"));
} else {
callback();
}
},
trigger: "blur"
}
];
/**邮箱校验规则 */
const emailRule = [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error("请输入邮箱~"));
} else if (!isEmail(value)) {
callback(new Error("请输入正确的邮箱格式~"));
} else {
callback();
}
},
trigger: "blur"
}
];
/** 重置密码 */
const handleReset = (row: UserInfo) => {
addDialog({
title: `重置 ${row.username}--${row.nickname} 的密码`,
width: "30%",
draggable: true,
closeOnClickModal: false,
contentRenderer: () => (
<div>
<ElForm ref={ruleFormRef} model={passwordForm}>
<ElFormItem
prop="oldPassword"
rules={[
{
required: true,
message: "请输入旧密码~",
trigger: "blur"
}
]}
>
<ElInput
clearable
show-password
type="password"
v-model={passwordForm.oldPassword}
placeholder="请输入旧密码~"
/>
</ElFormItem>
<ElFormItem prop="newPassword" rules={passwordRule}>
<ElInput
clearable
show-password
type="password"
v-model={passwordForm.newPassword}
placeholder="请输入新密码~"
/>
</ElFormItem>
</ElForm>
<div class="mt-4 flex">
{pwdProgress.map(({ color, text }, idx) => (
<div
class="w-[19vw]"
style={{ marginLeft: idx !== 0 ? "4px" : 0 }}
>
<ElProgress
striped
striped-flow
duration={curScore.value === idx ? 6 : 0}
percentage={curScore.value >= idx ? 100 : 0}
color={color}
stroke-width={10}
show-text={false}
/>
<p
class="text-center"
style={{ color: curScore.value === idx ? color : "" }}
>
{text}
</p>
</div>
))}
</div>
</div>
),
closeCallBack: () => passwordForm,
beforeSure: done => {
ruleFormRef.value.validate(async (valid: any) => {
if (valid) {
// 表单规则校验通过
const res = await putUpdatePasswordAPI(passwordForm);
if (res.code === 200) {
done();
message(res.msg, {
type: "success"
});
} else {
message(res.msg, {
type: "error"
});
}
}
});
}
});
};
/**更新手机号 */
const handlePhone = (row: UserInfo) => {
phoneForm.phone = row.phone;
addDialog({
title: `更新 ${row.username}--${row.nickname} 的联系号码`,
width: "30%",
draggable: true,
closeOnClickModal: false,
contentRenderer: () => (
<div>
<ElForm ref={ruleFormRef} model={phoneForm}>
<ElFormItem
prop="password"
rules={[
{
required: true,
message: "请输入密码~",
trigger: "blur"
}
]}
>
<ElInput
clearable
show-password
type="password"
v-model={phoneForm.password}
placeholder="请输入密码~"
/>
</ElFormItem>
<ElFormItem prop="phone" rules={phoneRule}>
<ElInput
clearable
type="text"
v-model={phoneForm.phone}
placeholder="请输入手机号~"
/>
</ElFormItem>
</ElForm>
</div>
),
closeCallBack: () => phoneForm,
beforeSure: done => {
ruleFormRef.value.validate(async (valid: any) => {
if (valid) {
// 表单规则校验通过
const res = await putUpdatePhoneAPI(phoneForm);
if (res.code === 200) {
done();
message(res.msg, {
type: "success"
});
} else {
message(res.msg, {
type: "error"
});
}
}
});
}
});
};
/**更新邮箱 */
const handleEmail = (row: UserInfo) => {
emailForm.email = row.email;
addDialog({
title: `更新 ${row.username}--${row.nickname} 的联系邮箱`,
width: "30%",
draggable: true,
closeOnClickModal: false,
contentRenderer: () => (
<div>
<ElForm ref={ruleFormRef} model={emailForm}>
<ElFormItem
prop="password"
rules={[
{
required: true,
message: "请输入密码~",
trigger: "blur"
}
]}
>
<ElInput
clearable
show-password
type="password"
v-model={emailForm.password}
placeholder="请输入密码~"
/>
</ElFormItem>
<ElFormItem prop="email" rules={emailRule}>
<ElInput
clearable
type="text"
v-model={emailForm.email}
placeholder="请输入联系邮箱~"
/>
</ElFormItem>
</ElForm>
</div>
),
closeCallBack: () => emailForm,
beforeSure: done => {
ruleFormRef.value.validate(async (valid: any) => {
if (valid) {
// 表单规则校验通过
const res = await putUpdateEmailAPI(emailForm);
if (res.code === 200) {
done();
message(res.msg, {
type: "success"
});
} else {
message(res.msg, {
type: "error"
});
}
}
});
}
});
};
return {
userInfo,
getUserInfo,
handleReset,
handlePhone,
handleEmail
};
};

View File

@@ -27,7 +27,7 @@ const onLogin = async (formEl: FormInstance | undefined) => {
if (valid) {
// 模拟登录请求,需根据实际开发进行修改
setTimeout(() => {
message(transformI18n($t("login.pureLoginSuccess")), {
message(transformI18n($t("login:LoginSuccess")), {
type: "success"
});
loading.value = false;
@@ -51,7 +51,7 @@ function onBack() {
<el-input
v-model="ruleForm.phone"
clearable
:placeholder="t('login.purePhone')"
:placeholder="t('login:Phone')"
:prefix-icon="useRenderIcon(Iphone)"
/>
</el-form-item>
@@ -63,7 +63,7 @@ function onBack() {
<el-input
v-model="ruleForm.verifyCode"
clearable
:placeholder="t('login.pureSmsVerifyCode')"
:placeholder="t('login:SmsVerifyCode')"
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
/>
<el-button
@@ -73,8 +73,8 @@ function onBack() {
>
{{
text.length > 0
? text + t("login.pureInfo")
: t("login.pureGetVerifyCode")
? text + t("login:Info")
: t("login:GetVerifyCode")
}}
</el-button>
</div>
@@ -90,7 +90,7 @@ function onBack() {
:loading="loading"
@click="onLogin(ruleFormRef)"
>
{{ t("login.pureLogin") }}
{{ t("login:Login") }}
</el-button>
</el-form-item>
</Motion>
@@ -98,7 +98,7 @@ function onBack() {
<Motion :delay="200">
<el-form-item>
<el-button class="w-full" size="default" @click="onBack">
{{ t("login.pureBack") }}
{{ t("login:Back") }}
</el-button>
</el-form-item>
</Motion>

View File

@@ -9,11 +9,11 @@ const { t } = useI18n();
<template>
<Motion class="-mt-2 -mb-2">
<ReQrcode :text="t('login.pureTest')" />
<ReQrcode :text="t('login:Test')" />
</Motion>
<Motion :delay="100">
<el-divider>
<p class="text-gray-500 text-xs">{{ t("login.pureTip") }}</p>
<p class="text-gray-500 text-xs">{{ t("login:Tip") }}</p>
</el-divider>
</Motion>
<Motion :delay="150">
@@ -21,7 +21,7 @@ const { t } = useI18n();
class="w-full mt-4"
@click="useUserStoreHook().SET_CURRENTPAGE(0)"
>
{{ t("login.pureBack") }}
{{ t("login:Back") }}
</el-button>
</Motion>
</template>

View File

@@ -3,37 +3,70 @@ import { useI18n } from "vue-i18n";
import { ref, reactive } from "vue";
import Motion from "../utils/motion";
import { message } from "@/utils/message";
import { updateRules } from "../utils/rule";
import type { FormInstance } from "element-plus";
import { registerRules } from "../utils/rule";
import type { FormInstance, FormItemProp } from "element-plus";
import { useVerifyCode } from "../utils/verifyCode";
import { clone } from "@pureadmin/utils";
import { $t, transformI18n } from "@/plugins/i18n";
import { useUserStoreHook } from "@/store/modules/user";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Lock from "@iconify-icons/ri/lock-fill";
import Iphone from "@iconify-icons/ep/iphone";
import User from "@iconify-icons/ri/user-3-fill";
import UserName from "@iconify-icons/ri/user-4-line";
import Mail from "@iconify-icons/ri/mail-open-line";
import { postGetCodeAPI, postRegisterAPI } from "@/api/login";
const { t } = useI18n();
const checked = ref(false);
const loading = ref(false);
const timer = ref(null);
const ruleForm = reactive({
username: "",
phone: "",
verifyCode: "",
gender: 1,
email: "",
nickname: "",
department_id: "",
code: "",
password: "",
repeatPassword: ""
});
/**当前选项 */
const currentOption = ref<number>(1);
/**
* 点击返回
*/
const clickBack = () => {
if (currentOption.value >= 1) {
currentOption.value--;
} else {
currentOption.value = 1;
}
};
/**
* 点击下一步
*/
const clickNext = async () => {
await ruleFormRef.value.validate(async (valid, fields) => {
if (valid) {
if (currentOption.value <= 2) {
currentOption.value++;
} else {
currentOption.value = 2;
}
}
});
};
const ruleFormRef = ref<FormInstance>();
const { isDisabled, text } = useVerifyCode();
const repeatPasswordRule = [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePassWordSureReg"))));
callback(new Error(transformI18n($t("login:PassWordSureReg"))));
} else if (ruleForm.password !== value) {
callback(
new Error(transformI18n($t("login.purePassWordDifferentReg")))
);
callback(new Error(transformI18n($t("login:PassWordDifferentReg"))));
} else {
callback();
}
@@ -45,19 +78,33 @@ const repeatPasswordRule = [
const onUpdate = async (formEl: FormInstance | undefined) => {
loading.value = true;
if (!formEl) return;
await formEl.validate(valid => {
await formEl.validate(async (valid, fields) => {
if (valid) {
if (checked.value) {
// 模拟请求,需根据实际开发进行修改
setTimeout(() => {
message(transformI18n($t("login.pureRegisterSuccess")), {
const res = await postRegisterAPI({
username: ruleForm.username,
password: ruleForm.password,
gender: ruleForm.gender,
email: ruleForm.email,
nickname: ruleForm.nickname,
phone: ruleForm.phone,
code: ruleForm.code,
department_id: ruleForm.department_id
});
if (res.code === 200) {
message(transformI18n($t("login:RegisterSuccess")), {
type: "success"
});
loading.value = false;
}, 2000);
useUserStoreHook().SET_CURRENTPAGE(0);
} else {
message(res.msg, {
type: "error"
});
}
} else {
loading.value = false;
message(transformI18n($t("login.pureTickPrivacy")), {
message(transformI18n($t("login:TickPrivacy")), {
type: "warning"
});
}
@@ -66,9 +113,42 @@ const onUpdate = async (formEl: FormInstance | undefined) => {
}
});
};
const start = async (
formEl: FormInstance | undefined,
props: FormItemProp,
time = 120
) => {
if (!formEl) return;
const initTime = clone(time, true);
await formEl.validateField(props, async isValid => {
if (isValid) {
const res = await postGetCodeAPI({
username: ruleForm.username,
title: "注册",
mail: ruleForm.email
});
if (res.code === 200) {
clearInterval(timer.value);
isDisabled.value = true;
text.value = `${time}`;
timer.value = setInterval(() => {
if (time > 0) {
time -= 1;
text.value = `${time}`;
} else {
text.value = "";
isDisabled.value = false;
clearInterval(timer.value);
time = initTime;
}
}, 1000);
} else {
message(res.msg, { type: "error" });
}
}
});
};
function onBack() {
useVerifyCode().end();
useUserStoreHook().SET_CURRENTPAGE(0);
}
</script>
@@ -77,15 +157,15 @@ function onBack() {
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="updateRules"
:rules="registerRules"
size="large"
>
<Motion>
<Motion v-if="currentOption === 1">
<el-form-item
:rules="[
{
required: true,
message: transformI18n($t('login.pureUsernameReg')),
message: transformI18n($t('login:UsernameReg')),
trigger: 'blur'
}
]"
@@ -94,101 +174,215 @@ function onBack() {
<el-input
v-model="ruleForm.username"
clearable
:placeholder="t('login.pureUsername')"
:prefix-icon="useRenderIcon(User)"
:placeholder="t('login:Username')"
:prefix-icon="
useRenderIcon(UserName, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion v-if="currentOption === 1">
<el-form-item
:rules="[
{
required: true,
message: transformI18n($t('login:NicknameReg')),
trigger: 'blur'
}
]"
prop="nickname"
>
<el-input
v-model="ruleForm.nickname"
clearable
:placeholder="t('login:Nickname')"
:prefix-icon="
useRenderIcon(User, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion :delay="100">
<Motion v-if="currentOption === 1" :delay="100">
<el-form-item prop="phone">
<el-input
v-model="ruleForm.phone"
clearable
:placeholder="t('login.purePhone')"
:prefix-icon="useRenderIcon(Iphone)"
:placeholder="t('login:Phone')"
:prefix-icon="
useRenderIcon(Iphone, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion v-if="currentOption === 2" :delay="100">
<el-form-item prop="email">
<el-input
v-model="ruleForm.email"
clearable
:placeholder="t('login:Email')"
:prefix-icon="
useRenderIcon(Mail, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="verifyCode">
<Motion v-if="currentOption === 2" :delay="150">
<el-form-item prop="code">
<div class="w-full flex justify-between">
<el-input
v-model="ruleForm.verifyCode"
v-model="ruleForm.code"
clearable
:placeholder="t('login.pureSmsVerifyCode')"
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
:placeholder="t('login:EmailVerifyCode')"
:prefix-icon="
useRenderIcon('ri:shield-keyhole-line', {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
<el-button
:disabled="isDisabled"
class="ml-2"
@click="useVerifyCode().start(ruleFormRef, 'phone')"
@click="start(ruleFormRef, 'email')"
>
{{
text.length > 0
? text + t("login.pureInfo")
: t("login.pureGetVerifyCode")
? text + t("login:Info")
: t("login:GetVerifyCode")
}}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion :delay="200">
<Motion v-if="currentOption === 2" :delay="200">
<el-form-item prop="password">
<el-input
v-model="ruleForm.password"
clearable
show-password
:placeholder="t('login.purePassword')"
:prefix-icon="useRenderIcon(Lock)"
:placeholder="t('login:Password')"
:prefix-icon="
useRenderIcon(Lock, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion :delay="250">
<Motion v-if="currentOption === 2" :delay="250">
<el-form-item :rules="repeatPasswordRule" prop="repeatPassword">
<el-input
v-model="ruleForm.repeatPassword"
clearable
show-password
:placeholder="t('login.pureSure')"
:prefix-icon="useRenderIcon(Lock)"
:placeholder="t('login:Sure')"
:prefix-icon="
useRenderIcon(Lock, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion v-if="currentOption === 1" :delay="100">
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="ruleForm.gender" class="ml-4">
<el-radio :value="1" size="default"></el-radio>
<el-radio :value="0" size="default"></el-radio>
</el-radio-group>
</el-form-item>
</Motion>
<Motion :delay="300">
<el-form-item>
<el-checkbox v-model="checked">
{{ t("login.pureReadAccept") }}
{{ t("login:ReadAccept") }}
</el-checkbox>
<el-button link type="primary">
{{ t("login.purePrivacyPolicy") }}
{{ t("login:PrivacyPolicy") }}
</el-button>
</el-form-item>
</Motion>
<Motion :delay="350">
<Motion v-if="currentOption !== 1" :delay="350">
<el-form-item>
<el-button
class="w-full"
size="default"
type="primary"
:loading="loading"
@click="onUpdate(ruleFormRef)"
>
{{ t("login.pureDefinite") }}
</el-button>
<div class="w-full flex justify-center items-center">
<el-button
class="w-[75%]"
size="default"
round
:loading="loading"
@click="clickBack"
>
{{ t("login:Laststep") }}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion v-if="currentOption !== 2" :delay="350">
<el-form-item>
<div class="w-full flex justify-center items-center">
<el-button
class="w-[75%]"
size="default"
type="primary"
round
:loading="loading"
@click="clickNext"
>
{{ t("login:Nextstep") }}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion v-if="currentOption === 2" :delay="350">
<el-form-item>
<div class="w-full flex justify-center items-center">
<el-button
class="w-[75%]"
size="default"
type="primary"
round
:loading="loading"
@click="onUpdate(ruleFormRef)"
>
{{ t("login:Register") }}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion :delay="400">
<el-form-item>
<el-button class="w-full" size="default" @click="onBack">
{{ t("login.pureBack") }}
</el-button>
<div class="w-full flex justify-center items-center">
<el-button class="w-[75%]" round size="default" @click="onBack">
{{ t("login:Back") }}
</el-button>
</div>
</el-form-item>
</Motion>
</el-form>

View File

@@ -4,19 +4,24 @@ import { ref, reactive } from "vue";
import Motion from "../utils/motion";
import { message } from "@/utils/message";
import { updateRules } from "../utils/rule";
import type { FormInstance } from "element-plus";
import type { FormInstance, FormItemProp } from "element-plus";
import { useVerifyCode } from "../utils/verifyCode";
import { $t, transformI18n } from "@/plugins/i18n";
import { clone } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { postGetCodeAPI, postResetPasswordAPI } from "@/api/login";
import Lock from "@iconify-icons/ri/lock-fill";
import Iphone from "@iconify-icons/ep/iphone";
import UserName from "@iconify-icons/ri/user-4-line";
import Mail from "@iconify-icons/ri/mail-open-line";
const { t } = useI18n();
const loading = ref(false);
const timer = ref(null);
const ruleForm = reactive({
phone: "",
verifyCode: "",
username: "",
email: "",
code: "",
password: "",
repeatPassword: ""
});
@@ -26,11 +31,9 @@ const repeatPasswordRule = [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePassWordSureReg"))));
callback(new Error(transformI18n($t("login:PassWordSureReg"))));
} else if (ruleForm.password !== value) {
callback(
new Error(transformI18n($t("login.purePassWordDifferentReg")))
);
callback(new Error(transformI18n($t("login:PassWordDifferentReg"))));
} else {
callback();
}
@@ -42,23 +45,67 @@ const repeatPasswordRule = [
const onUpdate = async (formEl: FormInstance | undefined) => {
loading.value = true;
if (!formEl) return;
await formEl.validate(valid => {
await formEl.validate(async (valid, fields) => {
if (valid) {
// 模拟请求,需根据实际开发进行修改
setTimeout(() => {
message(transformI18n($t("login.purePassWordUpdateReg")), {
const res = await postResetPasswordAPI({
username: ruleForm.username,
password: ruleForm.password,
code: ruleForm.code,
mail: ruleForm.email
});
if (res.success) {
message(res.msg, {
type: "success"
});
loading.value = false;
}, 2000);
useUserStoreHook().SET_CURRENTPAGE(0);
} else {
message(res.msg, {
type: "error"
});
loading.value = false;
}
} else {
loading.value = false;
}
});
};
const start = async (
formEl: FormInstance | undefined,
props: FormItemProp,
time = 120
) => {
if (!formEl) return;
const initTime = clone(time, true);
await formEl.validateField(props, async isValid => {
if (isValid) {
const res = await postGetCodeAPI({
username: ruleForm.username,
title: "重置",
mail: ruleForm.email
});
if (res.code === 200) {
clearInterval(timer.value);
isDisabled.value = true;
text.value = `${time}`;
timer.value = setInterval(() => {
if (time > 0) {
time -= 1;
text.value = `${time}`;
} else {
text.value = "";
isDisabled.value = false;
clearInterval(timer.value);
time = initTime;
}
}, 1000);
} else {
message(res.msg, { type: "error" });
}
}
});
};
function onBack() {
useVerifyCode().end();
useUserStoreHook().SET_CURRENTPAGE(0);
}
</script>
@@ -71,34 +118,70 @@ function onBack() {
size="large"
>
<Motion>
<el-form-item prop="phone">
<el-form-item
:rules="[
{
required: true,
message: transformI18n($t('login:UsernameReg')),
trigger: 'blur'
}
]"
prop="username"
>
<el-input
v-model="ruleForm.phone"
v-model="ruleForm.username"
clearable
:placeholder="t('login.purePhone')"
:prefix-icon="useRenderIcon(Iphone)"
:placeholder="t('login:Username')"
:prefix-icon="
useRenderIcon(UserName, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion :delay="100">
<el-form-item prop="verifyCode">
<el-form-item prop="email">
<el-input
v-model="ruleForm.email"
clearable
:placeholder="t('login:Email')"
:prefix-icon="
useRenderIcon(Mail, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion :delay="100">
<el-form-item prop="code">
<div class="w-full flex justify-between">
<el-input
v-model="ruleForm.verifyCode"
v-model="ruleForm.code"
clearable
:placeholder="t('login.pureSmsVerifyCode')"
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
:placeholder="t('login:EmailVerifyCode')"
:prefix-icon="
useRenderIcon('ri:shield-keyhole-line', {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
<el-button
:disabled="isDisabled"
class="ml-2"
@click="useVerifyCode().start(ruleFormRef, 'phone')"
@click="start(ruleFormRef, 'email')"
>
{{
text.length > 0
? text + t("login.pureInfo")
: t("login.pureGetVerifyCode")
? text + t("login:Info")
: t("login:GetVerifyCode")
}}
</el-button>
</div>
@@ -111,8 +194,14 @@ function onBack() {
v-model="ruleForm.password"
clearable
show-password
:placeholder="t('login.purePassword')"
:prefix-icon="useRenderIcon(Lock)"
:placeholder="t('login:Password')"
:prefix-icon="
useRenderIcon(Lock, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
@@ -123,31 +212,42 @@ function onBack() {
v-model="ruleForm.repeatPassword"
clearable
show-password
:placeholder="t('login.pureSure')"
:prefix-icon="useRenderIcon(Lock)"
:placeholder="t('login:Sure')"
:prefix-icon="
useRenderIcon(Lock, {
color: '#4380FF',
width: 32,
height: 32
})
"
/>
</el-form-item>
</Motion>
<Motion :delay="250">
<el-form-item>
<el-button
class="w-full"
size="default"
type="primary"
:loading="loading"
@click="onUpdate(ruleFormRef)"
>
{{ t("login.pureDefinite") }}
</el-button>
<div class="w-full flex justify-center items-center">
<el-button
class="w-[75%]"
size="default"
type="primary"
round
:loading="loading"
@click="onUpdate(ruleFormRef)"
>
{{ t("login:Definite") }}
</el-button>
</div>
</el-form-item>
</Motion>
<Motion :delay="300">
<el-form-item>
<el-button class="w-full" size="default" @click="onBack">
{{ t("login.pureBack") }}
</el-button>
<div class="w-full flex justify-center items-center">
<el-button class="w-[75%]" size="default" round @click="onBack">
{{ t("login:Back") }}
</el-button>
</div>
</el-form-item>
</Motion>
</el-form>

View File

@@ -31,7 +31,9 @@ import Lock from "@iconify-icons/ri/lock-fill";
import Check from "@iconify-icons/ep/check";
import User from "@iconify-icons/ri/user-3-fill";
import Info from "@iconify-icons/ri/information-line";
import { GetCaptchaAPI } from "@/api/user";
import { GetCaptchaAPI } from "@/api/login";
import { getUserInfoAPI } from "@/api/login";
import { setUserInfo } from "@/utils/auth";
defineOptions({
name: "Login"
@@ -79,17 +81,21 @@ const onLogin = async (formEl: FormInstance | undefined) => {
if (res.code === 200) {
useUserStoreHook().SET_ACCESSTOKEN(res.data.accessToken);
// 获取后端路由
return initRouter().then(() => {
return initRouter().then(async () => {
disabled.value = true;
const res = await getUserInfoAPI();
if (res.success) {
setUserInfo(res.data);
}
router
.push(getTopMenu(true).path)
.then(() => {
message(t("login.pureLoginSuccess"), { type: "success" });
message(t("login:LoginSuccess"), { type: "success" });
})
.finally(() => (disabled.value = false));
});
} else {
message(t("login.pureLoginFail"), { type: "error" });
message(t("login:LoginFail"), { type: "error" });
await getImgCode();
}
})
@@ -107,9 +113,9 @@ const immediateDebounce: any = debounce(
/**获取验证码 */
const getImgCode = async () => {
const res = await GetCaptchaAPI();
if (res.code === 200) {
imgCode.value = res.data.image;
imgId.value = res.data.captchaId;
if (res.success) {
imgCode.value = res.data.captcha;
imgId.value = res.data.uuid;
}
};
@@ -122,9 +128,6 @@ useEventListener(document, "keydown", ({ code }) => {
immediateDebounce(ruleFormRef.value);
});
// watch(imgCode, value => {
// useUserStoreHook().SET_VERIFYCODE(value);
// });
watch(checked, bool => {
useUserStoreHook().SET_ISREMEMBERED(bool);
});
@@ -208,7 +211,7 @@ onMounted(async () => {
:rules="[
{
required: true,
message: transformI18n($t('login.pureUsernameReg')),
message: transformI18n($t('login:UsernameReg')),
trigger: 'blur'
}
]"
@@ -217,7 +220,7 @@ onMounted(async () => {
<el-input
v-model="ruleForm.username"
clearable
:placeholder="t('login.pureUsername')"
:placeholder="t('login:Username')"
:prefix-icon="useRenderIcon(User)"
/>
</el-form-item>
@@ -229,7 +232,7 @@ onMounted(async () => {
v-model="ruleForm.password"
clearable
show-password
:placeholder="t('login.purePassword')"
:placeholder="t('login:Password')"
:prefix-icon="useRenderIcon(Lock)"
/>
</el-form-item>
@@ -240,7 +243,7 @@ onMounted(async () => {
<el-input
v-model="ruleForm.verifyCode"
clearable
:placeholder="t('login.pureVerifyCode')"
:placeholder="t('login:VerifyCode')"
:prefix-icon="useRenderIcon('ri:shield-keyhole-line')"
>
<template v-slot:append>
@@ -273,10 +276,10 @@ onMounted(async () => {
<option value="7">7</option>
<option value="30">30</option>
</select>
{{ t("login.pureRemember") }}
{{ t("login:Remember") }}
<IconifyIconOffline
v-tippy="{
content: t('login.pureRememberInfo'),
content: t('login:RememberInfo'),
placement: 'top'
}"
:icon="Info"
@@ -289,7 +292,7 @@ onMounted(async () => {
type="primary"
@click="useUserStoreHook().SET_CURRENTPAGE(4)"
>
{{ t("login.pureForget") }}
{{ t("login:Forget") }}
</el-button>
</div>
<el-button
@@ -300,7 +303,7 @@ onMounted(async () => {
:disabled="disabled"
@click="onLogin(ruleFormRef)"
>
{{ t("login.pureLogin") }}
{{ t("login:Login") }}
</el-button>
</el-form-item>
</Motion>

View File

@@ -2,31 +2,31 @@ import { $t } from "@/plugins/i18n";
const operates = [
{
title: $t("login.purePhoneLogin")
title: $t("login:PhoneLogin")
},
{
title: $t("login.pureQRCodeLogin")
title: $t("login:QRCodeLogin")
},
{
title: $t("login.pureRegister")
title: $t("login:Register")
}
];
const thirdParty = [
{
title: $t("login.pureWeChatLogin"),
title: $t("login:WeChatLogin"),
icon: "wechat"
},
{
title: $t("login.pureAlipayLogin"),
title: $t("login:AlipayLogin"),
icon: "alipay"
},
{
title: $t("login.pureQQLogin"),
title: $t("login:QQLogin"),
icon: "qq"
},
{
title: $t("login.pureWeiBoLogin"),
title: $t("login:WeiBoLogin"),
icon: "weibo"
}
];

View File

@@ -16,9 +16,9 @@ const loginRules = reactive<FormRules>({
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePassWordReg"))));
callback(new Error(transformI18n($t("login:PassWordReg"))));
} else if (!REGEXP_PWD.test(value)) {
callback(new Error(transformI18n($t("login.purePassWordRuleReg"))));
callback(new Error(transformI18n($t("login:PassWordRuleReg"))));
} else {
callback();
}
@@ -30,7 +30,7 @@ const loginRules = reactive<FormRules>({
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
} else {
callback();
}
@@ -46,9 +46,9 @@ const phoneRules = reactive<FormRules>({
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePhoneReg"))));
callback(new Error(transformI18n($t("login:PhoneReg"))));
} else if (!isPhone(value)) {
callback(new Error(transformI18n($t("login.purePhoneCorrectReg"))));
callback(new Error(transformI18n($t("login:PhoneCorrectReg"))));
} else {
callback();
}
@@ -60,9 +60,9 @@ const phoneRules = reactive<FormRules>({
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
} else if (!REGEXP_SIX.test(value)) {
callback(new Error(transformI18n($t("login.pureVerifyCodeSixReg"))));
callback(new Error(transformI18n($t("login:VerifyCodeSixReg"))));
} else {
callback();
}
@@ -74,27 +74,22 @@ const phoneRules = reactive<FormRules>({
/** 忘记密码校验 */
const updateRules = reactive<FormRules>({
phone: [
email: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePhoneReg"))));
} else if (!isPhone(value)) {
callback(new Error(transformI18n($t("login.purePhoneCorrectReg"))));
callback(new Error(transformI18n($t("login:EmailReg"))));
} else {
callback();
}
},
trigger: "blur"
}
}
],
verifyCode: [
code: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
} else if (!REGEXP_SIX.test(value)) {
callback(new Error(transformI18n($t("login.pureVerifyCodeSixReg"))));
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
} else {
callback();
}
@@ -106,9 +101,9 @@ const updateRules = reactive<FormRules>({
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login.purePassWordReg"))));
callback(new Error(transformI18n($t("login:PassWordReg"))));
} else if (!REGEXP_PWD.test(value)) {
callback(new Error(transformI18n($t("login.purePassWordRuleReg"))));
callback(new Error(transformI18n($t("login:PassWordRuleReg"))));
} else {
callback();
}
@@ -118,4 +113,81 @@ const updateRules = reactive<FormRules>({
]
});
export { loginRules, phoneRules, updateRules };
/** 注册账号校验 */
const registerRules = reactive<FormRules>({
phone: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login:PhoneReg"))));
} else if (!isPhone(value)) {
callback(new Error(transformI18n($t("login:PhoneCorrectReg"))));
} else {
callback();
}
},
trigger: "blur"
}
],
code: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login:VerifyCodeReg"))));
} else {
callback();
}
},
trigger: "blur"
}
],
password: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login:PassWordReg"))));
} else if (!REGEXP_PWD.test(value)) {
callback(new Error(transformI18n($t("login:PassWordRuleReg"))));
} else {
callback();
}
},
trigger: "blur"
}
],
username: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login:UsernameReg"))));
} else {
callback();
}
}
}
],
nickname: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login:NicknameReg"))));
} else {
callback();
}
}
}
],
email: [
{
validator: (rule, value, callback) => {
if (value === "") {
callback(new Error(transformI18n($t("login:EmailReg"))));
} else {
callback();
}
}
}
]
});
export { loginRules, phoneRules, updateRules, registerRules };

View File

@@ -1,99 +0,0 @@
<script setup lang="ts">
import { hasAuth, getAuths } from "@/router/utils";
defineOptions({
name: "PermissionButtonRouter"
});
</script>
<template>
<div>
<p class="mb-2">当前拥有的code列表{{ getAuths() }}</p>
<el-card shadow="never" class="mb-2">
<template #header>
<div class="card-header">组件方式判断权限</div>
</template>
<el-space wrap>
<Auth value="permission:btn:add">
<el-button plain type="warning">
拥有code'permission:btn:add' 权限可见
</el-button>
</Auth>
<Auth :value="['permission:btn:edit']">
<el-button plain type="primary">
拥有code['permission:btn:edit'] 权限可见
</el-button>
</Auth>
<Auth
:value="[
'permission:btn:add',
'permission:btn:edit',
'permission:btn:delete'
]"
>
<el-button plain type="danger">
拥有code['permission:btn:add', 'permission:btn:edit',
'permission:btn:delete'] 权限可见
</el-button>
</Auth>
</el-space>
</el-card>
<el-card shadow="never" class="mb-2">
<template #header>
<div class="card-header">函数方式判断权限</div>
</template>
<el-space wrap>
<el-button v-if="hasAuth('permission:btn:add')" plain type="warning">
拥有code'permission:btn:add' 权限可见
</el-button>
<el-button v-if="hasAuth(['permission:btn:edit'])" plain type="primary">
拥有code['permission:btn:edit'] 权限可见
</el-button>
<el-button
v-if="
hasAuth([
'permission:btn:add',
'permission:btn:edit',
'permission:btn:delete'
])
"
plain
type="danger"
>
拥有code['permission:btn:add', 'permission:btn:edit',
'permission:btn:delete'] 权限可见
</el-button>
</el-space>
</el-card>
<el-card shadow="never">
<template #header>
<div class="card-header">
指令方式判断权限(该方式不能动态修改权限)
</div>
</template>
<el-space wrap>
<el-button v-auth="'permission:btn:add'" plain type="warning">
拥有code'permission:btn:add' 权限可见
</el-button>
<el-button v-auth="['permission:btn:edit']" plain type="primary">
拥有code['permission:btn:edit'] 权限可见
</el-button>
<el-button
v-auth="[
'permission:btn:add',
'permission:btn:edit',
'permission:btn:delete'
]"
plain
type="danger"
>
拥有code['permission:btn:add', 'permission:btn:edit',
'permission:btn:delete'] 权限可见
</el-button>
</el-space>
</el-card>
</div>
</template>

View File

@@ -1,109 +0,0 @@
<script setup lang="ts">
import { hasPerms } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user";
const { permissions } = useUserStoreHook();
defineOptions({
name: "PermissionButtonLogin"
});
</script>
<template>
<div>
<p class="mb-2">当前拥有的code列表{{ permissions }}</p>
<p v-show="permissions?.[0] === '*:*:*'" class="mb-2">
*:*:* 代表拥有全部按钮级别权限
</p>
<el-card shadow="never" class="mb-2">
<template #header>
<div class="card-header">组件方式判断权限</div>
</template>
<el-space wrap>
<Perms value="permission:btn:add">
<el-button plain type="warning">
拥有code'permission:btn:add' 权限可见
</el-button>
</Perms>
<Perms :value="['permission:btn:edit']">
<el-button plain type="primary">
拥有code['permission:btn:edit'] 权限可见
</el-button>
</Perms>
<Perms
:value="[
'permission:btn:add',
'permission:btn:edit',
'permission:btn:delete'
]"
>
<el-button plain type="danger">
拥有code['permission:btn:add', 'permission:btn:edit',
'permission:btn:delete'] 权限可见
</el-button>
</Perms>
</el-space>
</el-card>
<el-card shadow="never" class="mb-2">
<template #header>
<div class="card-header">函数方式判断权限</div>
</template>
<el-space wrap>
<el-button v-if="hasPerms('permission:btn:add')" plain type="warning">
拥有code'permission:btn:add' 权限可见
</el-button>
<el-button
v-if="hasPerms(['permission:btn:edit'])"
plain
type="primary"
>
拥有code['permission:btn:edit'] 权限可见
</el-button>
<el-button
v-if="
hasPerms([
'permission:btn:add',
'permission:btn:edit',
'permission:btn:delete'
])
"
plain
type="danger"
>
拥有code['permission:btn:add', 'permission:btn:edit',
'permission:btn:delete'] 权限可见
</el-button>
</el-space>
</el-card>
<el-card shadow="never">
<template #header>
<div class="card-header">
指令方式判断权限(该方式不能动态修改权限)
</div>
</template>
<el-space wrap>
<el-button v-perms="'permission:btn:add'" plain type="warning">
拥有code'permission:btn:add' 权限可见
</el-button>
<el-button v-perms="['permission:btn:edit']" plain type="primary">
拥有code['permission:btn:edit'] 权限可见
</el-button>
<el-button
v-perms="[
'permission:btn:add',
'permission:btn:edit',
'permission:btn:delete'
]"
plain
type="danger"
>
拥有code['permission:btn:add', 'permission:btn:edit',
'permission:btn:delete'] 权限可见
</el-button>
</el-space>
</el-card>
</div>
</template>

View File

@@ -1,66 +0,0 @@
<script setup lang="ts">
import { initRouter } from "@/router/utils";
import { storageLocal } from "@pureadmin/utils";
import { type CSSProperties, ref, computed } from "vue";
import { useUserStoreHook } from "@/store/modules/user";
import { usePermissionStoreHook } from "@/store/modules/permission";
defineOptions({
name: "PermissionPage"
});
const elStyle = computed((): CSSProperties => {
return {
width: "85vw",
justifyContent: "start"
};
});
const username = ref(useUserStoreHook()?.username);
const options = [
{
value: "admin",
label: "管理员角色"
},
{
value: "common",
label: "普通角色"
}
];
function onChange() {
useUserStoreHook()
.loginByUsername({ username: username.value, password: "admin123" })
.then(res => {
if (res.success) {
storageLocal().removeItem("async-routes");
usePermissionStoreHook().clearAllCachePage();
initRouter();
}
});
}
</script>
<template>
<div>
<p class="mb-2">
模拟后台根据不同角色返回对应路由观察左侧菜单变化管理员角色可查看系统管理菜单普通角色不可查看系统管理菜单
</p>
<el-card shadow="never" :style="elStyle">
<template #header>
<div class="card-header">
<span>当前角色{{ username }}</span>
</div>
</template>
<el-select v-model="username" class="!w-[160px]" @change="onChange">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-card>
</div>
</template>

View File

@@ -5,5 +5,5 @@ defineOptions({
</script>
<template>
<h1>Pure-Admin-Thin国际化版本</h1>
<h1>首页</h1>
</template>