登录、字体图标组件、菜单管理
This commit is contained in:
parent
bb1a41fb9a
commit
3a7c381580
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="page-footer">
|
||||
<div class="page-footer-link">
|
||||
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 官网 </a>
|
||||
<a href="https://github.com/jekip/naive-ui-admin" 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>
|
||||
<a href="https://www.baidu.com" target="_blank"> 社区 </a>
|
||||
<a href="https://www.baidu.com" target="_blank"> 交流 </a>
|
||||
</div>
|
||||
<div class="copyright"> naive-ui-admin 1.4 · Made by Ah jung </div>
|
||||
<div class="copyright">Copyright © 2024 南京云恒信息技术有限公司</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-footer {
|
||||
//margin: 28px 0 24px 0;
|
||||
margin: 20px 0 10px 0;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
|
||||
|
@ -53,6 +53,7 @@
|
||||
<div class="admin-layout-content-main">
|
||||
<div class="main-view" ref="adminBodyRef">
|
||||
<MainView />
|
||||
<PageFooter/>
|
||||
</div>
|
||||
</div>
|
||||
<n-back-top :right="100" />
|
||||
@ -79,6 +80,7 @@
|
||||
import { MainView } from './components/Main';
|
||||
import { AsideMenu } from './components/Menu';
|
||||
import { PageHeader } from './components/Header';
|
||||
import { PageFooter } from './components/Footer';
|
||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
@ -4,10 +4,12 @@
|
||||
*/
|
||||
import { App } from 'vue';
|
||||
import { PageWrapper, PageFooter } from '@/components/Page';
|
||||
import { basicModal } from '@/components/Modal';
|
||||
import { Authority } from '@/components/Authority';
|
||||
|
||||
export function setupCustomComponents(app: App) {
|
||||
app.component('PageWrapper', PageWrapper);
|
||||
app.component('basicModal', basicModal);
|
||||
app.component('PageFooter', PageFooter);
|
||||
app.component('Authority', Authority);
|
||||
}
|
||||
|
@ -8,10 +8,10 @@
|
||||
:rules="rules"
|
||||
>
|
||||
<n-form-item path="username">
|
||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
||||
<n-input v-model:value="formInline.username" placeholder="请输入登录账号">
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<PersonOutline />
|
||||
<UserOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
@ -19,58 +19,71 @@
|
||||
<n-form-item path="password">
|
||||
<n-input
|
||||
v-model:value="formInline.password"
|
||||
show-password
|
||||
type="password"
|
||||
showPasswordOn="click"
|
||||
placeholder="请输入密码"
|
||||
placeholder="请输入登录密码"
|
||||
@keyup.enter="handleSubmit"
|
||||
>
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<LockClosedOutline />
|
||||
<LockOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<div class="mb-6 default-color">
|
||||
<div class="flex justify-between">
|
||||
<n-form-item path="code">
|
||||
<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">
|
||||
<n-checkbox v-model:checked="autoLogin">自动登录</n-checkbox>
|
||||
</div>
|
||||
<div class="flex-initial order-last">
|
||||
<a href="javascript:">忘记密码</a>
|
||||
</div>
|
||||
<n-checkbox v-model:value:checked="autoLogin">记住密码</n-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-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>
|
||||
<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>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@ -79,51 +92,66 @@
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { FormRules, useMessage } from 'naive-ui';
|
||||
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 {
|
||||
UserOutlined,
|
||||
LockOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
GithubOutlined,
|
||||
AlipayCircleOutlined,
|
||||
} from '@vicons/antd';
|
||||
const captchaImg = ref('');
|
||||
// 动态加载滑块验证码组件
|
||||
|
||||
interface FormState {
|
||||
username: string;
|
||||
password: string;
|
||||
code: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
const formRef = ref();
|
||||
const message = useMessage();
|
||||
const emit = defineEmits(['backLogin']);
|
||||
const formRef = ref();
|
||||
|
||||
const loading = ref(false);
|
||||
const autoLogin = ref(true);
|
||||
const LOGIN_NAME = PageEnum.BASE_LOGIN_NAME;
|
||||
|
||||
const formInline = reactive({
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
username: '',
|
||||
password: '',
|
||||
code: '',
|
||||
key: '',
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
username: { required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
username: { 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 router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
const { username, password } = formInline;
|
||||
message.loading('登录中...');
|
||||
const handleSubmit = () => {
|
||||
if (!formRef.value) return;
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
loading.value = true;
|
||||
|
||||
const params: FormState = {
|
||||
username,
|
||||
password,
|
||||
username: formInline.username,
|
||||
password: formInline.password,
|
||||
code: formInline.code,
|
||||
key: formInline.key,
|
||||
// grant_type:"password"
|
||||
};
|
||||
|
||||
try {
|
||||
const { code, message: msg } = await userStore.login(params);
|
||||
message.destroyAll();
|
||||
const { code, msg } = await userStore.login(params);
|
||||
if (code == ResultEnum.SUCCESS) {
|
||||
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
|
||||
message.success('登录成功,即将进入系统');
|
||||
@ -131,18 +159,43 @@
|
||||
router.replace('/');
|
||||
} else router.replace(toPath);
|
||||
} else {
|
||||
message.info(msg || '登录失败');
|
||||
getCaptcha();
|
||||
message.error(msg || '登录失败');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function goRegister() {
|
||||
emit('goRegister');
|
||||
}
|
||||
const getCaptcha = async () => {
|
||||
let { key, captcha } = await getInfoCaptcha();
|
||||
formInline.key = key;
|
||||
captchaImg.value = captcha;
|
||||
};
|
||||
const goRegister = () => {
|
||||
emit('backLogin', false);
|
||||
};
|
||||
|
||||
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>
|
||||
|
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,122 +1,110 @@
|
||||
<template>
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:show-label="false"
|
||||
:show-require-mark="false"
|
||||
size="large"
|
||||
:model="formInline"
|
||||
:rules="rules"
|
||||
>
|
||||
<n-form ref="formRef" :show-label="false" :show-require-mark="false" size="large" :model="formInline" :rules="rules"
|
||||
class="register-form">
|
||||
<n-form-item path="username">
|
||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<PersonOutline />
|
||||
<UserOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<n-form-item path="mobile">
|
||||
<div class="flex w-full">
|
||||
<n-input class="order-first" v-model:value="formInline.mobile" placeholder="请输入手机号码">
|
||||
<n-input v-model:value="formInline.mobile" placeholder="请输入手机号码">
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<MobileOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</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 path="code">
|
||||
<n-input v-model:value="formInline.code" placeholder="请输入验证码">
|
||||
<n-input v-model:value.trim="formInline.code" placeholder="验证码">
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<SafetyOutlined />
|
||||
<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 path="password">
|
||||
<n-input
|
||||
v-model:value="formInline.password"
|
||||
type="password"
|
||||
showPasswordOn="click"
|
||||
placeholder="请输入密码"
|
||||
>
|
||||
<n-input v-model:value="formInline.password" type="password" show-password placeholder="请输入密码">
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<LockClosedOutline />
|
||||
<LockOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="retPassword">
|
||||
<n-input
|
||||
v-model:value="formInline.retPassword"
|
||||
type="password"
|
||||
showPasswordOn="click"
|
||||
placeholder="请再次输入密码"
|
||||
@keyup.enter="handleSubmit"
|
||||
>
|
||||
<n-input v-model:value="formInline.retPassword" type="password" show-password placeholder="请再次输入密码">
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<LockClosedOutline />
|
||||
<LockOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
</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">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-initial">
|
||||
<n-checkbox v-model:checked="formInline.agreement">我同意隐私协议</n-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { FormRules, useMessage } from 'naive-ui';
|
||||
import { PersonOutline, LockClosedOutline } from '@vicons/ionicons5';
|
||||
import { MobileOutlined, SafetyOutlined } from '@vicons/antd';
|
||||
import { isNumber } from '@/utils/is';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { rule } from '@/utils/validate';
|
||||
import {
|
||||
UserOutlined,
|
||||
MobileOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
LockOutlined,
|
||||
} from '@vicons/antd';
|
||||
|
||||
const formRef = ref();
|
||||
const message = useMessage();
|
||||
const loading = ref(false);
|
||||
const codeMsg = ref<number | string>('获取验证码');
|
||||
const isGetCode = ref(false);
|
||||
const formRef = ref();
|
||||
const message = useMessage();
|
||||
const loading = ref(false);
|
||||
const codeMsg: any = ref('获取验证码');
|
||||
const isGetCode = ref(false);
|
||||
|
||||
const emit = defineEmits(['backLogin']);
|
||||
const emit = defineEmits(['backLogin']);
|
||||
|
||||
const formInline = reactive({
|
||||
const formInline = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
retPassword: '',
|
||||
mobile: '',
|
||||
code: '',
|
||||
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' },
|
||||
mobile: { required: true, message: '请输入手机号码', trigger: 'blur' },
|
||||
mobile: [{ key: 'a', required: true, validator: validatePhone, trigger: 'blur' }],
|
||||
code: { required: true, message: '请输入短信验证码', trigger: 'blur' },
|
||||
password: { required: true, message: '请输入密码', trigger: 'blur' },
|
||||
retPassword: { required: true, message: '请输入确认密码', trigger: 'blur' },
|
||||
@ -124,37 +112,52 @@
|
||||
required: true,
|
||||
type: 'boolean',
|
||||
trigger: 'change',
|
||||
message: '请先勾选协议',
|
||||
validator: (_, value) => value === true,
|
||||
},
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
formRef.value.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
message.success('注册准备就绪');
|
||||
loading.value = true;
|
||||
validator: async (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请先勾选协议');
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
loading.value = true;
|
||||
backLogin();
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error('请填写完整信息');
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const backLogin = () => {
|
||||
emit('backLogin');
|
||||
};
|
||||
const backLogin = () => {
|
||||
emit('backLogin', true);
|
||||
};
|
||||
|
||||
function getCode() {
|
||||
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 as number)--;
|
||||
if (isNumber(codeMsg.value) && codeMsg.value <= 0) {
|
||||
codeMsg.value--;
|
||||
if (codeMsg.value <= 0) {
|
||||
clearInterval(time);
|
||||
codeMsg.value = '获取验证码';
|
||||
isGetCode.value = false;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,221 +1,221 @@
|
||||
<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 class="account">
|
||||
<div class="account-container">
|
||||
<div class="account-wrap-login">
|
||||
<div class="login-pic">
|
||||
<h1 class="login-title">云恒WMS</h1>
|
||||
<h4 class="login-subtitle">赋能开发者,助力企业发展,全方位提供数据中台解决方案!</h4>
|
||||
</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 class="view-account-form">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
size="large"
|
||||
:model="formInline"
|
||||
:rules="rules"
|
||||
<template v-if="loginFlag">
|
||||
<div class="account-tab-box">
|
||||
<div
|
||||
:class="activeIndex == index ? 'active' : ''"
|
||||
v-for="(item, index) in tabData"
|
||||
@click="handleClick(index)"
|
||||
:key="index"
|
||||
>{{ item }}</div
|
||||
>
|
||||
<n-form-item path="username">
|
||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
||||
<template #prefix>
|
||||
<n-icon size="18" color="#808695">
|
||||
<PersonOutline />
|
||||
</n-icon>
|
||||
</div>
|
||||
<LoginForm v-if="activeIndex === 0" @back-login="goLogin" />
|
||||
<PhoneForm v-else-if="activeIndex === 1" />
|
||||
<QrcodeForm v-else-if="activeIndex === 2" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
<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 v-else>
|
||||
<RegisterForm @back-login="goLogin" />
|
||||
</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>
|
||||
</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">
|
||||
<LogoGithub />
|
||||
<div class="corner-box" @click="handleCornerClick">
|
||||
<n-icon size="40" color="#ffffff" v-if="loginFlag">
|
||||
<UserAddOutlined />
|
||||
</n-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-initial mx-2">
|
||||
<a href="javascript:">
|
||||
<n-icon size="24" color="#2d8cf0">
|
||||
<LogoFacebook />
|
||||
<n-icon size="40" color="#ffffff" v-else>
|
||||
<ArrowUndoOutline />
|
||||
</n-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-initial" style="margin-left: auto">
|
||||
<a href="javascript:">注册账号</a>
|
||||
</div>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 logo from '@/assets/images/logo.png';
|
||||
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook } from '@vicons/ionicons5';
|
||||
|
||||
interface FormState {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
import { ref } from 'vue';
|
||||
import LoginForm from './LoginForm.vue';
|
||||
import PhoneForm from './PhoneForm.vue';
|
||||
import QrcodeForm from './QrcodeForm.vue';
|
||||
import RegisterForm from './RegisterForm.vue';
|
||||
import { UserAddOutlined, RollbackOutlined } from '@vicons/antd';
|
||||
import { ArrowUndoOutline } from '@vicons/ionicons5';
|
||||
const loginFlag = ref(true);
|
||||
const activeIndex = ref(0);
|
||||
const tabData = ref(['账号登录', '手机号登录', '扫码登录']);
|
||||
const handleClick = (index) => {
|
||||
activeIndex.value = index;
|
||||
};
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
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 handleCornerClick = () => {
|
||||
loginFlag.value = !loginFlag.value;
|
||||
};
|
||||
|
||||
const { code, message: msg } = await userStore.login(params);
|
||||
|
||||
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;
|
||||
const goLogin = (type) => {
|
||||
loginFlag.value = type;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.view-account {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
.account {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
|
||||
&-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%;
|
||||
}
|
||||
|
||||
.default-color {
|
||||
color: #515a6e;
|
||||
|
||||
.ant-checkbox-wrapper {
|
||||
color: #515a6e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.view-account {
|
||||
background-image: url('../../assets/images/login.svg');
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
background-image: url('@/assets/images/login-bg.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50%;
|
||||
background-size: 100%;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.page-account-container {
|
||||
padding: 32px 0 24px 0;
|
||||
&-wrap-login {
|
||||
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>
|
||||
|
@ -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 { 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[] = [
|
||||
{
|
||||
title: '菜单名称',
|
||||
key: 'label',
|
||||
key: 'name',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'type',
|
||||
render(row) {
|
||||
return h(
|
||||
'span',
|
||||
{},
|
||||
{
|
||||
default: () => (row.type === 1 ? '侧边栏菜单' : ''),
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '副标题',
|
||||
key: 'subtitle',
|
||||
},
|
||||
{
|
||||
title: '路径',
|
||||
key: 'path',
|
||||
},
|
||||
{
|
||||
title: '权限标识',
|
||||
key: 'auth',
|
||||
},
|
||||
{
|
||||
title: '打开方式',
|
||||
key: 'openType',
|
||||
render(row) {
|
||||
width: 100,
|
||||
render(record) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: 'info',
|
||||
type: record.type == 1 ? 'success' : 'info',
|
||||
},
|
||||
{
|
||||
default: () => (row.openType === 1 ? '当前窗口' : '新窗口'),
|
||||
default: () => (record.type == 1 ? '按钮' : '菜单'),
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'create_date',
|
||||
title: '图标',
|
||||
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>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="菜单权限管理">
|
||||
页面数据为 Mock 示例数据,非真实数据。
|
||||
</n-card>
|
||||
</div>
|
||||
<n-grid class="mt-3" cols="1 s:1 m:1 l:3 xl:3 2xl:3" responsive="screen" :x-gap="12">
|
||||
<n-gi span="1">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small">
|
||||
<template #header>
|
||||
<div class="menu-index">
|
||||
<n-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<n-spin :show="loading">
|
||||
<BasicTable ref="tableRef" :columns="columns" :paginate-single-page="false" :request="loadDataTable"
|
||||
:row-key="(row) => row.id" :autoScrollX="true" :actionColumn="actionColumn"
|
||||
v-model:expanded-row-keys="expandKeys">
|
||||
<template #tableTitle>
|
||||
<n-space>
|
||||
<n-dropdown trigger="hover" @select="selectAddMenu" :options="addMenuOptions">
|
||||
<n-button type="info" ghost icon-placement="right">
|
||||
添加菜单
|
||||
<n-button type="primary" @click="handleAdd()" v-perm="['sys:menu:add']">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<DownOutlined />
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</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>
|
||||
新增
|
||||
</n-button>
|
||||
<n-button @click="handleExpand"> 展开/折叠 </n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input v-model:value="pattern" placeholder="输入菜单名称搜索">
|
||||
<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>
|
||||
</BasicTable>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<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" />
|
||||
<editDialog ref="createModalRef" v-if="editVisible" :menuId="menuId" :pid="pid" v-model:visible="editVisible"
|
||||
@success="loadDataTable" />
|
||||
</div>
|
||||
</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 = {
|
||||
label: {
|
||||
required: true,
|
||||
message: '请输入标题',
|
||||
trigger: 'blur',
|
||||
},
|
||||
path: {
|
||||
required: true,
|
||||
message: '请输入路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
<script lang="ts" setup name="menus">
|
||||
import { defineAsyncComponent, nextTick, onMounted, ref, reactive, h } from 'vue';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, FormOutlined } from '@vicons/antd';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
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 formRef: any = ref(null);
|
||||
const createDrawerRef = ref();
|
||||
const message = useMessage();
|
||||
/**
|
||||
* 定义参数变量
|
||||
*/
|
||||
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);
|
||||
|
||||
let treeItemKey = ref([]);
|
||||
|
||||
let expandedKeys = ref([]);
|
||||
|
||||
const treeData = ref([]);
|
||||
|
||||
const loading = ref(true);
|
||||
const subLoading = ref(false);
|
||||
const isEditMenu = ref(false);
|
||||
const treeItemTitle = ref('');
|
||||
const pattern = ref('');
|
||||
const drawerTitle = ref('');
|
||||
|
||||
const isAddSon = computed(() => {
|
||||
return !treeItemKey.value.length;
|
||||
});
|
||||
|
||||
const addMenuOptions = ref([
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '添加顶级菜单',
|
||||
key: 'home',
|
||||
disabled: false,
|
||||
label: '新增',
|
||||
type: 'info',
|
||||
icon: renderIcon(PlusOutlined),
|
||||
auth: ['sys:menu:add'],
|
||||
ifShow: () => {
|
||||
return record.type !== 1
|
||||
},
|
||||
onclick: handleAdd.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '添加子菜单',
|
||||
key: 'son',
|
||||
disabled: isAddSon,
|
||||
label: '编辑',
|
||||
type: 'warning',
|
||||
icon: renderIcon(FormOutlined),
|
||||
auth: ['sys:menu:update'],
|
||||
onclick: handleEdit.bind(null, record),
|
||||
},
|
||||
]);
|
||||
|
||||
const formParams = reactive({
|
||||
type: 1,
|
||||
label: '',
|
||||
subtitle: '',
|
||||
path: '',
|
||||
auth: '',
|
||||
openType: 1,
|
||||
{
|
||||
label: '删除',
|
||||
type: 'error',
|
||||
icon: renderIcon(DeleteOutlined),
|
||||
auth: ['sys:menu:delete'],
|
||||
onclick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
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('请填写完整信息');
|
||||
}
|
||||
/**
|
||||
* 获取菜单列表
|
||||
*/
|
||||
const loadDataTable = async (res) => {
|
||||
const data = await getMenuList();
|
||||
data.map((item) => {
|
||||
item.key = item.id;
|
||||
});
|
||||
const result = {
|
||||
records: buildTree(data),
|
||||
total: 1
|
||||
}
|
||||
return result
|
||||
};
|
||||
|
||||
function packHandle() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = unref(treeData).map((item: any) => item.key as string) as [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 执行添加
|
||||
*/
|
||||
const handleAdd = async (record: any) => {
|
||||
menuId.value = 0;
|
||||
pid.value = record ? record.parentId : 0;
|
||||
editVisible.value = true;
|
||||
await nextTick();
|
||||
createModalRef.value.openModal();
|
||||
|
||||
onMounted(async () => {
|
||||
const treeMenuList = await getMenuList();
|
||||
const keys = treeMenuList.list.map((item) => item.key);
|
||||
Object.assign(formParams, keys);
|
||||
treeData.value = treeMenuList.list;
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行编辑
|
||||
* @param data 参数
|
||||
*/
|
||||
const handleEdit = async (data: any) => {
|
||||
menuId.value = data.id;
|
||||
editVisible.value = true;
|
||||
await nextTick();
|
||||
createModalRef.value.openModal();
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行删除
|
||||
* @param id 菜单ID
|
||||
*/
|
||||
const handleDelete = (row: number) => {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
loading.value = true;
|
||||
await menuDelete(row.id);
|
||||
message.success('删除成功');
|
||||
loadDataTable()
|
||||
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>
|
||||
|
||||
<style scoped></style>
|
||||
|
Loading…
Reference in New Issue
Block a user