登录、字体图标组件、菜单管理

This commit is contained in:
陈红丽 2024-11-13 14:03:36 +08:00
parent bb1a41fb9a
commit 3a7c381580
13 changed files with 1118 additions and 758 deletions

View File

@ -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;

View File

@ -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';

View File

@ -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);
}

View File

@ -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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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,
}
];

View 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>

View File

@ -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>