登录、字体图标组件、菜单管理
This commit is contained in:
parent
bb1a41fb9a
commit
3a7c381580
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-footer">
|
<div class="page-footer">
|
||||||
<div class="page-footer-link">
|
<div class="page-footer-link">
|
||||||
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 官网 </a>
|
<a href="https://www.baidu.com" target="_blank"> 官网 </a>
|
||||||
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 社区 </a>
|
<a href="https://www.baidu.com" target="_blank"> 社区 </a>
|
||||||
<a href="https://github.com/jekip/naive-ui-admin/issues" target="_blank"> 交流 </a>
|
<a href="https://www.baidu.com" target="_blank"> 交流 </a>
|
||||||
</div>
|
</div>
|
||||||
<div class="copyright"> naive-ui-admin 1.4 · Made by Ah jung </div>
|
<div class="copyright">Copyright © 2024 南京云恒信息技术有限公司</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.page-footer {
|
.page-footer {
|
||||||
//margin: 28px 0 24px 0;
|
margin: 20px 0 10px 0;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
<div class="admin-layout-content-main">
|
<div class="admin-layout-content-main">
|
||||||
<div class="main-view" ref="adminBodyRef">
|
<div class="main-view" ref="adminBodyRef">
|
||||||
<MainView />
|
<MainView />
|
||||||
|
<PageFooter/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<n-back-top :right="100" />
|
<n-back-top :right="100" />
|
||||||
@ -79,6 +80,7 @@
|
|||||||
import { MainView } from './components/Main';
|
import { MainView } from './components/Main';
|
||||||
import { AsideMenu } from './components/Menu';
|
import { AsideMenu } from './components/Menu';
|
||||||
import { PageHeader } from './components/Header';
|
import { PageHeader } from './components/Header';
|
||||||
|
import { PageFooter } from './components/Footer';
|
||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
@ -4,10 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
import { PageWrapper, PageFooter } from '@/components/Page';
|
import { PageWrapper, PageFooter } from '@/components/Page';
|
||||||
|
import { basicModal } from '@/components/Modal';
|
||||||
import { Authority } from '@/components/Authority';
|
import { Authority } from '@/components/Authority';
|
||||||
|
|
||||||
export function setupCustomComponents(app: App) {
|
export function setupCustomComponents(app: App) {
|
||||||
app.component('PageWrapper', PageWrapper);
|
app.component('PageWrapper', PageWrapper);
|
||||||
|
app.component('basicModal', basicModal);
|
||||||
app.component('PageFooter', PageFooter);
|
app.component('PageFooter', PageFooter);
|
||||||
app.component('Authority', Authority);
|
app.component('Authority', Authority);
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
:rules="rules"
|
:rules="rules"
|
||||||
>
|
>
|
||||||
<n-form-item path="username">
|
<n-form-item path="username">
|
||||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
<n-input v-model:value="formInline.username" placeholder="请输入登录账号">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<PersonOutline />
|
<UserOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
@ -19,58 +19,71 @@
|
|||||||
<n-form-item path="password">
|
<n-form-item path="password">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="formInline.password"
|
v-model:value="formInline.password"
|
||||||
|
show-password
|
||||||
type="password"
|
type="password"
|
||||||
showPasswordOn="click"
|
placeholder="请输入登录密码"
|
||||||
placeholder="请输入密码"
|
|
||||||
@keyup.enter="handleSubmit"
|
@keyup.enter="handleSubmit"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<LockClosedOutline />
|
<LockOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<div class="mb-6 default-color">
|
<n-form-item path="code">
|
||||||
<div class="flex justify-between">
|
<div style="display: flex">
|
||||||
|
<n-input
|
||||||
|
@keyup.enter="handleSubmit"
|
||||||
|
v-model:value.trim="formInline.code"
|
||||||
|
placeholder="验证码"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon size="18" color="#808695">
|
||||||
|
<SafetyCertificateOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
<img
|
||||||
|
style="
|
||||||
|
width: 108px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
cursor: pointer;
|
||||||
|
"
|
||||||
|
@click="getCaptcha"
|
||||||
|
v-if="captchaImg"
|
||||||
|
:src="captchaImg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between forget">
|
||||||
<div class="flex-initial">
|
<div class="flex-initial">
|
||||||
<n-checkbox v-model:checked="autoLogin">自动登录</n-checkbox>
|
<n-checkbox v-model:value:checked="autoLogin">记住密码</n-checkbox>
|
||||||
</div>
|
|
||||||
<div class="flex-initial order-last">
|
|
||||||
<a href="javascript:">忘记密码</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-form-item :show-label="false">
|
<n-form-item :show-label="false">
|
||||||
<n-button type="primary" @click="handleSubmit" size="large" :loading="loading" block>
|
<n-button class="w-full" type="primary" @click="handleSubmit" size="large" :loading="loading">
|
||||||
登录
|
登录
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<div class="mb-4 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">
|
|
||||||
<LogoGithub />
|
|
||||||
</n-icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex-initial mx-2">
|
|
||||||
<a href="javascript:">
|
|
||||||
<n-icon size="24" color="#2d8cf0">
|
|
||||||
<LogoFacebook />
|
|
||||||
</n-icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex-initial" style="margin-left: auto">
|
|
||||||
<a href="javascript:" @click="goRegister">注册账号</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-form>
|
</n-form>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span>其他登录方式</span>
|
||||||
|
<n-icon size="20" color="#1890ff" style="cursor: pointer">
|
||||||
|
<GithubOutlined />
|
||||||
|
</n-icon>
|
||||||
|
<n-icon size="20" color="#1890ff" style="cursor: pointer">
|
||||||
|
<AlipayCircleOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
<div style="cursor: pointer" @click="goRegister">注册账号</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -79,51 +92,66 @@
|
|||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { FormRules, useMessage } from 'naive-ui';
|
import { FormRules, useMessage } from 'naive-ui';
|
||||||
import { ResultEnum } from '@/enums/httpEnum';
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook } from '@vicons/ionicons5';
|
import { getInfoCaptcha } from '@/api/system/user';
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
|
import {
|
||||||
|
UserOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
SafetyCertificateOutlined,
|
||||||
|
GithubOutlined,
|
||||||
|
AlipayCircleOutlined,
|
||||||
|
} from '@vicons/antd';
|
||||||
|
const captchaImg = ref('');
|
||||||
|
// 动态加载滑块验证码组件
|
||||||
|
|
||||||
interface FormState {
|
interface FormState {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
code: string;
|
||||||
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formRef = ref();
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
|
const emit = defineEmits(['backLogin']);
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const autoLogin = ref(true);
|
const autoLogin = ref(true);
|
||||||
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
||||||
|
|
||||||
const formInline = reactive({
|
const formInline = reactive({
|
||||||
username: 'admin',
|
username: '',
|
||||||
password: '123456',
|
password: '',
|
||||||
|
code: '',
|
||||||
|
key: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules: FormRules = {
|
const rules: FormRules = {
|
||||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
username: { required: true, message: '请输入登录账号', trigger: 'blur' },
|
||||||
password: { required: true, message: '请输入密码', trigger: 'blur' },
|
password: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
|
code: { required: true, message: '请输入验证码', trigger: 'blur' },
|
||||||
};
|
};
|
||||||
const emit = defineEmits(['goRegister']);
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = () => {
|
||||||
e.preventDefault();
|
if (!formRef.value) return;
|
||||||
formRef.value.validate(async (errors) => {
|
formRef.value
|
||||||
if (!errors) {
|
.validate()
|
||||||
const { username, password } = formInline;
|
.then(async () => {
|
||||||
message.loading('登录中...');
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
const params: FormState = {
|
const params: FormState = {
|
||||||
username,
|
username: formInline.username,
|
||||||
password,
|
password: formInline.password,
|
||||||
|
code: formInline.code,
|
||||||
|
key: formInline.key,
|
||||||
|
// grant_type:"password"
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { code, message: msg } = await userStore.login(params);
|
const { code, msg } = await userStore.login(params);
|
||||||
message.destroyAll();
|
|
||||||
if (code == ResultEnum.SUCCESS) {
|
if (code == ResultEnum.SUCCESS) {
|
||||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
||||||
message.success('登录成功,即将进入系统');
|
message.success('登录成功,即将进入系统');
|
||||||
@ -131,18 +159,43 @@
|
|||||||
router.replace('/');
|
router.replace('/');
|
||||||
} else router.replace(toPath);
|
} else router.replace(toPath);
|
||||||
} else {
|
} else {
|
||||||
message.info(msg || '登录失败');
|
getCaptcha();
|
||||||
|
message.error(msg || '登录失败');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
|
.catch((error) => {
|
||||||
message.error('请填写完整信息');
|
message.error('请填写完整信息');
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function goRegister() {
|
const getCaptcha = async () => {
|
||||||
emit('goRegister');
|
let { key, captcha } = await getInfoCaptcha();
|
||||||
}
|
formInline.key = key;
|
||||||
|
captchaImg.value = captcha;
|
||||||
|
};
|
||||||
|
const goRegister = () => {
|
||||||
|
emit('backLogin', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
getCaptcha();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.forget {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less">
|
||||||
|
input:-webkit-autofill,
|
||||||
|
textarea:-webkit-autofill,
|
||||||
|
select:-webkit-autofill {
|
||||||
|
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image: none;
|
||||||
|
transition: background-color 50000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
169
src/views/login/PhoneForm.vue
Normal file
169
src/views/login/PhoneForm.vue
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<n-form
|
||||||
|
ref="formRef"
|
||||||
|
:show-label="false"
|
||||||
|
:show-require-mark="false"
|
||||||
|
size="large"
|
||||||
|
:model="formInline"
|
||||||
|
:rules="rules"
|
||||||
|
>
|
||||||
|
<n-form-item path="mobile">
|
||||||
|
<n-input 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
|
||||||
|
@keyup.enter="handleSubmit"
|
||||||
|
v-model:value.trim="formInline.code"
|
||||||
|
placeholder="验证码"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<n-icon size="18" color="#808695">
|
||||||
|
<SafetyCertificateOutlined/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
<n-button quaternary :disabled="isGetCode" @click="getCode">
|
||||||
|
{{ codeMsg }}<span v-if="isGetCode">s</span>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :show-label="false">
|
||||||
|
<n-button class="w-full" type="primary" @click="handleSubmit" size="large" :loading="loading">
|
||||||
|
登录
|
||||||
|
</n-button>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
import { FormRules, useMessage } from 'naive-ui';
|
||||||
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
|
import { getInfoCaptcha } from '@/api/system/user';
|
||||||
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
|
import { MobileOutlined, SafetyCertificateOutlined } from '@vicons/antd';
|
||||||
|
const captchaImg = ref('');
|
||||||
|
// 动态加载滑块验证码组件
|
||||||
|
|
||||||
|
interface FormState {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
code: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const message = useMessage();
|
||||||
|
const codeMsg: any = ref('获取验证码');
|
||||||
|
const isGetCode = ref(false);
|
||||||
|
const autoLogin = ref(true);
|
||||||
|
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
||||||
|
|
||||||
|
const formInline = reactive({
|
||||||
|
mobile: '',
|
||||||
|
code: '',
|
||||||
|
key: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules:FormRules = {
|
||||||
|
mobile: { key:'a',required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||||
|
code: { required: true, message: '请输入验证码', trigger: 'blur' },
|
||||||
|
};
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
function getCode() {
|
||||||
|
if (!formInline.mobile) {
|
||||||
|
formRef.value?.validate(
|
||||||
|
(errors) => {
|
||||||
|
},
|
||||||
|
(rule) => {
|
||||||
|
return rule?.key === 'a'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
codeMsg.value = 60;
|
||||||
|
isGetCode.value = true;
|
||||||
|
let time = setInterval(() => {
|
||||||
|
codeMsg.value--;
|
||||||
|
if (codeMsg.value <= 0) {
|
||||||
|
clearInterval(time);
|
||||||
|
codeMsg.value = '获取验证码';
|
||||||
|
isGetCode.value = false;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!formRef.value) return;
|
||||||
|
formRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
const params: FormState = {
|
||||||
|
username: formInline.username,
|
||||||
|
password: formInline.password,
|
||||||
|
code: formInline.code,
|
||||||
|
key: formInline.key,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { code, msg } = await userStore.login(params);
|
||||||
|
if (code == ResultEnum.SUCCESS) {
|
||||||
|
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
||||||
|
message.success('登录成功,即将进入系统');
|
||||||
|
if (route.name === LOGIN_NAME) {
|
||||||
|
router.replace('/');
|
||||||
|
} else router.replace(toPath);
|
||||||
|
} else {
|
||||||
|
message.error(msg || '登录失败');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message({
|
||||||
|
message: '请填写完整信息',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCaptcha = async () => {
|
||||||
|
let { key, captcha } = await getInfoCaptcha();
|
||||||
|
formInline.key = key;
|
||||||
|
captchaImg.value = captcha;
|
||||||
|
};
|
||||||
|
|
||||||
|
getCaptcha();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.forget {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less">
|
||||||
|
input:-webkit-autofill,
|
||||||
|
textarea:-webkit-autofill,
|
||||||
|
select:-webkit-autofill {
|
||||||
|
-webkit-box-shadow: 0 0 0px 1000px transparent inset !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image: none;
|
||||||
|
transition: background-color 50000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
</style>
|
41
src/views/login/QrcodeForm.vue
Normal file
41
src/views/login/QrcodeForm.vue
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<div class="qrCode-box">
|
||||||
|
<div class="qr-img">
|
||||||
|
<QrCode :value="qrCodeUrl" :width="160" :options="{ margin: 0 }" />
|
||||||
|
</div>
|
||||||
|
<div class="qr-text">
|
||||||
|
<el-icon :size="18"><RefreshRight /></el-icon>刷新二维码
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { QrCode } from '@/components/Qrcode/index';
|
||||||
|
const qrCodeUrl = 'https://www.baidu.com';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.qrCode-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 24px;
|
||||||
|
.qr-img {
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.qr-text {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
> i {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,106 +1,84 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-form
|
<n-form ref="formRef" :show-label="false" :show-require-mark="false" size="large" :model="formInline" :rules="rules"
|
||||||
ref="formRef"
|
class="register-form">
|
||||||
:show-label="false"
|
|
||||||
:show-require-mark="false"
|
|
||||||
size="large"
|
|
||||||
:model="formInline"
|
|
||||||
:rules="rules"
|
|
||||||
>
|
|
||||||
<n-form-item path="username">
|
<n-form-item path="username">
|
||||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<PersonOutline />
|
<UserOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item path="mobile">
|
<n-form-item path="mobile">
|
||||||
<div class="flex w-full">
|
<n-input v-model:value="formInline.mobile" placeholder="请输入手机号码">
|
||||||
<n-input class="order-first" v-model:value="formInline.mobile" placeholder="请输入手机号码">
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<MobileOutlined />
|
<MobileOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
<n-button class="order-last ml-3" :disabled="isGetCode" @click="getCode"
|
|
||||||
>{{ codeMsg }}<span v-if="isGetCode">s</span>
|
|
||||||
</n-button>
|
|
||||||
</div>
|
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item path="code">
|
<n-form-item path="code">
|
||||||
<n-input v-model:value="formInline.code" placeholder="请输入验证码">
|
<n-input v-model:value.trim="formInline.code" placeholder="验证码">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<SafetyOutlined />
|
<SafetyCertificateOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
|
<template #suffix>
|
||||||
|
<n-button quaternary :disabled="isGetCode" @click="getCode">
|
||||||
|
{{ codeMsg }}<span v-if="isGetCode">s</span>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item path="password">
|
<n-form-item path="password">
|
||||||
<n-input
|
<n-input v-model:value="formInline.password" type="password" show-password placeholder="请输入密码">
|
||||||
v-model:value="formInline.password"
|
|
||||||
type="password"
|
|
||||||
showPasswordOn="click"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<LockClosedOutline />
|
<LockOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item path="retPassword">
|
<n-form-item path="retPassword">
|
||||||
<n-input
|
<n-input v-model:value="formInline.retPassword" type="password" show-password placeholder="请再次输入密码">
|
||||||
v-model:value="formInline.retPassword"
|
|
||||||
type="password"
|
|
||||||
showPasswordOn="click"
|
|
||||||
placeholder="请再次输入密码"
|
|
||||||
@keyup.enter="handleSubmit"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<LockClosedOutline />
|
<LockOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
<n-button class="w-full" type="primary" @click="handleSubmit" size="large" :loading="loading">
|
||||||
|
注册
|
||||||
|
</n-button>
|
||||||
<n-form-item class="default-color" path="agreement">
|
<n-form-item class="default-color" path="agreement">
|
||||||
<div class="flex justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex-initial">
|
<div class="flex-initial">
|
||||||
<n-checkbox v-model:checked="formInline.agreement">我同意隐私协议</n-checkbox>
|
<n-checkbox v-model:checked="formInline.agreement">我同意隐私协议</n-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<n-form-item :show-label="false">
|
|
||||||
<n-button type="primary" @click="handleSubmit" size="large" :loading="loading" block>
|
|
||||||
注册
|
|
||||||
</n-button>
|
|
||||||
</n-form-item>
|
|
||||||
|
|
||||||
<n-form-item :show-label="false">
|
|
||||||
<n-button size="large" block @click="backLogin">返回</n-button>
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
</n-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { FormRules, useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import { PersonOutline, LockClosedOutline } from '@vicons/ionicons5';
|
import { rule } from '@/utils/validate';
|
||||||
import { MobileOutlined, SafetyOutlined } from '@vicons/antd';
|
import {
|
||||||
import { isNumber } from '@/utils/is';
|
UserOutlined,
|
||||||
|
MobileOutlined,
|
||||||
|
SafetyCertificateOutlined,
|
||||||
|
LockOutlined,
|
||||||
|
} from '@vicons/antd';
|
||||||
|
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const codeMsg = ref<number | string>('获取验证码');
|
const codeMsg: any = ref('获取验证码');
|
||||||
const isGetCode = ref(false);
|
const isGetCode = ref(false);
|
||||||
|
|
||||||
const emit = defineEmits(['backLogin']);
|
const emit = defineEmits(['backLogin']);
|
||||||
@ -114,9 +92,19 @@
|
|||||||
agreement: false,
|
agreement: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules: FormRules = {
|
const validatePhone = async (_rule, value: string) => {
|
||||||
|
var isPhone = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject('请输入手机号');
|
||||||
|
} else if (!isPhone.test(value)) {
|
||||||
|
return Promise.reject('请输入合法手机号');
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rules = {
|
||||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
mobile: { required: true, message: '请输入手机号码', trigger: 'blur' },
|
mobile: [{ key: 'a', required: true, validator: validatePhone, trigger: 'blur' }],
|
||||||
code: { required: true, message: '请输入短信验证码', trigger: 'blur' },
|
code: { required: true, message: '请输入短信验证码', trigger: 'blur' },
|
||||||
password: { required: true, message: '请输入密码', trigger: 'blur' },
|
password: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||||
retPassword: { required: true, message: '请输入确认密码', trigger: 'blur' },
|
retPassword: { required: true, message: '请输入确认密码', trigger: 'blur' },
|
||||||
@ -124,33 +112,48 @@
|
|||||||
required: true,
|
required: true,
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
trigger: 'change',
|
trigger: 'change',
|
||||||
message: '请先勾选协议',
|
validator: async (_, value) => {
|
||||||
validator: (_, value) => value === true,
|
if (!value) {
|
||||||
|
return Promise.reject('请先勾选协议');
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = () => {
|
||||||
e.preventDefault();
|
formRef.value
|
||||||
formRef.value.validate(async (errors) => {
|
.validate()
|
||||||
if (!errors) {
|
.then(async () => {
|
||||||
message.success('注册准备就绪');
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
} else {
|
backLogin();
|
||||||
|
loading.value = false;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
message.error('请填写完整信息');
|
message.error('请填写完整信息');
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const backLogin = () => {
|
const backLogin = () => {
|
||||||
emit('backLogin');
|
emit('backLogin', true);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCode() {
|
function getCode() {
|
||||||
|
if (!formInline.mobile) {
|
||||||
|
formRef.value?.validate(
|
||||||
|
(errors) => {},
|
||||||
|
(rule) => {
|
||||||
|
return rule?.key === 'a'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
}
|
||||||
codeMsg.value = 60;
|
codeMsg.value = 60;
|
||||||
isGetCode.value = true;
|
isGetCode.value = true;
|
||||||
let time = setInterval(() => {
|
let time = setInterval(() => {
|
||||||
(codeMsg.value as number)--;
|
codeMsg.value--;
|
||||||
if (isNumber(codeMsg.value) && codeMsg.value <= 0) {
|
if (codeMsg.value <= 0) {
|
||||||
clearInterval(time);
|
clearInterval(time);
|
||||||
codeMsg.value = '获取验证码';
|
codeMsg.value = '获取验证码';
|
||||||
isGetCode.value = false;
|
isGetCode.value = false;
|
||||||
|
@ -1,221 +1,221 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="view-account">
|
<div class="account">
|
||||||
<div class="view-account-header"></div>
|
<div class="account-container">
|
||||||
<div class="view-account-container">
|
<div class="account-wrap-login">
|
||||||
<div class="view-account-top">
|
<div class="login-pic">
|
||||||
<div class="view-account-top-logo">
|
<h1 class="login-title">云恒WMS</h1>
|
||||||
<img src="~@/assets/images/account-logo.png" alt="" />
|
<h4 class="login-subtitle">赋能开发者,助力企业发展,全方位提供数据中台解决方案!</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-account-top-desc">Naive Admin中台前端/设计解决方案</div>
|
<div class="login-form">
|
||||||
|
<div class="login-form-container">
|
||||||
|
<div class="account-top">
|
||||||
|
<div class="account-top-desc">{{ loginFlag ? '用户登录' : '用户注册' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-account-form">
|
<template v-if="loginFlag">
|
||||||
<n-form
|
<div class="account-tab-box">
|
||||||
ref="formRef"
|
<div
|
||||||
label-placement="left"
|
:class="activeIndex == index ? 'active' : ''"
|
||||||
size="large"
|
v-for="(item, index) in tabData"
|
||||||
:model="formInline"
|
@click="handleClick(index)"
|
||||||
:rules="rules"
|
:key="index"
|
||||||
|
>{{ item }}</div
|
||||||
>
|
>
|
||||||
<n-form-item path="username">
|
</div>
|
||||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
<LoginForm v-if="activeIndex === 0" @back-login="goLogin" />
|
||||||
<template #prefix>
|
<PhoneForm v-else-if="activeIndex === 1" />
|
||||||
<n-icon size="18" color="#808695">
|
<QrcodeForm v-else-if="activeIndex === 2" />
|
||||||
<PersonOutline />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
<template v-else>
|
||||||
</n-form-item>
|
<RegisterForm @back-login="goLogin" />
|
||||||
<n-form-item path="password">
|
|
||||||
<n-input
|
|
||||||
v-model:value="formInline.password"
|
|
||||||
type="password"
|
|
||||||
showPasswordOn="click"
|
|
||||||
placeholder="请输入密码"
|
|
||||||
@keyup.enter="handleSubmit"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
|
||||||
<n-icon size="18" color="#808695">
|
|
||||||
<LockClosedOutline />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item path="isCaptcha">
|
|
||||||
<div class="w-full">
|
|
||||||
<mi-captcha width="384" theme-color="#2d8cf0" :logo="logo" @success="onAuthCode" />
|
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
<div class="corner-box" @click="handleCornerClick">
|
||||||
<n-form-item class="default-color">
|
<n-icon size="40" color="#ffffff" v-if="loginFlag">
|
||||||
<div class="flex justify-between">
|
<UserAddOutlined />
|
||||||
<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">
|
|
||||||
<LogoGithub />
|
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</a>
|
<n-icon size="40" color="#ffffff" v-else>
|
||||||
</div>
|
<ArrowUndoOutline />
|
||||||
<div class="flex-initial mx-2">
|
|
||||||
<a href="javascript:">
|
|
||||||
<n-icon size="24" color="#2d8cf0">
|
|
||||||
<LogoFacebook />
|
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="flex-initial" style="margin-left: auto">
|
|
||||||
<a href="javascript:">注册账号</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import LoginForm from './LoginForm.vue';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import PhoneForm from './PhoneForm.vue';
|
||||||
import { FormRules, useMessage } from 'naive-ui';
|
import QrcodeForm from './QrcodeForm.vue';
|
||||||
import { ResultEnum } from '@/enums/httpEnum';
|
import RegisterForm from './RegisterForm.vue';
|
||||||
import logo from '@/assets/images/logo.png';
|
import { UserAddOutlined, RollbackOutlined } from '@vicons/antd';
|
||||||
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook } from '@vicons/ionicons5';
|
import { ArrowUndoOutline } from '@vicons/ionicons5';
|
||||||
|
const loginFlag = ref(true);
|
||||||
interface FormState {
|
const activeIndex = ref(0);
|
||||||
username: string;
|
const tabData = ref(['账号登录', '手机号登录', '扫码登录']);
|
||||||
password: string;
|
const handleClick = (index) => {
|
||||||
}
|
activeIndex.value = index;
|
||||||
|
|
||||||
const formRef = ref();
|
|
||||||
const message = useMessage();
|
|
||||||
const loading = ref(false);
|
|
||||||
const autoLogin = ref(true);
|
|
||||||
|
|
||||||
const formInline = reactive({
|
|
||||||
username: 'admin',
|
|
||||||
password: '123456',
|
|
||||||
isCaptcha: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules: FormRules = {
|
|
||||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
|
||||||
password: { required: true, message: '请输入密码', trigger: 'blur' },
|
|
||||||
isCaptcha: {
|
|
||||||
required: true,
|
|
||||||
type: 'boolean',
|
|
||||||
trigger: 'change',
|
|
||||||
message: '请点击按钮进行验证码校验',
|
|
||||||
validator: (_, value) => value === true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
const handleCornerClick = () => {
|
||||||
const userStore = useUserStore();
|
loginFlag.value = !loginFlag.value;
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
formRef.value.validate(async (errors) => {
|
|
||||||
if (!errors) {
|
|
||||||
const { username, password } = formInline;
|
|
||||||
message.loading('登录中...');
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
const params: FormState = {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
};
|
};
|
||||||
|
const goLogin = (type) => {
|
||||||
const { code, message: msg } = await userStore.login(params);
|
loginFlag.value = type;
|
||||||
|
|
||||||
if (code == ResultEnum.SUCCESS) {
|
|
||||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
|
||||||
message.success('登录成功!');
|
|
||||||
router.replace(toPath).then((_) => {
|
|
||||||
if (route.name == 'login') {
|
|
||||||
router.replace('/');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
message.info(msg || '登录失败');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message.error('请填写完整信息,并且进行验证码校验');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAuthCode = () => {
|
|
||||||
formInline.isCaptcha = true;
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.view-account {
|
.account {
|
||||||
display: flex;
|
width: 100%;
|
||||||
flex-direction: column;
|
margin: 0 auto;
|
||||||
height: 100vh;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
&-container {
|
&-container {
|
||||||
flex: 1;
|
|
||||||
padding: 32px 0;
|
|
||||||
width: 384px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-top {
|
|
||||||
padding: 32px 0;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&-desc {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #808695;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-other {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
.default-color {
|
flex-wrap: wrap;
|
||||||
color: #515a6e;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
.ant-checkbox-wrapper {
|
padding: 20px;
|
||||||
color: #515a6e;
|
box-sizing: border-box;
|
||||||
}
|
background-image: url('@/assets/images/login-bg.png');
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.view-account {
|
|
||||||
background-image: url('../../assets/images/login.svg');
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 50%;
|
background-size: 100% 100%;
|
||||||
background-size: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-account-container {
|
&-wrap-login {
|
||||||
padding: 32px 0 24px 0;
|
width: 920px;
|
||||||
|
height: 510px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
.login-pic {
|
||||||
|
flex: 1;
|
||||||
|
padding: 32px 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #1681fd;
|
||||||
|
background-image: url('@/assets/images/login-img.png');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom;
|
||||||
|
background-size: contain;
|
||||||
|
text-align: center;
|
||||||
|
.login-title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 28px;
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
}
|
||||||
|
.login-subtitle {
|
||||||
|
color: #fffc;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
width: 400px;
|
||||||
|
position: relative;
|
||||||
|
&-container {
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 25px 48px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
&-title {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.corner-box {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 0 0 0 150%;
|
||||||
|
background: #1890ff;
|
||||||
|
cursor: pointer;
|
||||||
|
> .n-icon {
|
||||||
|
margin-right: -20px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.login-pic {
|
||||||
|
padding: 20px 12px 100px;
|
||||||
|
background-size: auto 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.account-top {
|
||||||
|
&-desc {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000000;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.account-tab-box {
|
||||||
|
display: flex;
|
||||||
|
height: 34px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px;
|
||||||
|
> div {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: rgba(0, 0, 0, 0.6);
|
||||||
|
&.active {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: rgba(0, 0, 0, 0.92);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: rgba(0, 0, 0, 0.92);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
&-container {
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
&-wrap-login {
|
||||||
|
display: block;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
<template>
|
|
||||||
<n-drawer v-model:show="isDrawer" :width="width" placement="right">
|
|
||||||
<n-drawer-content :title="title" closable>
|
|
||||||
<n-form
|
|
||||||
:model="formParams"
|
|
||||||
:rules="rules"
|
|
||||||
ref="formRef"
|
|
||||||
label-placement="left"
|
|
||||||
:label-width="100"
|
|
||||||
>
|
|
||||||
<n-form-item label="类型" path="type">
|
|
||||||
<span>{{ formParams.type === 1 ? '侧边栏菜单' : '' }}</span>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="标题" path="label">
|
|
||||||
<n-input placeholder="请输入标题" v-model:value="formParams.label" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="副标题" path="subtitle">
|
|
||||||
<n-input placeholder="请输入副标题" v-model:value="formParams.subtitle" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="路径" path="path">
|
|
||||||
<n-input placeholder="请输入路径" v-model:value="formParams.path" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="打开方式" path="openType">
|
|
||||||
<n-radio-group v-model:value="formParams.openType" name="openType">
|
|
||||||
<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="auth">
|
|
||||||
<n-input placeholder="请输入权限,多个权限用,分割" v-model:value="formParams.auth" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="隐藏侧边栏" path="hidden">
|
|
||||||
<n-switch v-model:value="formParams.hidden" />
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<n-space>
|
|
||||||
<n-button type="primary" :loading="subLoading" @click="formSubmit">提交</n-button>
|
|
||||||
<n-button @click="handleReset">重置</n-button>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
</n-drawer-content>
|
|
||||||
</n-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { FormRules, useMessage } from 'naive-ui';
|
|
||||||
|
|
||||||
const rules: FormRules = {
|
|
||||||
label: {
|
|
||||||
required: true,
|
|
||||||
message: '请输入标题',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
path: {
|
|
||||||
required: true,
|
|
||||||
message: '请输入路径',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: '添加顶级菜单',
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: Number,
|
|
||||||
default: 450,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const message = useMessage();
|
|
||||||
const formRef = ref();
|
|
||||||
const defaultValueRef = () => ({
|
|
||||||
label: '',
|
|
||||||
type: 1,
|
|
||||||
subtitle: '',
|
|
||||||
openType: 1,
|
|
||||||
auth: '',
|
|
||||||
path: '',
|
|
||||||
hidden: false,
|
|
||||||
});
|
|
||||||
const isDrawer = ref(false);
|
|
||||||
const subLoading = ref(false);
|
|
||||||
const formParams = ref(defaultValueRef());
|
|
||||||
|
|
||||||
function openDrawer() {
|
|
||||||
isDrawer.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeDrawer() {
|
|
||||||
isDrawer.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formSubmit() {
|
|
||||||
formRef.value.validate((errors) => {
|
|
||||||
if (!errors) {
|
|
||||||
message.success('添加成功');
|
|
||||||
handleReset();
|
|
||||||
closeDrawer();
|
|
||||||
} else {
|
|
||||||
message.error('请填写完整信息');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleReset() {
|
|
||||||
formRef.value.restoreValidation();
|
|
||||||
formParams.value = Object.assign(formParams.value, defaultValueRef());
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
openDrawer,
|
|
||||||
closeDrawer,
|
|
||||||
});
|
|
||||||
</script>
|
|
@ -1,54 +1,89 @@
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { BasicColumn } from '@/components/Table';
|
import { BasicColumn } from '@/components/Table';
|
||||||
import {NTag } from 'naive-ui';
|
import {NTag } from 'naive-ui';
|
||||||
|
import { renderIcon } from '@/utils';
|
||||||
|
import * as VueIcon from '@vicons/antd';
|
||||||
|
const iconComponent = (icon) => {
|
||||||
|
const IconComponent = renderIcon(VueIcon[icon]);
|
||||||
|
return IconComponent;
|
||||||
|
};
|
||||||
export const columns: BasicColumn[] = [
|
export const columns: BasicColumn[] = [
|
||||||
{
|
{
|
||||||
title: '菜单名称',
|
title: '菜单名称',
|
||||||
key: 'label',
|
key: 'name',
|
||||||
|
width: 250,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '类型',
|
title: '类型',
|
||||||
key: 'type',
|
key: 'type',
|
||||||
render(row) {
|
width: 100,
|
||||||
return h(
|
render(record) {
|
||||||
'span',
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
default: () => (row.type === 1 ? '侧边栏菜单' : ''),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '副标题',
|
|
||||||
key: 'subtitle',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '路径',
|
|
||||||
key: 'path',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '权限标识',
|
|
||||||
key: 'auth',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '打开方式',
|
|
||||||
key: 'openType',
|
|
||||||
render(row) {
|
|
||||||
return h(
|
return h(
|
||||||
NTag,
|
NTag,
|
||||||
{
|
{
|
||||||
type: 'info',
|
type: record.type == 1 ? 'success' : 'info',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () => (row.openType === 1 ? '当前窗口' : '新窗口'),
|
default: () => (record.type == 1 ? '按钮' : '菜单'),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '图标',
|
||||||
key: 'create_date',
|
key: 'icon2',
|
||||||
|
width: 100,
|
||||||
|
render(record){
|
||||||
|
return h(
|
||||||
|
iconComponent(record.icon2)
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '权限标识',
|
||||||
|
key: 'permission',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
width: 100,
|
||||||
|
render(record) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type:record.status == 0 ? 'info' : 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => (record.status == 0 ? '正常' : '停用'),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '是否隐藏',
|
||||||
|
key: 'type',
|
||||||
|
width: 100,
|
||||||
|
render(record) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type: record.hide == 0 ? 'success' : 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => (record.hide == 0 ? '显示' : '隐藏'),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '排序',
|
||||||
|
key: 'sort',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '更新时间',
|
||||||
|
key: 'updateTime',
|
||||||
|
width: 180,
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
254
src/views/system/menu/edit.vue
Normal file
254
src/views/system/menu/edit.vue
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
<template>
|
||||||
|
<basicModal @register="modalRegister" ref="modalRef" class="basicModal basicFormModal" @on-ok="handleSubmit"
|
||||||
|
@on-close="handleClose">
|
||||||
|
<template #default>
|
||||||
|
<n-form ref="formRef" :model="formData" label-placement="left" label-width='90px'>
|
||||||
|
<n-form-item label="菜单类型" path="type" required>
|
||||||
|
<n-radio-group v-model:value="formData.type">
|
||||||
|
<n-radio :value="0">菜单</n-radio>
|
||||||
|
<n-radio :value="1">按钮</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="父级菜单" path="parentId" :rules="{ required: true, message: '请选择父级菜单', trigger: 'change' }">
|
||||||
|
<n-tree-select class="flex-1" v-model:value="formData.parentId" :options="menuOptions" label-field="name"
|
||||||
|
key-field="id" default-expand-all placeholder="请选择父级菜单" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="菜单名称" path="name" :rules="{ required: true, message: '请输入菜单名称', trigger: 'blur' }">
|
||||||
|
<n-input v-model:value="formData.name" placeholder="请输入菜单名称" clearable />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="formData.type == 0" label="菜单图标" path="icon2">
|
||||||
|
<icon-picker v-model:icon="formData.icon2">
|
||||||
|
<template #iconSelect>
|
||||||
|
<n-input v-model:value="formData.icon2" placeholder="请选择菜单图标">
|
||||||
|
<template #prefix>
|
||||||
|
<component :is="iconComponent(formData.icon2)" />
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
</template>
|
||||||
|
</icon-picker>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="打开方式" path="target" v-if="formData.type == 0">
|
||||||
|
<div>
|
||||||
|
<n-radio-group v-model:value="formData.target">
|
||||||
|
<n-radio :value="0">组件</n-radio>
|
||||||
|
<n-radio :value="1">内链</n-radio>
|
||||||
|
<n-radio :value="2">外链</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
<div class="form-tips"> 选择外链,则新窗口打开页面 </div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="formData.type == 0 && (formData.target == 0 || formData.target == 1)" label="路由路径"
|
||||||
|
path="path" :rules="{ required: true, message: '请输入路由路径', trigger: 'blur' }">
|
||||||
|
<div class="flex-1">
|
||||||
|
<n-input v-model:value="formData.path" placeholder="请输入路由路径" clearable />
|
||||||
|
<div class="form-tips"> 访问的路由地址 </div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="formData.type == 0" label="组件路径" path="component">
|
||||||
|
<div class="flex-1">
|
||||||
|
<n-auto-complete style="width: 100%" v-model:value="formData.component" :options="dataSource"
|
||||||
|
@update:value="filterOption" placeholder="请输入组件路径" />
|
||||||
|
<div class="form-tips">
|
||||||
|
访问的组件路径,如:`permission/admin/index`,默认在`views`目录下,如外网地址需内链访问则以`http(s)://`开头
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="formData.target == 0" label="权限字符" path="permission">
|
||||||
|
<div class="flex-1">
|
||||||
|
<n-input v-model:value="formData.permission" placeholder="请输入权限字符" clearable />
|
||||||
|
<div class="form-tips">
|
||||||
|
将作为server端API验权使用,如`system:admin:list`,请谨慎修改
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="formData.type == 0" label="是否显示" path="hide" required>
|
||||||
|
<div>
|
||||||
|
<n-radio-group v-model:value="formData.hide">
|
||||||
|
<n-radio :value="0">显示</n-radio>
|
||||||
|
<n-radio :value="1">隐藏</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
<div class="form-tips"> 选择隐藏则路由将不会出现在侧边栏,但仍然可以访问 </div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item v-if="formData.type == 0" label="菜单状态" path="status" required>
|
||||||
|
<div>
|
||||||
|
<n-radio-group v-model:value="formData.status">
|
||||||
|
<n-radio :value="0">正常</n-radio>
|
||||||
|
<n-radio :value="1">停用</n-radio>
|
||||||
|
</n-radio-group>
|
||||||
|
<div class="form-tips"> 选择停用则路由将不会出现在侧边栏,也不能被访问 </div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="菜单排序" path="sort">
|
||||||
|
<div>
|
||||||
|
<n-input-number v-model:value="formData.sort" :max="9999" />
|
||||||
|
<div class="form-tips">数值越小越排前</div>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</template>
|
||||||
|
</basicModal>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { menuAdd, menuUpdate, getMenuList, getMenuDetail } from '@/api/system/menu';
|
||||||
|
import { onMounted, reactive, ref, shallowRef } from 'vue';
|
||||||
|
import { getModulesKey } from '@/router';
|
||||||
|
import { arrayToTree, treeToArray, buildTree } from '@/utils/auth';
|
||||||
|
import { useLockFn } from '@/utils/useLockFn';
|
||||||
|
import IconPicker from '@/components/icon/picker.vue';
|
||||||
|
import * as VueIcon from '@vicons/antd';
|
||||||
|
import { useMessage, useDialog } from 'naive-ui';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { useModal } from '@/components/Modal';
|
||||||
|
import { renderIcon } from '@/utils';
|
||||||
|
/**
|
||||||
|
* 定义接收的参数
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
menuId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
pid: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义参数变量
|
||||||
|
*/
|
||||||
|
|
||||||
|
const [modalRegister, { openModal, closeModal, setSubLoading, setProps }] = useModal({
|
||||||
|
title: props.menuId ? '编辑菜单' : "添加菜单",
|
||||||
|
subBtuText: '确定',
|
||||||
|
width: 600,
|
||||||
|
});
|
||||||
|
const message = useMessage();
|
||||||
|
const emit = defineEmits(['success', 'update:visible']);
|
||||||
|
const formRef = ref();
|
||||||
|
const dataSource = ref([]);
|
||||||
|
|
||||||
|
const componentsOptions = ref(getModulesKey());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义表单参数
|
||||||
|
*/
|
||||||
|
const formData = reactive({
|
||||||
|
id: '',
|
||||||
|
//父级id
|
||||||
|
parentId: 0,
|
||||||
|
//类型
|
||||||
|
type: 0,
|
||||||
|
//图标
|
||||||
|
icon2: '',
|
||||||
|
//名称
|
||||||
|
name: '',
|
||||||
|
//排序号
|
||||||
|
sort: 0,
|
||||||
|
// 路由路径
|
||||||
|
path: '',
|
||||||
|
//权限链接
|
||||||
|
permission: '',
|
||||||
|
//前端组件
|
||||||
|
component: '',
|
||||||
|
//是否显示 0=显示, 1=不显示
|
||||||
|
hide: 0,
|
||||||
|
//是否外链 0=不是, 1=是
|
||||||
|
target: 0,
|
||||||
|
status: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuOptions = ref<any[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义Icon组件
|
||||||
|
* @param icon 图标
|
||||||
|
*/
|
||||||
|
const iconComponent = (icon) => {
|
||||||
|
const IconComponent = renderIcon(VueIcon[icon]);
|
||||||
|
return IconComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterOption = (input: string) => {
|
||||||
|
const results = input
|
||||||
|
? componentsOptions.value.filter((item) =>
|
||||||
|
item.toLowerCase().includes(input.toLowerCase()),
|
||||||
|
)
|
||||||
|
: componentsOptions.value;
|
||||||
|
dataSource.value = results.map((item) => ({ label: item, value: item }))
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单列表
|
||||||
|
*/
|
||||||
|
const getMenu = async () => {
|
||||||
|
const data: any = await getMenuList();
|
||||||
|
const menu: any = { id: 0, name: '顶级', children: [] };
|
||||||
|
const lists = buildTree(data.filter((item) => item.type == 0));
|
||||||
|
menu.children = lists;
|
||||||
|
menuOptions.value.push(menu);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行提交表单
|
||||||
|
*/
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
props.menuId ? await menuUpdate(formData) : await menuAdd(formData);
|
||||||
|
message.success('操作成功');
|
||||||
|
emit('update:visible', false);
|
||||||
|
emit('success');
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isLock: subLoading, lockFn: submit } = useLockFn(handleSubmit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置表单数据
|
||||||
|
* @param data 参数
|
||||||
|
*/
|
||||||
|
const setFormData = (data: Record<any, any>) => {
|
||||||
|
for (const key in formData) {
|
||||||
|
if (data[key] != null && data[key] != undefined) {
|
||||||
|
formData[key] = data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*获取菜单详情
|
||||||
|
*/
|
||||||
|
const getDetail = async () => {
|
||||||
|
const data = await getMenuDetail(props.menuId);
|
||||||
|
setFormData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭窗体
|
||||||
|
*/
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 钩子函数
|
||||||
|
*/
|
||||||
|
onMounted(async () => {
|
||||||
|
componentsOptions.value.map((item) => {
|
||||||
|
dataSource.value.push({ label: item, value: item });
|
||||||
|
});
|
||||||
|
getMenu();
|
||||||
|
if (props.menuId) {
|
||||||
|
getDetail();
|
||||||
|
} else {
|
||||||
|
formData.parentId = props.pid;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//导出方法
|
||||||
|
defineExpose({
|
||||||
|
openModal,
|
||||||
|
closeModal,
|
||||||
|
setProps,
|
||||||
|
});
|
||||||
|
</script>
|
@ -1,249 +1,169 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="menu-index">
|
||||||
<div class="n-layout-page-header">
|
<n-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||||
<n-card :bordered="false" title="菜单权限管理">
|
<n-spin :show="loading">
|
||||||
页面数据为 Mock 示例数据,非真实数据。
|
<BasicTable ref="tableRef" :columns="columns" :paginate-single-page="false" :request="loadDataTable"
|
||||||
</n-card>
|
:row-key="(row) => row.id" :autoScrollX="true" :actionColumn="actionColumn"
|
||||||
</div>
|
v-model:expanded-row-keys="expandKeys">
|
||||||
<n-grid class="mt-3" cols="1 s:1 m:1 l:3 xl:3 2xl:3" responsive="screen" :x-gap="12">
|
<template #tableTitle>
|
||||||
<n-gi span="1">
|
|
||||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
|
||||||
<template #header>
|
|
||||||
<n-space>
|
<n-space>
|
||||||
<n-dropdown trigger="hover" @select="selectAddMenu" :options="addMenuOptions">
|
<n-button type="primary" @click="handleAdd()" v-perm="['sys:menu:add']">
|
||||||
<n-button type="info" ghost icon-placement="right">
|
|
||||||
添加菜单
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<div class="flex items-center">
|
<n-icon>
|
||||||
<n-icon size="14">
|
<PlusOutlined />
|
||||||
<DownOutlined />
|
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</n-button>
|
|
||||||
</n-dropdown>
|
|
||||||
<n-button type="info" ghost icon-placement="left" @click="packHandle">
|
|
||||||
全部{{ expandedKeys.length ? '收起' : '展开' }}
|
|
||||||
<template #icon>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<n-icon size="14">
|
|
||||||
<AlignLeftOutlined />
|
|
||||||
</n-icon>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
新增
|
||||||
</n-button>
|
</n-button>
|
||||||
|
<n-button @click="handleExpand"> 展开/折叠 </n-button>
|
||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
<div class="w-full menu">
|
</BasicTable>
|
||||||
<n-input v-model:value="pattern" placeholder="输入菜单名称搜索">
|
</n-spin>
|
||||||
<template #suffix>
|
|
||||||
<n-icon size="18" class="cursor-pointer">
|
|
||||||
<SearchOutlined />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
</n-input>
|
|
||||||
<div class="py-3 menu-list">
|
|
||||||
<template v-if="loading">
|
|
||||||
<div class="flex items-center justify-center py-4">
|
|
||||||
<n-spin size="medium" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<n-tree
|
|
||||||
block-line
|
|
||||||
cascade
|
|
||||||
checkable
|
|
||||||
:virtual-scroll="true"
|
|
||||||
:pattern="pattern"
|
|
||||||
:data="treeData"
|
|
||||||
:expandedKeys="expandedKeys"
|
|
||||||
style="max-height: 650px; overflow: hidden"
|
|
||||||
@update:selected-keys="selectedTree"
|
|
||||||
@update:expanded-keys="onExpandedKeys"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-gi>
|
<editDialog ref="createModalRef" v-if="editVisible" :menuId="menuId" :pid="pid" v-model:visible="editVisible"
|
||||||
<n-gi span="2">
|
@success="loadDataTable" />
|
||||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
|
||||||
<template #header>
|
|
||||||
<n-space>
|
|
||||||
<n-icon size="18">
|
|
||||||
<FormOutlined />
|
|
||||||
</n-icon>
|
|
||||||
<span>编辑菜单{{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
|
||||||
</n-space>
|
|
||||||
</template>
|
|
||||||
<n-alert type="info" closable> 从菜单列表选择一项后,进行编辑</n-alert>
|
|
||||||
<n-form
|
|
||||||
:model="formParams"
|
|
||||||
:rules="rules"
|
|
||||||
ref="formRef"
|
|
||||||
label-placement="left"
|
|
||||||
:label-width="100"
|
|
||||||
v-if="isEditMenu"
|
|
||||||
class="py-4"
|
|
||||||
>
|
|
||||||
<n-form-item label="类型" path="type">
|
|
||||||
<span>{{ formParams.type === 1 ? '侧边栏菜单' : '' }}</span>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="标题" path="label">
|
|
||||||
<n-input placeholder="请输入标题" v-model:value="formParams.label" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="副标题" path="subtitle">
|
|
||||||
<n-input placeholder="请输入副标题" v-model:value="formParams.subtitle" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="路径" path="path">
|
|
||||||
<n-input placeholder="请输入路径" v-model:value="formParams.path" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="打开方式" path="openType">
|
|
||||||
<n-radio-group v-model:value="formParams.openType" name="openType">
|
|
||||||
<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="auth">
|
|
||||||
<n-input placeholder="请输入权限,多个权限用,分割" v-model:value="formParams.auth" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item path="auth" style="margin-left: 100px">
|
|
||||||
<n-space>
|
|
||||||
<n-button type="primary" :loading="subLoading" @click="formSubmit"
|
|
||||||
>保存修改</n-button
|
|
||||||
>
|
|
||||||
<n-button @click="handleReset">重置</n-button>
|
|
||||||
</n-space>
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
</n-card>
|
|
||||||
</n-gi>
|
|
||||||
</n-grid>
|
|
||||||
<CreateDrawer ref="createDrawerRef" :title="drawerTitle" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, unref, reactive, onMounted, computed } from 'vue';
|
|
||||||
import { useMessage } from 'naive-ui';
|
|
||||||
import { DownOutlined, AlignLeftOutlined, SearchOutlined, FormOutlined } from '@vicons/antd';
|
|
||||||
import { getMenuList } from '@/api/system/menu';
|
|
||||||
import { getTreeItem } from '@/utils';
|
|
||||||
import CreateDrawer from './CreateDrawer.vue';
|
|
||||||
|
|
||||||
const rules = {
|
<script lang="ts" setup name="menus">
|
||||||
label: {
|
import { defineAsyncComponent, nextTick, onMounted, ref, reactive, h } from 'vue';
|
||||||
required: true,
|
import { PlusOutlined, EditOutlined, DeleteOutlined, FormOutlined } from '@vicons/antd';
|
||||||
message: '请输入标题',
|
import { BasicTable, TableAction } from '@/components/Table';
|
||||||
trigger: 'blur',
|
import { renderIcon } from '@/utils';
|
||||||
|
import editDialog from './edit.vue';
|
||||||
|
import { getMenuList, menuDelete } from '@/api/system/menu';
|
||||||
|
import { getTreeValues } from '@/utils/helper/treeHelper';
|
||||||
|
import { useMessage, useDialog } from 'naive-ui';
|
||||||
|
import { columns } from './columns';
|
||||||
|
import { buildTree } from '@/utils/auth';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义参数变量
|
||||||
|
*/
|
||||||
|
const message = useMessage();
|
||||||
|
const dialog = useDialog()
|
||||||
|
const tableRef = ref();
|
||||||
|
const loading = ref(false);
|
||||||
|
const editVisible = ref(false);
|
||||||
|
const expandKeys = ref([]);
|
||||||
|
const createModalRef = ref();
|
||||||
|
const menuId = ref(0);
|
||||||
|
const pid = ref(0);
|
||||||
|
|
||||||
|
const actionColumn = reactive({
|
||||||
|
width: 220,
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
fixed: 'right',
|
||||||
|
render(record) {
|
||||||
|
return h(TableAction as any, {
|
||||||
|
style: 'button',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: '新增',
|
||||||
|
type: 'info',
|
||||||
|
icon: renderIcon(PlusOutlined),
|
||||||
|
auth: ['sys:menu:add'],
|
||||||
|
ifShow: () => {
|
||||||
|
return record.type !== 1
|
||||||
},
|
},
|
||||||
path: {
|
onclick: handleAdd.bind(null, record),
|
||||||
required: true,
|
|
||||||
message: '请输入路径',
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '编辑',
|
||||||
|
type: 'warning',
|
||||||
|
icon: renderIcon(FormOutlined),
|
||||||
|
auth: ['sys:menu:update'],
|
||||||
|
onclick: handleEdit.bind(null, record),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
type: 'error',
|
||||||
|
icon: renderIcon(DeleteOutlined),
|
||||||
|
auth: ['sys:menu:delete'],
|
||||||
|
onclick: handleDelete.bind(null, record),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单列表
|
||||||
|
*/
|
||||||
|
const loadDataTable = async (res) => {
|
||||||
|
const data = await getMenuList();
|
||||||
|
data.map((item) => {
|
||||||
|
item.key = item.id;
|
||||||
|
});
|
||||||
|
const result = {
|
||||||
|
records: buildTree(data),
|
||||||
|
total: 1
|
||||||
|
}
|
||||||
|
return result
|
||||||
};
|
};
|
||||||
|
|
||||||
const formRef: any = ref(null);
|
/**
|
||||||
const createDrawerRef = ref();
|
* 执行添加
|
||||||
const message = useMessage();
|
*/
|
||||||
|
const handleAdd = async (record: any) => {
|
||||||
|
menuId.value = 0;
|
||||||
|
pid.value = record ? record.parentId : 0;
|
||||||
|
editVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
createModalRef.value.openModal();
|
||||||
|
|
||||||
let treeItemKey = ref([]);
|
};
|
||||||
|
|
||||||
let expandedKeys = ref([]);
|
/**
|
||||||
|
* 执行编辑
|
||||||
|
* @param data 参数
|
||||||
|
*/
|
||||||
|
const handleEdit = async (data: any) => {
|
||||||
|
menuId.value = data.id;
|
||||||
|
editVisible.value = true;
|
||||||
|
await nextTick();
|
||||||
|
createModalRef.value.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
const treeData = ref([]);
|
/**
|
||||||
|
* 执行删除
|
||||||
const loading = ref(true);
|
* @param id 菜单ID
|
||||||
const subLoading = ref(false);
|
*/
|
||||||
const isEditMenu = ref(false);
|
const handleDelete = (row: number) => {
|
||||||
const treeItemTitle = ref('');
|
dialog.warning({
|
||||||
const pattern = ref('');
|
title: '提示',
|
||||||
const drawerTitle = ref('');
|
content: '确定要删除?',
|
||||||
|
positiveText: '确定',
|
||||||
const isAddSon = computed(() => {
|
negativeText: '取消',
|
||||||
return !treeItemKey.value.length;
|
onPositiveClick: async () => {
|
||||||
});
|
loading.value = true;
|
||||||
|
await menuDelete(row.id);
|
||||||
const addMenuOptions = ref([
|
message.success('删除成功');
|
||||||
{
|
loadDataTable()
|
||||||
label: '添加顶级菜单',
|
|
||||||
key: 'home',
|
|
||||||
disabled: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '添加子菜单',
|
|
||||||
key: 'son',
|
|
||||||
disabled: isAddSon,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const formParams = reactive({
|
|
||||||
type: 1,
|
|
||||||
label: '',
|
|
||||||
subtitle: '',
|
|
||||||
path: '',
|
|
||||||
auth: '',
|
|
||||||
openType: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
function selectAddMenu(key: string) {
|
|
||||||
drawerTitle.value = key === 'home' ? '添加顶栏菜单' : `添加子菜单:${treeItemTitle.value}`;
|
|
||||||
openCreateDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
function openCreateDrawer() {
|
|
||||||
const { openDrawer } = createDrawerRef.value;
|
|
||||||
openDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectedTree(keys) {
|
|
||||||
if (keys.length) {
|
|
||||||
const treeItem = getTreeItem(unref(treeData), keys[0]);
|
|
||||||
treeItemKey.value = keys;
|
|
||||||
treeItemTitle.value = treeItem.label;
|
|
||||||
Object.assign(formParams, treeItem);
|
|
||||||
isEditMenu.value = true;
|
|
||||||
} else {
|
|
||||||
isEditMenu.value = false;
|
|
||||||
treeItemKey.value = [];
|
|
||||||
treeItemTitle.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleReset() {
|
|
||||||
const treeItem = getTreeItem(unref(treeData), treeItemKey.value[0]);
|
|
||||||
Object.assign(formParams, treeItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formSubmit() {
|
|
||||||
formRef.value.validate((errors: boolean) => {
|
|
||||||
if (!errors) {
|
|
||||||
message.error('抱歉,您没有该权限');
|
|
||||||
} else {
|
|
||||||
message.error('请填写完整信息');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function packHandle() {
|
|
||||||
if (expandedKeys.value.length) {
|
|
||||||
expandedKeys.value = [];
|
|
||||||
} else {
|
|
||||||
expandedKeys.value = unref(treeData).map((item: any) => item.key as string) as [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const treeMenuList = await getMenuList();
|
|
||||||
const keys = treeMenuList.list.map((item) => item.key);
|
|
||||||
Object.assign(formParams, keys);
|
|
||||||
treeData.value = treeMenuList.list;
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
});
|
},
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
function onExpandedKeys(keys) {
|
/**
|
||||||
expandedKeys.value = keys;
|
* 执行扩展、收缩
|
||||||
|
*/
|
||||||
|
const handleExpand = () => {
|
||||||
|
loading.value = true;
|
||||||
|
if (!expandKeys.value.length) {
|
||||||
|
expandKeys.value = getTreeValues(tableRef.value.getDataSource(), 'id');
|
||||||
|
console.log(expandKeys.value)
|
||||||
|
// setTimeout(()=>{
|
||||||
|
// loading.value = false;
|
||||||
|
// },2000)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
expandKeys.value = [];
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
Loading…
Reference in New Issue
Block a user