mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-12 20:23:52 +08:00
This commit is contained in:
@@ -174,4 +174,4 @@
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
<style lang="less"></style>
|
||||
|
||||
64
web/src/views/login/components/form-other.vue
Normal file
64
web/src/views/login/components/form-other.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<n-form-item class="default-color">
|
||||
<div class="flex view-account-other">
|
||||
<div class="flex-initial">
|
||||
<span>其它登录方式</span>
|
||||
</div>
|
||||
<div class="flex-initial mx-2">
|
||||
<a @click="handleLoginWechat">
|
||||
<n-icon size="24" color="rgb(24, 160, 88)">
|
||||
<LogoWechat />
|
||||
</n-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-initial mx-2">
|
||||
<a @click="handleLogoTiktok">
|
||||
<n-icon size="24" color="rgba(25, 28, 34, 0.88)">
|
||||
<LogoTiktok />
|
||||
</n-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-initial" style="margin-left: auto" v-if="userStore.loginConfig?.loginRegisterSwitch === 1">
|
||||
<a @click="updateActiveModule(moduleKey)">{{ tag }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LogoWechat, LogoTiktok } from '@vicons/ionicons5';
|
||||
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import {useMessage} from "naive-ui";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
interface Props {
|
||||
moduleKey: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
moduleKey: 'register',
|
||||
tag: '注册账号',
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
const emit = defineEmits(['updateActiveModule']);
|
||||
|
||||
function updateActiveModule(key: string) {
|
||||
emit('updateActiveModule', key);
|
||||
}
|
||||
|
||||
function handleLogoTiktok() {
|
||||
console.log('handleLogoTiktok...');
|
||||
message.info('暂未开放');
|
||||
}
|
||||
|
||||
function handleLoginWechat() {
|
||||
console.log('handleLoginWechat...');
|
||||
message.info('暂未开放');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
68
web/src/views/login/components/style.less
Normal file
68
web/src/views/login/components/style.less
Normal file
@@ -0,0 +1,68 @@
|
||||
.view-account {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
|
||||
&-container {
|
||||
flex: 1;
|
||||
padding: 32px 12px;
|
||||
max-width: 384px;
|
||||
min-width: 320px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&-top {
|
||||
padding: 32px 0;
|
||||
text-align: center;
|
||||
|
||||
&-desc {
|
||||
font-size: 14px;
|
||||
color: #808695;
|
||||
}
|
||||
}
|
||||
|
||||
&-other {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.default-color {
|
||||
color: #515a6e;
|
||||
|
||||
.ant-checkbox-wrapper {
|
||||
color: #515a6e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.view-account {
|
||||
background-image: url('~@/assets/images/login.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50%;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.page-account-container {
|
||||
padding: 32px 0 24px 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-y-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.w-300px {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.w-12px {
|
||||
width: 12px;
|
||||
}
|
||||
@@ -1,224 +1,96 @@
|
||||
<template>
|
||||
<div class="view-account">
|
||||
<div class="view-account-header"></div>
|
||||
<div class="view-account-container">
|
||||
<div class="view-account-top">
|
||||
<div class="view-account-top-logo">
|
||||
<img src="~@/assets/images/account-logo.png" alt="" />
|
||||
</div>
|
||||
<div class="view-account-top-desc">HotGo 后台管理系统</div>
|
||||
</div>
|
||||
<div class="view-account-form">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
size="large"
|
||||
:model="formInline"
|
||||
:rules="rules"
|
||||
>
|
||||
<n-form-item path="username">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.username"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<PersonOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="pass">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.pass"
|
||||
type="password"
|
||||
showpassOn="click"
|
||||
placeholder="请输入密码"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<LockClosedOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="code" v-show="codeBase64 !== ''">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
:style="{ width: '100%' }"
|
||||
placeholder="验证码"
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.code"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695" :component="SafetyCertificateOutlined" />
|
||||
</template>
|
||||
<template #suffix> </template>
|
||||
</n-input>
|
||||
|
||||
<n-loading-bar-provider
|
||||
:to="loadingBarTargetRef"
|
||||
container-style="position: absolute;"
|
||||
>
|
||||
<img
|
||||
ref="loadingBarTargetRef"
|
||||
style="width: 100px"
|
||||
:src="codeBase64"
|
||||
@click="refreshCode"
|
||||
loading="lazy"
|
||||
alt="点击获取"
|
||||
/>
|
||||
<loading-bar-trigger />
|
||||
</n-loading-bar-provider>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
<n-form-item class="default-color">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex-initial">
|
||||
<n-checkbox v-model:checked="autoLogin">自动登录</n-checkbox>
|
||||
</div>
|
||||
<div class="flex-initial order-last">
|
||||
<a href="javascript:">忘记密码</a>
|
||||
</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="primary" @click="handleSubmit" size="large" :loading="loading" block>
|
||||
登录
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
<n-form-item class="default-color">
|
||||
<div class="flex view-account-other">
|
||||
<div class="flex-initial">
|
||||
<span>其它登录方式</span>
|
||||
</div>
|
||||
<div class="flex-initial mx-2">
|
||||
<a href="javascript:">
|
||||
<n-icon size="24" color="#2d8cf0">
|
||||
<LogoWechat />
|
||||
</n-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-initial mx-2">
|
||||
<a href="javascript:">
|
||||
<n-icon size="24" color="#2d8cf0">
|
||||
<LogoTiktok />
|
||||
</n-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-initial" style="margin-left: auto">
|
||||
<a @click="handleRegister">注册账号</a>
|
||||
</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
<div :style="containerCSS">
|
||||
<n-card :bordered="false">
|
||||
<header class="justify-between">
|
||||
<n-space justify="center">
|
||||
<div></div>
|
||||
<img src="~@/assets/images/logo.png" class="account-logo" alt="" />
|
||||
<n-gradient-text type="primary" :size="26">{{ projectName }}</n-gradient-text>
|
||||
<div></div>
|
||||
</n-space>
|
||||
</header>
|
||||
<main class="pt-24px">
|
||||
<div class="pt-18px">
|
||||
<transition name="fade-slide" appear>
|
||||
<component
|
||||
:is="activeModule.component"
|
||||
@updateActiveModule="handleUpdateActiveModule"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</main>
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import LoginFrom from './login/index.vue';
|
||||
import RegisterFrom from './register/index.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useMessage, useLoadingBar } from 'naive-ui';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import { PersonOutline, LockClosedOutline, LogoWechat, LogoTiktok } from '@vicons/ionicons5';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
import { SafetyCertificateOutlined } from '@vicons/antd';
|
||||
import { GetCaptcha } from '@/api/base';
|
||||
import { aesEcb } from '@/utils/encrypt';
|
||||
|
||||
interface FormState {
|
||||
username: string;
|
||||
pass: string;
|
||||
cid: string;
|
||||
code: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const formRef = ref();
|
||||
const message = useMessage();
|
||||
const loading = ref(false);
|
||||
const autoLogin = ref(true);
|
||||
const codeBase64 = ref('');
|
||||
const loadingBar = useLoadingBar();
|
||||
const loadingBarTargetRef = ref<undefined | HTMLElement>(undefined);
|
||||
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
||||
|
||||
const formInline = ref<FormState>({
|
||||
username: '',
|
||||
pass: '',
|
||||
cid: '',
|
||||
code: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
const rules = {
|
||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
pass: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||
code: { required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;
|
||||
|
||||
interface LoginModule {
|
||||
key: string;
|
||||
label: string;
|
||||
component: Component;
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const activeModule = ref<LoginModule>({
|
||||
key: 'login',
|
||||
label: '账号登录',
|
||||
component: LoginFrom,
|
||||
});
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
message.loading('登录中...');
|
||||
loading.value = true;
|
||||
try {
|
||||
const { code, message: msg } = await userStore.login({
|
||||
username: formInline.value.username,
|
||||
password: aesEcb.encrypt(formInline.value.pass),
|
||||
cid: formInline.value.cid,
|
||||
code: formInline.value.code,
|
||||
});
|
||||
message.destroyAll();
|
||||
if (code == ResultEnum.SUCCESS) {
|
||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
||||
message.success('登录成功,即将进入系统');
|
||||
if (route.name === LOGIN_NAME) {
|
||||
await router.replace('/');
|
||||
} else await router.replace(toPath);
|
||||
} else {
|
||||
message.info(msg || '登录失败');
|
||||
await refreshCode();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
message.error('请填写完整信息,并且进行验证码校验');
|
||||
}
|
||||
});
|
||||
};
|
||||
const modules: LoginModule[] = [
|
||||
{ key: 'login', label: '账号登录', component: LoginFrom },
|
||||
// { key: 'register', label: '注册账号', component: RegisterFrom },
|
||||
// { key: 'reset-pwd', label: '重置密码', component: ResetPwd },
|
||||
// { key: 'bind-wechat', label: '绑定微信', component: BindWechat }
|
||||
];
|
||||
|
||||
async function refreshCode() {
|
||||
loadingBar.start();
|
||||
const data = await GetCaptcha();
|
||||
codeBase64.value = data.base64;
|
||||
formInline.value.cid = data.cid;
|
||||
formInline.value.code = '';
|
||||
loadingBar.finish();
|
||||
const containerCSS = computed(() => {
|
||||
const val = document.body.clientWidth;
|
||||
return val <= 720
|
||||
? {}
|
||||
: {
|
||||
flex: `1`,
|
||||
padding: `62px 12px`,
|
||||
'max-width': `484px`,
|
||||
'min-width': '320px',
|
||||
margin: '0 auto',
|
||||
};
|
||||
});
|
||||
|
||||
function handleUpdateActiveModule(key: string) {
|
||||
const findItem = modules.find((item) => item.key === key);
|
||||
if (findItem) {
|
||||
activeModule.value = findItem;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(function () {
|
||||
refreshCode();
|
||||
});
|
||||
console.log('window.location.href',route.path);
|
||||
});
|
||||
//是否开放注册
|
||||
if (userStore.loginConfig?.loginRegisterSwitch === 1) {
|
||||
const findItem = modules.find((item) => item.key === 'register');
|
||||
if (!findItem) {
|
||||
modules.push({ key: 'register', label: '注册账号', component: RegisterFrom });
|
||||
}
|
||||
}
|
||||
|
||||
function handleRegister() {
|
||||
message.success('即将开放,请稍后');
|
||||
return;
|
||||
}
|
||||
const key = router.currentRoute.value.query?.scope as string;
|
||||
if (key) {
|
||||
handleUpdateActiveModule(key);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -228,14 +100,6 @@
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
|
||||
&-container {
|
||||
flex: 1;
|
||||
padding: 32px 12px;
|
||||
max-width: 384px;
|
||||
min-width: 320px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&-top {
|
||||
padding: 32px 0;
|
||||
text-align: center;
|
||||
@@ -271,4 +135,40 @@
|
||||
padding: 32px 0 24px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-tabs .n-tabs-nav--bar-type {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.pt-24px {
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.pt-18px {
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.text-18px {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.duration-300 {
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color,
|
||||
fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.account-logo {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
</style>
|
||||
|
||||
47
web/src/views/login/login/demo-account.vue
Normal file
47
web/src/views/login/login/demo-account.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<n-space :vertical="true">
|
||||
<n-divider>演示角色登录</n-divider>
|
||||
<n-space justify="center">
|
||||
<n-button
|
||||
v-for="item in accounts"
|
||||
:key="item.username"
|
||||
type="primary"
|
||||
@click="login(item.username, item.password)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
interface Emits {
|
||||
(e: 'login', param: { username: string; password: string }): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const accounts = [
|
||||
{
|
||||
label: '超级管理员',
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
},
|
||||
{
|
||||
label: '管理员',
|
||||
username: 'test',
|
||||
password: '123456',
|
||||
},
|
||||
{
|
||||
label: '普通用户',
|
||||
username: 'ameng',
|
||||
password: '123456',
|
||||
},
|
||||
];
|
||||
|
||||
function login(username: string, password: string) {
|
||||
emit('login', { username, password });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
311
web/src/views/login/login/form.vue
Normal file
311
web/src/views/login/login/form.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<n-form
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
size="large"
|
||||
:model="mode === 'account' ? formInline : formMobile"
|
||||
:rules="mode === 'account' ? rules : mobileRules"
|
||||
>
|
||||
<template v-if="mode === 'account'">
|
||||
<n-form-item path="username">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.username"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<PersonOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="pass">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.pass"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
placeholder="请输入密码"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<LockClosedOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="code" v-show="codeBase64 !== ''">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
:style="{ width: '100%' }"
|
||||
placeholder="验证码"
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.code"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695" :component="SafetyCertificateOutlined" />
|
||||
</template>
|
||||
<template #suffix> </template>
|
||||
</n-input>
|
||||
|
||||
<n-loading-bar-provider :to="loadingBarTargetRef" container-style="position: absolute;">
|
||||
<img
|
||||
ref="loadingBarTargetRef"
|
||||
style="width: 100px"
|
||||
:src="codeBase64"
|
||||
@click="refreshCode"
|
||||
loading="lazy"
|
||||
alt="点击获取"
|
||||
/>
|
||||
<loading-bar-trigger />
|
||||
</n-loading-bar-provider>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<template v-if="mode === 'mobile'">
|
||||
<n-form-item path="mobile">
|
||||
<n-input
|
||||
@keyup.enter="handleMobileSubmit"
|
||||
v-model:value="formMobile.mobile"
|
||||
placeholder="请输入手机号码"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<MobileOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="code">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
@keyup.enter="handleMobileSubmit"
|
||||
v-model:value="formMobile.code"
|
||||
placeholder="请输入验证码"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695" :component="SafetyCertificateOutlined" />
|
||||
</template>
|
||||
</n-input>
|
||||
<n-button
|
||||
type="primary"
|
||||
ghost
|
||||
@click="sendMobileCode"
|
||||
:disabled="isCounting"
|
||||
:loading="sendLoading"
|
||||
>
|
||||
{{ sendLabel }}
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<n-space :vertical="true" :size="24">
|
||||
<div class="flex-y-center justify-between">
|
||||
<n-checkbox v-model:checked="autoLogin">自动登录</n-checkbox>
|
||||
<n-button :text="true" @click="handleResetPassword">忘记密码?</n-button>
|
||||
</div>
|
||||
<n-button type="primary" size="large" :block="true" :loading="loading" @click="handleLogin">
|
||||
确定
|
||||
</n-button>
|
||||
|
||||
<FormOther moduleKey="register" tag="注册账号" @updateActiveModule="updateActiveModule" />
|
||||
</n-space>
|
||||
|
||||
<DemoAccount @login="handleDemoAccountLogin" />
|
||||
</n-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import '../components/style.less';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useMessage, useLoadingBar } from 'naive-ui';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import { PersonOutline, LockClosedOutline } from '@vicons/ionicons5';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
import { SafetyCertificateOutlined, MobileOutlined } from '@vicons/antd';
|
||||
import { GetCaptcha } from '@/api/base';
|
||||
import { aesEcb } from '@/utils/encrypt';
|
||||
import DemoAccount from './demo-account.vue';
|
||||
import FormOther from '../components/form-other.vue';
|
||||
import { useSendCode } from '@/hooks/common';
|
||||
import { SendSms } from '@/api/system/user';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
|
||||
interface Props {
|
||||
mode: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
mode: 'account',
|
||||
});
|
||||
|
||||
interface FormState {
|
||||
username: string;
|
||||
pass: string;
|
||||
cid: string;
|
||||
code: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface FormMobileState {
|
||||
mobile: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
const formRef = ref();
|
||||
const message = useMessage();
|
||||
const loading = ref(false);
|
||||
const autoLogin = ref(true);
|
||||
const codeBase64 = ref('');
|
||||
const loadingBar = useLoadingBar();
|
||||
const loadingBarTargetRef = ref<undefined | HTMLElement>(undefined);
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { sendLabel, isCounting, loading: sendLoading, activateSend } = useSendCode();
|
||||
const emit = defineEmits(['updateActiveModule']);
|
||||
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
||||
|
||||
const formInline = ref<FormState>({
|
||||
username: '',
|
||||
pass: '',
|
||||
cid: '',
|
||||
code: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
const formMobile = ref<FormMobileState>({
|
||||
mobile: '',
|
||||
code: '',
|
||||
});
|
||||
|
||||
const rules = {
|
||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
pass: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||
};
|
||||
|
||||
const mobileRules = {
|
||||
mobile: { required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
code: { required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
if (userStore.loginConfig?.loginCaptchaSwitch === 1 && formInline.value.code === '') {
|
||||
message.error('请输入验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {
|
||||
username: formInline.value.username,
|
||||
password: aesEcb.encrypt(formInline.value.pass),
|
||||
cid: formInline.value.cid,
|
||||
code: formInline.value.code,
|
||||
};
|
||||
await handleLoginResp(userStore.login(params));
|
||||
} else {
|
||||
message.error('请填写完整信息,并且进行验证码校验');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function refreshCode() {
|
||||
if (userStore.loginConfig?.loginCaptchaSwitch !== 1) {
|
||||
return;
|
||||
}
|
||||
loadingBar.start();
|
||||
const data = await GetCaptcha();
|
||||
codeBase64.value = data.base64;
|
||||
formInline.value.cid = data.cid;
|
||||
formInline.value.code = '';
|
||||
loadingBar.finish();
|
||||
}
|
||||
|
||||
async function handleDemoAccountLogin(user: { username: string; password: string }) {
|
||||
const params = {
|
||||
username: user.username,
|
||||
password: aesEcb.encrypt(user.password),
|
||||
isLock: true,
|
||||
};
|
||||
await handleLoginResp(userStore.login(params));
|
||||
}
|
||||
|
||||
const handleMobileSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
const params = {
|
||||
mobile: formMobile.value.mobile,
|
||||
code: formMobile.value.code,
|
||||
};
|
||||
await handleLoginResp(userStore.mobileLogin(params));
|
||||
} else {
|
||||
message.error('请填写完整信息,并且进行验证码校验');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function updateActiveModule(key: string) {
|
||||
emit('updateActiveModule', key);
|
||||
}
|
||||
|
||||
function sendMobileCode() {
|
||||
validate.phone(mobileRules.mobile, formMobile.value.mobile, function (error?: Error) {
|
||||
if (error === undefined) {
|
||||
activateSend(SendSms({ mobile: formMobile.value.mobile, event: 'login' }));
|
||||
return;
|
||||
}
|
||||
message.error(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function handleResetPassword() {
|
||||
message.info('如果你忘记了密码,请联系管理员找回');
|
||||
}
|
||||
|
||||
function handleLogin(e) {
|
||||
if (props.mode === 'account') {
|
||||
handleSubmit(e);
|
||||
return;
|
||||
}
|
||||
|
||||
handleMobileSubmit(e);
|
||||
}
|
||||
|
||||
async function handleLoginResp(request: Promise<any>) {
|
||||
message.loading('登录中...');
|
||||
loading.value = true;
|
||||
try {
|
||||
const { code, message: msg } = await request;
|
||||
message.destroyAll();
|
||||
if (code == ResultEnum.SUCCESS) {
|
||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
||||
message.success('登录成功,即将进入系统');
|
||||
if (route.name === LOGIN_NAME) {
|
||||
await router.replace('/');
|
||||
} else await router.replace(toPath);
|
||||
} else {
|
||||
message.destroyAll();
|
||||
message.info(msg || '登录失败');
|
||||
await refreshCode();
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(function () {
|
||||
refreshCode();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
22
web/src/views/login/login/index.vue
Normal file
22
web/src/views/login/login/index.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<n-tabs type="segment" justify-content="space-evenly">
|
||||
<n-tab-pane name="account" tab="账号登录">
|
||||
<Form @updateActiveModule="updateActiveModule" mode="account" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="mobile" tab="手机号登录">
|
||||
<Form @updateActiveModule="updateActiveModule" mode="mobile" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Form from './form.vue';
|
||||
|
||||
const emit = defineEmits(['updateActiveModule']);
|
||||
|
||||
function updateActiveModule(key: string) {
|
||||
emit('updateActiveModule', key);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
53
web/src/views/login/register/agreement.vue
Normal file
53
web/src/views/login/register/agreement.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-checkbox v-model:checked="checked" class="text-14px">我已阅读并接受</n-checkbox>
|
||||
<n-button :text="true" type="primary" @click="handleClickProtocol" class="text-13px"
|
||||
>《用户协议》</n-button
|
||||
>
|
||||
<n-button :text="true" type="primary" @click="handleClickPolicy" class="text-13px"
|
||||
>《隐私权政策》</n-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface Props {
|
||||
value?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: true,
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:value', value: boolean): void;
|
||||
(e: 'click-protocol'): void;
|
||||
(e: 'click-policy'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const checked = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(newValue: boolean) {
|
||||
emit('update:value', newValue);
|
||||
},
|
||||
});
|
||||
|
||||
function handleClickProtocol() {
|
||||
emit('click-protocol');
|
||||
}
|
||||
function handleClickPolicy() {
|
||||
emit('click-policy');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-14px {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
278
web/src/views/login/register/index.vue
Normal file
278
web/src/views/login/register/index.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<n-form ref="formRef" label-placement="left" size="large" :model="formInline" :rules="rules">
|
||||
<n-form-item path="username">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.username"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<PersonOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="pass">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.pass"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
show-password-on="click"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<LockClosedOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="confirmPwd">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.confirmPwd"
|
||||
type="password"
|
||||
placeholder="再次输入密码"
|
||||
show-password-on="click"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<LockClosedOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="mobile">
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.mobile"
|
||||
placeholder="请输入手机号码"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<MobileOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="code">
|
||||
<n-input-group>
|
||||
<n-input
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.code"
|
||||
placeholder="请输入验证码"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695" :component="SafetyCertificateOutlined" />
|
||||
</template>
|
||||
</n-input>
|
||||
<n-button
|
||||
type="primary"
|
||||
ghost
|
||||
@click="sendMobileCode"
|
||||
:disabled="isCounting"
|
||||
:loading="sendLoading"
|
||||
>
|
||||
{{ sendLabel }}
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="inviteCode">
|
||||
<n-input
|
||||
:style="{ width: '100%' }"
|
||||
placeholder="邀请码(选填)"
|
||||
@keyup.enter="handleSubmit"
|
||||
v-model:value="formInline.inviteCode"
|
||||
:disabled="inviteCodeDisabled"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695" :component="TagOutlined" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item class="default-color">
|
||||
<Agreement
|
||||
v-model:value="agreement"
|
||||
@clickProtocol="handleClickProtocol"
|
||||
@clickPolicy="handleClickPolicy"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="primary" @click="handleSubmit" size="large" :loading="loading" block>
|
||||
注册
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
|
||||
<FormOther moduleKey="login" tag="登录账号" @updateActiveModule="updateActiveModule" />
|
||||
</n-form>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
:mask-closable="false"
|
||||
preset="dialog"
|
||||
:closable="false"
|
||||
:title="modalTitle"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
position: 'top',
|
||||
bottom: '15vw',
|
||||
}"
|
||||
>
|
||||
<div v-html="modalContent"></div>
|
||||
|
||||
<n-divider />
|
||||
<n-space justify="center">
|
||||
<n-button type="info" ghost strong @click="handleAgreement(true)">我已知晓并接受</n-button>
|
||||
<n-button type="error" ghost strong @click="handleAgreement(false)">我拒绝</n-button>
|
||||
</n-space>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import '../components/style.less';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import { PersonOutline, LockClosedOutline } from '@vicons/ionicons5';
|
||||
import { SafetyCertificateOutlined, MobileOutlined, TagOutlined } from '@vicons/antd';
|
||||
import { aesEcb } from '@/utils/encrypt';
|
||||
import Agreement from './agreement.vue';
|
||||
import FormOther from '../components/form-other.vue';
|
||||
import { useSendCode } from '@/hooks/common';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { register, SendSms } from '@/api/system/user';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
interface FormState {
|
||||
username: string;
|
||||
pass: string;
|
||||
confirmPwd: string;
|
||||
mobile: string;
|
||||
code: string;
|
||||
inviteCode: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const formRef = ref();
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
const userStore = useUserStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const modalTitle = ref('');
|
||||
const modalContent = ref('');
|
||||
const { sendLabel, isCounting, loading: sendLoading, activateSend } = useSendCode();
|
||||
const agreement = ref(false);
|
||||
const inviteCodeDisabled = ref(false);
|
||||
const dialogWidth = ref('85%');
|
||||
const emit = defineEmits(['updateActiveModule']);
|
||||
|
||||
const formInline = ref<FormState>({
|
||||
username: '',
|
||||
pass: '',
|
||||
confirmPwd: '',
|
||||
mobile: '',
|
||||
code: '',
|
||||
inviteCode: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
const rules = {
|
||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
pass: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||
mobile: { required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
code: { required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
if (formInline.value.pass !== formInline.value.confirmPwd) {
|
||||
message.info('两次输入的密码不一致,请检查');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!agreement.value) {
|
||||
message.info('请确认你已经仔细阅读并接受《用户协议》和《隐私权政策》并已勾选接受选项');
|
||||
return;
|
||||
}
|
||||
|
||||
message.loading('注册中...');
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const { code, message: msg } = await register({
|
||||
username: formInline.value.username,
|
||||
password: aesEcb.encrypt(formInline.value.pass),
|
||||
mobile: formInline.value.mobile,
|
||||
code: formInline.value.code,
|
||||
inviteCode: formInline.value.inviteCode,
|
||||
});
|
||||
message.destroyAll();
|
||||
if (code == ResultEnum.SUCCESS) {
|
||||
message.success('注册成功,请登录!');
|
||||
updateActiveModule('login');
|
||||
} else {
|
||||
message.info(msg || '注册失败');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
message.error('请填写完整信息,并且进行验证码校验');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const inviteCode = router.currentRoute.value.query?.inviteCode as string;
|
||||
if (inviteCode) {
|
||||
inviteCodeDisabled.value = true;
|
||||
formInline.value.inviteCode = inviteCode;
|
||||
}
|
||||
|
||||
adaModalWidth(dialogWidth);
|
||||
});
|
||||
|
||||
function updateActiveModule(key: string) {
|
||||
emit('updateActiveModule', key);
|
||||
}
|
||||
|
||||
function sendMobileCode() {
|
||||
validate.phone(rules.mobile, formInline.value.mobile, function (error?: Error) {
|
||||
if (error === undefined) {
|
||||
activateSend(SendSms({ mobile: formInline.value.mobile, event: 'register' }));
|
||||
return;
|
||||
}
|
||||
message.error(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function handleClickProtocol() {
|
||||
showModal.value = true;
|
||||
modalTitle.value = '用户协议';
|
||||
modalContent.value = userStore.loginConfig?.loginProtocol as string;
|
||||
}
|
||||
function handleClickPolicy() {
|
||||
showModal.value = true;
|
||||
modalTitle.value = '隐私权政策';
|
||||
modalContent.value = userStore.loginConfig?.loginPolicy as string;
|
||||
}
|
||||
|
||||
function handleAgreement(agree: boolean) {
|
||||
showModal.value = false;
|
||||
agreement.value = agree;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -52,7 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { HardwareChip, Bookmark, AppsSharp, PieChart, Analytics } from '@vicons/ionicons5';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -79,12 +79,7 @@
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
// const loading = ref(true);
|
||||
// setTimeout(() => {
|
||||
// loading.value = false;
|
||||
// }, 1000);
|
||||
return {
|
||||
// loading,
|
||||
Bookmark,
|
||||
AppsSharp,
|
||||
PieChart,
|
||||
|
||||
@@ -102,11 +102,11 @@
|
||||
down: '0',
|
||||
up: '0',
|
||||
});
|
||||
const s = ref([]);
|
||||
const x = ref([]);
|
||||
const s = ref<any>([]);
|
||||
const x = ref<any>([]);
|
||||
const sName = ref('上行宽带');
|
||||
const xName = ref('下行宽带');
|
||||
const months = ref([]);
|
||||
const months = ref<any>([]);
|
||||
const option = ref({
|
||||
title: {
|
||||
subtext: '单位:KB',
|
||||
@@ -198,14 +198,14 @@
|
||||
});
|
||||
|
||||
const fullYearSalesChart = ref<HTMLDivElement | null>(null);
|
||||
watch(props, (_newVal, _oldVal) => {
|
||||
last.value = _newVal.dataModel[_newVal.dataModel.length - 1];
|
||||
|
||||
watch(props, (newVal, _oldVal) => {
|
||||
last.value = newVal.dataModel[newVal.dataModel.length - 1];
|
||||
if (months.value.length < 10) {
|
||||
for (let i = 0; i < _newVal.dataModel?.length; i++) {
|
||||
s.value.push(_newVal.dataModel[i].up);
|
||||
x.value.push(_newVal.dataModel[i].down);
|
||||
months.value.push(_newVal.dataModel[i].time);
|
||||
for (let i = 0; i < newVal.dataModel?.length; i++) {
|
||||
const v : any = newVal.dataModel[i]
|
||||
s.value.push(v.up);
|
||||
x.value.push(v.down);
|
||||
months.value.push(v.time);
|
||||
}
|
||||
} else {
|
||||
s.value.shift();
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const data = ref([]);
|
||||
const data = ref<any>([]);
|
||||
const option = ref({
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
@@ -76,9 +76,10 @@
|
||||
const orderChartWrapper = ref<HTMLDivElement | null>(null);
|
||||
const init = () => {
|
||||
for (let i = 0; i < props.dataModel?.length; i++) {
|
||||
const v : any = props.dataModel[i]
|
||||
data.value.push({
|
||||
name: 'CPU分钟负载比率',
|
||||
value: [props.dataModel[i]?.time, props.dataModel[i]?.ratio],
|
||||
value: [v?.time, v?.ratio],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,8 +104,8 @@
|
||||
onBeforeUnmount(() => {
|
||||
dispose(orderChartWrapper.value as HTMLDivElement);
|
||||
});
|
||||
watch(props, (_newVal, _oldVal) => {
|
||||
let last = _newVal.dataModel[_newVal.dataModel.length - 1];
|
||||
watch(props, (newVal, _oldVal) => {
|
||||
let last : any = newVal.dataModel[newVal.dataModel.length - 1];
|
||||
data.value.shift();
|
||||
data.value.push({
|
||||
name: 'CPU分钟负载比率',
|
||||
|
||||
@@ -50,6 +50,20 @@
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
type="success"
|
||||
@click="handleInviteQR(userStore.info?.inviteCode)"
|
||||
class="min-left-space"
|
||||
v-if="userStore.loginConfig?.loginRegisterSwitch === 1"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<QrCodeOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
邀请注册
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
@@ -196,25 +210,44 @@
|
||||
:showModal="showIntegralModal"
|
||||
:formParams="formParams"
|
||||
/>
|
||||
|
||||
<n-modal v-model:show="showQrModal" :show-icon="false" preset="dialog" title="邀请注册二维码">
|
||||
<n-form class="py-4">
|
||||
<div class="text-center">
|
||||
<qrcode-vue :value="qrParams.qrUrl" :size="220" class="canvas" style="margin: 0 auto" />
|
||||
</div>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showQrModal = false)">关闭</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { SelectOption, TreeSelectOption, useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { ActionItem, BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm } from '@/components/Form/index';
|
||||
import { Delete, Edit, List, Status, ResetPwd } from '@/api/org/user';
|
||||
import { columns } from './columns';
|
||||
import { PlusOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { QrCodeOutline } from '@vicons/ionicons5';
|
||||
import { sexOptions, statusOptions } from '@/enums/optionsiEnum';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { getRandomString } from '@/utils/charset';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import QrcodeVue from 'qrcode.vue';
|
||||
import AddBalance from './addBalance.vue';
|
||||
import AddIntegral from './addIntegral.vue';
|
||||
import { addNewState, addState, options, register, defaultState } from './model';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { LoginRoute } from '@/router';
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
@@ -233,6 +266,8 @@
|
||||
};
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const showIntegralModal = ref(false);
|
||||
const showBalanceModal = ref(false);
|
||||
const message = useMessage();
|
||||
@@ -246,6 +281,11 @@
|
||||
const checkedIds = ref([]);
|
||||
const dialogWidth = ref('50%');
|
||||
const formParams = ref<any>();
|
||||
const showQrModal = ref(false);
|
||||
const qrParams = ref({
|
||||
name: '',
|
||||
qrUrl: '',
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
@@ -253,6 +293,7 @@
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
const downActions = getDropDownActions(record);
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
@@ -289,23 +330,7 @@
|
||||
auth: ['/member/delete'],
|
||||
},
|
||||
],
|
||||
dropDownActions:
|
||||
record.id === 1
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: '重置密码',
|
||||
key: 0,
|
||||
},
|
||||
{
|
||||
label: '变更余额',
|
||||
key: 100,
|
||||
},
|
||||
{
|
||||
label: '变更积分',
|
||||
key: 101,
|
||||
},
|
||||
],
|
||||
dropDownActions: downActions,
|
||||
select: (key) => {
|
||||
if (key === 0) {
|
||||
return handleResetPwd(record);
|
||||
@@ -316,11 +341,48 @@
|
||||
if (key === 101) {
|
||||
return handleAddIntegral(record);
|
||||
}
|
||||
if (key === 102) {
|
||||
if (userStore.loginConfig?.loginRegisterSwitch !== 1) {
|
||||
message.error('管理员暂未开启此功能');
|
||||
return;
|
||||
}
|
||||
return handleInviteQR(record.inviteCode);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function getDropDownActions(record: Recordable): ActionItem[] {
|
||||
if (record.id === 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let list = [
|
||||
{
|
||||
label: '重置密码',
|
||||
key: 0,
|
||||
},
|
||||
{
|
||||
label: '变更余额',
|
||||
key: 100,
|
||||
},
|
||||
{
|
||||
label: '变更积分',
|
||||
key: 101,
|
||||
},
|
||||
];
|
||||
|
||||
if (userStore.loginConfig?.loginRegisterSwitch === 1) {
|
||||
list.push({
|
||||
label: 'TA的邀请码',
|
||||
key: 102,
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = cloneDeep(defaultState);
|
||||
@@ -465,6 +527,13 @@
|
||||
showIntegralModal.value = true;
|
||||
formParams.value = addNewState(record as addState);
|
||||
}
|
||||
|
||||
function handleInviteQR(code: string) {
|
||||
const w = window.location;
|
||||
const domain = w.protocol + '//' + w.host + w.pathname + '#';
|
||||
qrParams.value.qrUrl = domain + LoginRoute.path + '?scope=register&inviteCode=' + code;
|
||||
showQrModal.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -30,24 +30,6 @@
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="用户是否可注册开关" path="basicRegisterSwitch">
|
||||
<n-radio-group v-model:value="formValue.basicRegisterSwitch" name="basicRegisterSwitch">
|
||||
<n-space>
|
||||
<n-radio :value="1">开启</n-radio>
|
||||
<n-radio :value="0">关闭</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="验证码开关" path="basicCaptchaSwitch">
|
||||
<n-radio-group v-model:value="formValue.basicCaptchaSwitch" name="basicCaptchaSwitch">
|
||||
<n-space>
|
||||
<n-radio :value="1">开启</n-radio>
|
||||
<n-radio :value="0">关闭</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="网站开启访问" path="basicSystemOpen">
|
||||
<n-switch
|
||||
size="large"
|
||||
|
||||
173
web/src/views/system/config/LoginSetting.vue
Normal file
173
web/src/views/system/config/LoginSetting.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="show" description="请稍候...">
|
||||
<n-form :label-width="100" :model="formValue" :rules="rules" ref="formRef">
|
||||
<n-form-item label="登录验证码开关" path="loginCaptchaSwitch">
|
||||
<n-radio-group v-model:value="formValue.loginCaptchaSwitch" name="loginCaptchaSwitch">
|
||||
<n-space>
|
||||
<n-radio :value="1">开启</n-radio>
|
||||
<n-radio :value="2">关闭</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="注册开关" path="loginRegisterSwitch">
|
||||
<n-radio-group v-model:value="formValue.loginRegisterSwitch" name="cashSwitch">
|
||||
<n-space>
|
||||
<n-radio :value="1">开启</n-radio>
|
||||
<n-radio :value="2">关闭</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="默认注册头像" path="loginAvatar">
|
||||
<UploadImage :maxNumber="1" v-model:value="formValue.loginAvatar" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="默认注册角色" path="loginRoleId">
|
||||
<n-tree-select
|
||||
key-field="id"
|
||||
:options="options.role"
|
||||
v-model:value="formValue.loginRoleId"
|
||||
:default-expand-all="true"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="默认注册部门" path="loginDeptId">
|
||||
<n-tree-select
|
||||
key-field="id"
|
||||
:options="options.dept"
|
||||
v-model:value="formValue.loginDeptId"
|
||||
:default-expand-all="true"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="默认注册岗位" path="loginPostIds">
|
||||
<n-select v-model:value="formValue.loginPostIds" multiple :options="options.post" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="用户协议" path="loginProtocol">
|
||||
<Editor
|
||||
style="height: 320px"
|
||||
v-model:value="formValue.loginProtocol"
|
||||
id="loginProtocol"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="隐私权政策" path="loginPolicy">
|
||||
<Editor style="height: 320px" v-model:value="formValue.loginPolicy" id="loginPolicy" />
|
||||
</n-form-item>
|
||||
|
||||
<div>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="formSubmit">保存更新</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { getConfig, updateConfig } from '@/api/sys/config';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import { getDeptOption } from '@/api/org/dept';
|
||||
import { getRoleOption } from '@/api/system/role';
|
||||
import { getPostOption } from '@/api/org/post';
|
||||
import UploadImage from '@/components/Upload/uploadImage.vue';
|
||||
|
||||
const group = ref('login');
|
||||
const show = ref(false);
|
||||
const rules = {};
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const formValue = ref({
|
||||
loginRegisterSwitch: true,
|
||||
loginCaptchaSwitch: true,
|
||||
loginAvatar: '',
|
||||
loginProtocol: '',
|
||||
loginPolicy: '',
|
||||
loginRoleId: null,
|
||||
loginDeptId: null,
|
||||
loginPostIds: [],
|
||||
});
|
||||
|
||||
const options = ref<any>({
|
||||
role: [],
|
||||
roleTabs: [{ id: -1, name: '全部' }],
|
||||
dept: [],
|
||||
post: [],
|
||||
});
|
||||
|
||||
async function loadOptions() {
|
||||
const dept = await getDeptOption();
|
||||
if (dept.list !== undefined) {
|
||||
options.value.dept = dept.list;
|
||||
}
|
||||
|
||||
const role = await getRoleOption();
|
||||
if (role.list !== undefined) {
|
||||
options.value.role = role.list;
|
||||
treeDataToCompressed(role.list);
|
||||
}
|
||||
|
||||
const post = await getPostOption();
|
||||
if (post.list !== undefined && post.list.length > 0) {
|
||||
for (let i = 0; i < post.list.length; i++) {
|
||||
post.list[i].label = post.list[i].name;
|
||||
post.list[i].value = post.list[i].id;
|
||||
}
|
||||
options.value.post = post.list;
|
||||
}
|
||||
}
|
||||
|
||||
function treeDataToCompressed(source) {
|
||||
for (const i in source) {
|
||||
options.value.roleTabs.push(source[i]);
|
||||
source[i].children && source[i].children.length > 0
|
||||
? treeDataToCompressed(source[i].children)
|
||||
: ''; // 子级递归
|
||||
}
|
||||
|
||||
return options.value.roleTabs;
|
||||
}
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
updateConfig({ group: group.value, list: formValue.value })
|
||||
.then((_res) => {
|
||||
message.success('更新成功');
|
||||
load();
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error(error.toString());
|
||||
});
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadOptions();
|
||||
load();
|
||||
});
|
||||
|
||||
function load() {
|
||||
show.value = true;
|
||||
new Promise((_resolve, _reject) => {
|
||||
getConfig({ group: group.value })
|
||||
.then((res) => {
|
||||
show.value = false;
|
||||
formValue.value = res.list;
|
||||
})
|
||||
.catch((error) => {
|
||||
show.value = false;
|
||||
message.error(error.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -22,6 +22,7 @@
|
||||
<RevealSetting v-if="type === 3" />
|
||||
<EmailSetting v-if="type === 4" />
|
||||
<SmsSetting v-if="type === 5" />
|
||||
<LoginSetting v-if="type === 6" />
|
||||
<CashSetting v-if="type === 7" />
|
||||
<UploadSetting v-if="type === 8" />
|
||||
<GeoSetting v-if="type === 9" />
|
||||
@@ -44,6 +45,7 @@
|
||||
import SmsSetting from './SmsSetting.vue';
|
||||
import PaySetting from './PaySetting.vue';
|
||||
import WechatSetting from './WechatSetting.vue';
|
||||
import LoginSetting from './LoginSetting.vue';
|
||||
const typeTabList = [
|
||||
{
|
||||
name: '基本设置',
|
||||
@@ -70,11 +72,11 @@
|
||||
desc: '短信验证码平台',
|
||||
key: 5,
|
||||
},
|
||||
// {
|
||||
// name: '管理员配置',
|
||||
// desc: '默认设置和权限屏蔽',
|
||||
// key: 6,
|
||||
// },
|
||||
{
|
||||
name: '登录注册',
|
||||
desc: '登录注册配置',
|
||||
key: 6,
|
||||
},
|
||||
{
|
||||
name: '提现配置',
|
||||
desc: '管理员提现规则配置',
|
||||
@@ -113,6 +115,7 @@
|
||||
SmsSetting,
|
||||
PaySetting,
|
||||
WechatSetting,
|
||||
LoginSetting,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
|
||||
Reference in New Issue
Block a user