perf: 合并用户平台框架修改
This commit is contained in:
2
.env
2
.env
@@ -35,5 +35,3 @@ VITE_SERVICE_EXPIRED_TOKEN_CODES=403
|
||||
|
||||
VITE_SERVICE_SERVER_ERROR_CODE=500
|
||||
|
||||
# when the route mode is static, the defined super role
|
||||
VITE_STATIC_SUPER_ROLE=R_SUPER
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "vue-antd",
|
||||
"name": "wanfi-system",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import {useAuthStore} from '@/store/modules/auth';
|
||||
|
||||
export function useAuth() {
|
||||
const authStore = useAuthStore();
|
||||
@@ -15,7 +15,20 @@ export function useAuth() {
|
||||
return codes.some(code => authStore.userInfo.buttons.includes(code));
|
||||
}
|
||||
|
||||
function hasRole(role: string | string[]) {
|
||||
if (!authStore.isLogin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof role === 'string') {
|
||||
return authStore.userInfo.roles.includes(role);
|
||||
}
|
||||
|
||||
return role.some(code => authStore.userInfo.roles.includes(code));
|
||||
}
|
||||
|
||||
return {
|
||||
hasAuth
|
||||
hasAuth,
|
||||
hasRole,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { computed } from 'vue';
|
||||
import { useCountDown, useLoading } from '@sa/hooks';
|
||||
import { $t } from '@/locales';
|
||||
import {REG_EMAIL} from '@/constants/reg';
|
||||
import {useAuthStore} from "@/store/modules/auth";
|
||||
import { REG_EMAIL } from '@/constants/reg';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
|
||||
export function useCaptcha() {
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
@@ -39,20 +39,17 @@ export function useCaptcha() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//获取验证码方法
|
||||
async function getCaptcha(email: string) {
|
||||
console.log(email)
|
||||
//const valid = isPhoneValid(phone);
|
||||
const valid = isEmailValid(email);
|
||||
const valid = isEmailValid(email);
|
||||
if (!valid || loading.value) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
startLoading();
|
||||
|
||||
await authStore.captcha(
|
||||
email,
|
||||
);
|
||||
const data = await authStore.captcha(email);
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
@@ -62,6 +59,7 @@ export function useCaptcha() {
|
||||
start();
|
||||
|
||||
endLoading();
|
||||
return data;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { router as globalRouter } from '@/router';
|
||||
import type {RouteLocationRaw} from 'vue-router';
|
||||
import {useRouter} from 'vue-router';
|
||||
import type {RouteKey} from '@elegant-router/types';
|
||||
import {router as globalRouter} from '@/router';
|
||||
|
||||
/**
|
||||
* Router push
|
||||
@@ -24,7 +24,7 @@ export function useRouterPush(inSetup = true) {
|
||||
}
|
||||
|
||||
async function routerPushByKey(key: RouteKey, options?: RouterPushOptions) {
|
||||
const { query, params } = options || {};
|
||||
const {query, params} = options || {};
|
||||
|
||||
const routeLocation: RouteLocationRaw = {
|
||||
name: key
|
||||
@@ -53,7 +53,6 @@ export function useRouterPush(inSetup = true) {
|
||||
*/
|
||||
async function toLogin(loginModule?: UnionKey.LoginModule, redirectUrl?: string) {
|
||||
const module = loginModule || 'pwd-login';
|
||||
|
||||
const options: RouterPushOptions = {
|
||||
params: {
|
||||
module
|
||||
@@ -77,7 +76,7 @@ export function useRouterPush(inSetup = true) {
|
||||
async function toggleLoginModule(module: UnionKey.LoginModule) {
|
||||
const query = route.value.query as Record<string, string>;
|
||||
|
||||
return routerPushByKey('login', { query, params: { module } });
|
||||
return routerPushByKey('login', {query, params: {module}});
|
||||
}
|
||||
|
||||
/** Redirect from login */
|
||||
|
||||
@@ -6,8 +6,8 @@ defineOptions({
|
||||
|
||||
<template>
|
||||
<DarkModeContainer class="h-full flex-center">
|
||||
<a href="https://github.com/honghuangdc/soybean-admin/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">
|
||||
Copyright MIT © 2021 Soybean
|
||||
<a href="#" target="_blank" rel="noopener noreferrer">
|
||||
Copyright © 2024 WANFi
|
||||
</a>
|
||||
</DarkModeContainer>
|
||||
</template>
|
||||
|
||||
@@ -37,13 +37,13 @@ function logout() {
|
||||
</ButtonIcon>
|
||||
<template #overlay>
|
||||
<AMenu>
|
||||
<AMenuItem @click="routerPushByKey('user-center')">
|
||||
<div class="flex-center gap-8px">
|
||||
<SvgIcon icon="ph:user-circle" class="text-icon" />
|
||||
{{ $t('common.userCenter') }}
|
||||
</div>
|
||||
</AMenuItem>
|
||||
<AMenuDivider />
|
||||
<!-- <AMenuItem @click="routerPushByKey('user-center')">-->
|
||||
<!-- <div class="flex-center gap-8px">-->
|
||||
<!-- <SvgIcon icon="ph:user-circle" class="text-icon" />-->
|
||||
<!-- {{ $t('common.userCenter') }}-->
|
||||
<!-- </div>-->
|
||||
<!-- </AMenuItem>-->
|
||||
<!-- <AMenuDivider />-->
|
||||
<AMenuItem @click="logout">
|
||||
<div class="flex-center gap-8px">
|
||||
<SvgIcon icon="ph:sign-out" class="text-icon" />
|
||||
|
||||
@@ -10,6 +10,7 @@ import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
|
||||
import { useMixMenuContext } from '../../context';
|
||||
import ThemeButton from './components/theme-button.vue';
|
||||
import UserAvatar from './components/user-avatar.vue';
|
||||
import {useAuth} from "@/hooks/business/auth";
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalHeader'
|
||||
@@ -29,6 +30,8 @@ defineProps<Props>();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const { hasRole } = useAuth();
|
||||
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
const { menus } = useMixMenuContext();
|
||||
|
||||
@@ -54,14 +57,14 @@ const headerMenus = computed(() => {
|
||||
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
|
||||
</div>
|
||||
<div class="h-full flex-y-center justify-end">
|
||||
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
||||
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
|
||||
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
||||
<ThemeSchemaSwitch
|
||||
:theme-schema="themeStore.themeScheme"
|
||||
:is-dark="themeStore.darkMode"
|
||||
@switch="themeStore.toggleThemeScheme"
|
||||
/>
|
||||
<ThemeButton />
|
||||
<ThemeButton v-if="hasRole('super')" />
|
||||
<UserAvatar />
|
||||
</div>
|
||||
</DarkModeContainer>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
const viewEn: any = {
|
||||
"view.endpoint": "endpoint",
|
||||
"view.endpoint_access": "current",
|
||||
"view.endpoint_records": "Historical",
|
||||
"view.billing": "Billing",
|
||||
"view.billing_histories": "Historical",
|
||||
"view.set-meal": "Set Meal",
|
||||
};
|
||||
|
||||
const local: any = {
|
||||
system: {
|
||||
title: 'WANFi Platform'
|
||||
@@ -228,6 +237,17 @@ const local: any = {
|
||||
birthDate: 'Birth Date',
|
||||
birthDatePlaceholder: 'Please select birth date',
|
||||
birthDateRequired: 'Please select birth date',
|
||||
usernameLengthLimit:"Username is too short",
|
||||
usernameExists:"The username is already registered",
|
||||
usernameRequired:"The username cannot be empty",
|
||||
phoneInvalid:"The mobile phone number is incorrect",
|
||||
phoneExists:"The mobile phone number has been registered",
|
||||
emailInvalid:"The email is incorrect",
|
||||
emailExists:"The email has been registered",
|
||||
emailRequired:"The email cannot be empty",
|
||||
codeRequired:"The code cannot be empty",
|
||||
passwordRequired:"The password cannot be empty",
|
||||
passwordLength:"The password is too short",
|
||||
},
|
||||
resetPwd: {
|
||||
title: 'Reset Password'
|
||||
@@ -239,7 +259,7 @@ const local: any = {
|
||||
|
||||
about: {
|
||||
title: 'About',
|
||||
introduction: `Soybean Admin is an elegant and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite5, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. Soybean Admin provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.`,
|
||||
introduction: `Admin is an elegant and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite5, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. WANFI Admin provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.`,
|
||||
projectInfo: {
|
||||
title: 'Project Info',
|
||||
version: 'Version',
|
||||
@@ -269,11 +289,11 @@ const local: any = {
|
||||
projectNews: {
|
||||
title: 'Project News',
|
||||
moreNews: 'More News',
|
||||
desc1: 'Soybean created the open source project soybean-admin on May 28, 2021!',
|
||||
desc2: 'zyh submitted a bug to soybean-admin, the multi-tab bar will not adapt.',
|
||||
desc3: 'Soybean is ready to do sufficient preparation for the release of soybean-admin!',
|
||||
desc4: 'Soybean is busy writing project documentation for soybean-admin!',
|
||||
desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!'
|
||||
desc1: 'created the open source project WANFI-admin on May 28, 2021!',
|
||||
desc2: 'zyh submitted a bug to WANFI-admin, the multi-tab bar will not adapt.',
|
||||
desc3: 'WANFI is ready to do sufficient preparation for the release of WANFI-admin!',
|
||||
desc4: 'WANFI is busy writing project documentation for WANFI-admin!',
|
||||
desc5: 'WANFI just wrote some of the workbench pages casually, and it was enough to see!'
|
||||
},
|
||||
creativity: 'Creativity'
|
||||
},
|
||||
@@ -511,7 +531,8 @@ const local: any = {
|
||||
expand: 'Expand Menu',
|
||||
pin: 'Pin',
|
||||
unpin: 'Unpin'
|
||||
}
|
||||
},
|
||||
...viewEn
|
||||
};
|
||||
|
||||
export default local;
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
const viewZh: any = {
|
||||
"view.endpoint": "终端设备",
|
||||
"view.endpoint_access": "当前设备",
|
||||
"view.endpoint_records": "历史设备",
|
||||
"view.billing": "账单",
|
||||
"view.billing_histories": "历史查询",
|
||||
"view.set-meal": "套餐",
|
||||
};
|
||||
|
||||
const local:any = {
|
||||
system: {
|
||||
title: 'WANFi 平台',
|
||||
@@ -228,6 +237,17 @@ const local:any = {
|
||||
birthDate: '出生日期',
|
||||
birthDatePlaceholder: '请选择出生日期',
|
||||
birthDateRequired: '请选择出生日期',
|
||||
usernameLengthLimit:"用户名太短",
|
||||
usernameExists:"用户名已经注册",
|
||||
usernameRequired:"用户名不能为空",
|
||||
phoneInvalid:"手机号格式不正确",
|
||||
phoneExists:"手机号已经注册",
|
||||
emailInvalid:"邮箱格式不正确",
|
||||
emailExists:"邮箱已经注册",
|
||||
emailRequired:"邮箱不能为空",
|
||||
codeRequired:"验证码不能为空",
|
||||
passwordRequired:"密码不能为空",
|
||||
passwordLength:"密码太短",
|
||||
},
|
||||
resetPwd: {
|
||||
title: '重置密码'
|
||||
@@ -239,7 +259,7 @@ const local:any = {
|
||||
|
||||
about: {
|
||||
title: '关于',
|
||||
introduction: `Soybean Admin 是一个优雅且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件,代码规范严谨,实现了自动化的文件路由系统。此外,它还采用了基于 ApiFox 的在线Mock数据方案。Soybean Admin 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。`,
|
||||
introduction: `WANFI Admin 是一个优雅且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件,代码规范严谨,实现了自动化的文件路由系统。此外,它还采用了基于 ApiFox 的在线Mock数据方案。WANFI Admin 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。`,
|
||||
projectInfo: {
|
||||
title: '项目信息',
|
||||
version: '版本',
|
||||
@@ -269,11 +289,11 @@ const local:any = {
|
||||
projectNews: {
|
||||
title: '项目动态',
|
||||
moreNews: '更多动态',
|
||||
desc1: 'Soybean 在2021年5月28日创建了开源项目 soybean-admin!',
|
||||
desc2: 'soybean-admin 提交了一个bug,多标签栏不会自适应。',
|
||||
desc3: 'Soybean 准备为 soybean-admin 的发布做充分的准备工作!',
|
||||
desc4: 'Soybean 正在忙于为soybean-admin写项目说明文档!',
|
||||
desc5: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!'
|
||||
desc1: 'WANFI 在2021年5月28日创建了开源项目 WANFI-admin!',
|
||||
desc2: 'WANFI-admin 提交了一个bug,多标签栏不会自适应。',
|
||||
desc3: 'WANFI 准备为 WANFI-admin 的发布做充分的准备工作!',
|
||||
desc4: 'WANFI 正在忙于为WANFI-admin写项目说明文档!',
|
||||
desc5: 'WANFI 刚才把工作台页面随便写了一些,凑合能看了!'
|
||||
},
|
||||
creativity: '创意'
|
||||
},
|
||||
@@ -511,7 +531,8 @@ const local:any = {
|
||||
expand: '展开菜单',
|
||||
pin: '固定',
|
||||
unpin: '取消固定'
|
||||
}
|
||||
},
|
||||
...viewZh
|
||||
};
|
||||
|
||||
export default local;
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
|
||||
import type { GeneratedRoute } from '@elegant-router/types';
|
||||
|
||||
/**
|
||||
* custom routes
|
||||
*
|
||||
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
|
||||
*/
|
||||
export const generatedRoutes: GeneratedRoute[] = [
|
||||
{
|
||||
name: '403',
|
||||
@@ -40,16 +45,42 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
component: 'layout.base$view.about',
|
||||
name: 'home',
|
||||
path: '/home',
|
||||
component: 'layout.base$view.home',
|
||||
meta: {
|
||||
title: 'about',
|
||||
i18nKey: 'route.about',
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 10
|
||||
title: 'home',
|
||||
i18nKey: 'route.home',
|
||||
icon: 'mdi:monitor-dashboard',
|
||||
order: 0,
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
|
||||
component: 'layout.blank$view._builtin_login',
|
||||
props: true,
|
||||
meta: {
|
||||
title: 'login',
|
||||
i18nKey: 'route.login',
|
||||
constant: true,
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
// {
|
||||
// name: 'user-center',
|
||||
// path: '/user-center',
|
||||
// component: 'layout.base$view.user-center',
|
||||
// meta: {
|
||||
// title: 'user-center',
|
||||
// hideInMenu: true,
|
||||
// constant: true,
|
||||
// keepAlive: false,
|
||||
// i18nKey: 'route.user-center'
|
||||
// }
|
||||
// },
|
||||
|
||||
// 下面是指定角色才有
|
||||
{
|
||||
name: 'function',
|
||||
path: '/function',
|
||||
@@ -58,7 +89,8 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
title: 'function',
|
||||
i18nKey: 'route.function',
|
||||
icon: 'icon-park-outline:all-application',
|
||||
order: 6
|
||||
order: 1002,
|
||||
roles: ['super']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -141,7 +173,6 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
i18nKey: 'route.function_super-page',
|
||||
icon: 'ic:round-supervisor-account',
|
||||
order: 5,
|
||||
roles: ['R_SUPER']
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -169,38 +200,89 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'home',
|
||||
path: '/home',
|
||||
component: 'layout.base$view.home',
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
component: 'layout.base$view.about',
|
||||
meta: {
|
||||
title: 'home',
|
||||
i18nKey: 'route.home',
|
||||
icon: 'mdi:monitor-dashboard',
|
||||
order: 1
|
||||
title: 'about',
|
||||
i18nKey: 'route.about',
|
||||
icon: 'fluent:book-information-24-regular',
|
||||
order: 1003,
|
||||
roles: ['super']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
|
||||
component: 'layout.blank$view._builtin_login',
|
||||
props: true,
|
||||
meta: {
|
||||
title: 'login',
|
||||
i18nKey: 'route.login',
|
||||
constant: true,
|
||||
hideInMenu: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'user-center',
|
||||
path: '/user-center',
|
||||
component: 'layout.base$view.user-center',
|
||||
meta: {
|
||||
title: 'user-center',
|
||||
hideInMenu: true,
|
||||
constant: true,
|
||||
keepAlive: false,
|
||||
i18nKey: 'route.user-center'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
/**自定义静态菜单 */
|
||||
export const customRoutes: GeneratedRoute[] = [
|
||||
{
|
||||
name: 'endpoint',
|
||||
path: '/endpoint',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: '终端设备',
|
||||
i18nKey: 'view.endpoint',
|
||||
icon: 'icon-park-outline:all-application',
|
||||
order: 11,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'endpoint_access',
|
||||
path: '/endpoint/access',
|
||||
component: 'view.endpoint_access',
|
||||
meta: {
|
||||
title: '当前',
|
||||
i18nKey: 'view.endpoint_access',
|
||||
icon: 'material-symbols:filter-list-off',
|
||||
order: 1
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'endpoint_records',
|
||||
path: '/endpoint/records',
|
||||
component: 'view.endpoint_records',
|
||||
meta: {
|
||||
title: '历史',
|
||||
i18nKey: 'view.endpoint_records',
|
||||
icon: 'ic:round-supervisor-account',
|
||||
order: 3,
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'billing',
|
||||
path: '/billing',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: '账单',
|
||||
i18nKey: 'view.billing',
|
||||
icon: 'icon-park-outline:all-application',
|
||||
order: 13,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'billing_histories',
|
||||
path: '/billing/histories',
|
||||
component: 'view.billing_histories',
|
||||
meta: {
|
||||
title: '历史查询',
|
||||
i18nKey: 'view.billing_histories',
|
||||
icon: 'material-symbols:filter-list-off',
|
||||
order: 2
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'set-meal',
|
||||
path: '/set-meal',
|
||||
component: 'layout.base$view.set-meal',
|
||||
meta: {
|
||||
title: '套餐',
|
||||
i18nKey: 'view.set-meal',
|
||||
icon: 'icon-park-outline:all-application',
|
||||
order: 15,
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// Generated by elegant-router
|
||||
// Read more: https://github.com/soybeanjs/elegant-router
|
||||
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import type { ElegantConstRoute } from '@elegant-router/vue';
|
||||
import type { RouteKey, RouteMap, RoutePath } from '@elegant-router/types';
|
||||
import type {RouteRecordRaw} from 'vue-router';
|
||||
import type {ElegantConstRoute} from '@elegant-router/vue';
|
||||
import type {RouteKey, RouteMap} from '@elegant-router/types';
|
||||
import BaseLayout from '@/layouts/base-layout/index.vue';
|
||||
import BlankLayout from '@/layouts/blank-layout/index.vue';
|
||||
|
||||
@@ -84,14 +84,14 @@ function transformElegantRouteToVueRoute(
|
||||
route.props = true;
|
||||
}
|
||||
|
||||
const { name, path, component, children, ...rest } = route;
|
||||
const {name, path, component, children, ...rest} = route;
|
||||
|
||||
const vueRoute = { name, path, ...rest } as RouteRecordRaw;
|
||||
const vueRoute = {name, path, ...rest} as RouteRecordRaw;
|
||||
|
||||
try {
|
||||
if (component) {
|
||||
if (isSingleLevelRoute(route)) {
|
||||
const { layout, view } = getSingleLevelRouteComponent(component);
|
||||
const {layout, view} = getSingleLevelRouteComponent(component);
|
||||
|
||||
const singleLevelRoute: RouteRecordRaw = {
|
||||
path,
|
||||
@@ -208,18 +208,5 @@ const routeMap: RouteMap = {
|
||||
* @param name route name
|
||||
*/
|
||||
export function getRoutePath<T extends RouteKey>(name: T) {
|
||||
console.log(name);
|
||||
return routeMap[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* get route name by route path
|
||||
* @param path route path
|
||||
*/
|
||||
export function getRouteName(path: RoutePath) {
|
||||
const routeEntries = Object.entries(routeMap) as [RouteKey, RoutePath][];
|
||||
|
||||
const routeName: RouteKey | null = routeEntries.find(([, routePath]) => routePath === path)?.[0] || null;
|
||||
|
||||
return routeName;
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ export function createRouteGuard(router: Router) {
|
||||
const needLogin = !to.meta.constant;
|
||||
const routeRoles = to.meta.roles || [];
|
||||
|
||||
// const hasRole = authStore.userInfo.roles?.some(role => routeRoles.includes(role));
|
||||
const hasAuth = authStore.isStaticSuper || !routeRoles.length;
|
||||
const hasRole = authStore.userInfo.roles?.some(role => routeRoles.includes(role));
|
||||
const hasAuth = !routeRoles.length || hasRole
|
||||
|
||||
const routeSwitches: CommonType.StrategicPattern[] = [
|
||||
// if it is login route when logged in, then switch to the root page
|
||||
|
||||
@@ -1,57 +1,6 @@
|
||||
import type { ElegantConstRoute } from '@elegant-router/types';
|
||||
import { generatedRoutes } from '../elegant/routes';
|
||||
import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
|
||||
|
||||
/**
|
||||
* custom routes
|
||||
*
|
||||
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
|
||||
*/
|
||||
const customRoutes: any[] = [
|
||||
{
|
||||
name: 'exception',
|
||||
path: '/exception',
|
||||
component: 'layout.base',
|
||||
meta: {
|
||||
title: 'exception',
|
||||
i18nKey: 'route.exception',
|
||||
icon: 'ant-design:exception-outlined',
|
||||
order: 7
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'exception_403',
|
||||
path: '/exception/403',
|
||||
component: 'view._builtin_403',
|
||||
meta: {
|
||||
title: 'exception_403',
|
||||
i18nKey: 'route.exception_403',
|
||||
icon: 'ic:baseline-block'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'exception_404',
|
||||
path: '/exception/404',
|
||||
component: 'view._builtin_404',
|
||||
meta: {
|
||||
title: 'exception_404',
|
||||
i18nKey: 'route.exception_404',
|
||||
icon: 'ic:baseline-web-asset-off'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'exception_500',
|
||||
path: '/exception/500',
|
||||
component: 'view._builtin_500',
|
||||
meta: {
|
||||
title: 'exception_500',
|
||||
i18nKey: 'route.exception_500',
|
||||
icon: 'ic:baseline-wifi-off'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
import type {ElegantConstRoute} from '@elegant-router/types';
|
||||
import {customRoutes, generatedRoutes} from '../elegant/routes';
|
||||
import {transformElegantRoutesToVueRoutes} from '../elegant/transform';
|
||||
|
||||
/** create routes when the auth route mode is static */
|
||||
export function createStaticRoutes() {
|
||||
@@ -59,7 +8,7 @@ export function createStaticRoutes() {
|
||||
|
||||
const authRoutes: any[] = [];
|
||||
|
||||
[...customRoutes, ...generatedRoutes].forEach(item => {
|
||||
[...generatedRoutes, ...customRoutes].forEach(item => {
|
||||
if (item.meta?.constant) {
|
||||
constantRoutes.push(item);
|
||||
} else {
|
||||
|
||||
@@ -13,14 +13,23 @@ export function fetchLogin(body: Api.Auth.LoginBody) {
|
||||
data: body
|
||||
});
|
||||
}
|
||||
|
||||
//邮箱验证码接口
|
||||
export function sendCaptcha(body:Api.Auth.EmailCaptcha){
|
||||
export function sendCaptcha(body: Api.Auth.EmailCaptcha) {
|
||||
return request({
|
||||
url:`/system/email/code?email=${body.email}`,
|
||||
method:'get',
|
||||
})
|
||||
url: `/code?email=${body.email}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
//验证注册
|
||||
export function doCheckUserRepeat(body: Api.Auth.CheckBody) {
|
||||
return request<boolean>({
|
||||
url: '/auth/checkRepeat',
|
||||
method: 'post',
|
||||
data: body
|
||||
});
|
||||
}
|
||||
|
||||
//添加注册
|
||||
export function fetchRegister(body: Api.Auth.RegisterBody) {
|
||||
@@ -30,6 +39,7 @@ export function fetchRegister(body: Api.Auth.RegisterBody) {
|
||||
data: body
|
||||
});
|
||||
}
|
||||
|
||||
/** logout */
|
||||
export function doDeleteLogout() {
|
||||
return request<App.Service.Response<null>>({
|
||||
|
||||
@@ -7,7 +7,7 @@ import { localStg } from '@/utils/storage';
|
||||
import { $t } from '@/locales';
|
||||
import { useRouteStore } from '../route';
|
||||
import { clearAuthStorage, emptyInfo, getToken } from './shared';
|
||||
import {sendCaptcha} from "@/service/api/auth";
|
||||
import { doCheckUserRepeat, sendCaptcha } from '@/service/api/auth';
|
||||
|
||||
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
const routeStore = useRouteStore();
|
||||
@@ -31,13 +31,6 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
}
|
||||
);
|
||||
|
||||
/** is super role in static route */
|
||||
const isStaticSuper = computed(() => {
|
||||
const { VITE_AUTH_ROUTE_MODE, VITE_STATIC_SUPER_ROLE } = import.meta.env;
|
||||
|
||||
return VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles?.includes(VITE_STATIC_SUPER_ROLE);
|
||||
});
|
||||
|
||||
/** Is login */
|
||||
const isLogin = computed(() => Boolean(token.value));
|
||||
|
||||
@@ -122,6 +115,15 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户信息是否已存在
|
||||
*/
|
||||
async function checkUserRepeat(checkForm: Api.Auth.CheckBody) {
|
||||
const { data, error } = await doCheckUserRepeat(checkForm);
|
||||
return { exists: data, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new user
|
||||
*/
|
||||
@@ -139,22 +141,18 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
endLoading();
|
||||
return !error;
|
||||
}
|
||||
async function captcha(email:string){
|
||||
|
||||
async function captcha(email: string) {
|
||||
if (!email) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await sendCaptcha({ email }); // 这里调用后端接口发送验证码
|
||||
} catch (error) {
|
||||
|
||||
return null;
|
||||
}
|
||||
const { data, error } = await sendCaptcha({ email }); // 这里调用后端接口发送验证码
|
||||
return { data, error };
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
userInfo,
|
||||
isStaticSuper,
|
||||
isLogin,
|
||||
loginLoading,
|
||||
resetStore,
|
||||
@@ -163,5 +161,6 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
refreshUserInfo,
|
||||
register,
|
||||
captcha,
|
||||
checkUserRepeat
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { computed, ref, shallowRef } from 'vue';
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey } from '@elegant-router/types';
|
||||
import { SetupStoreId } from '@/enum';
|
||||
import { router } from '@/router';
|
||||
import { createStaticRoutes, getAuthVueRoutes } from '@/router/routes';
|
||||
import { ROOT_ROUTE } from '@/router/routes/builtin';
|
||||
import { getRoutePath } from '@/router/elegant/transform';
|
||||
import { useAppStore } from '../app';
|
||||
import { useAuthStore } from '../auth';
|
||||
import { useTabStore } from '../tab';
|
||||
import {computed, ref, shallowRef} from 'vue';
|
||||
import type {RouteRecordRaw} from 'vue-router';
|
||||
import {defineStore} from 'pinia';
|
||||
import {useBoolean} from '@sa/hooks';
|
||||
import type {CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey} from '@elegant-router/types';
|
||||
import {SetupStoreId} from '@/enum';
|
||||
import {router} from '@/router';
|
||||
import {createStaticRoutes, getAuthVueRoutes} from '@/router/routes';
|
||||
import {ROOT_ROUTE} from '@/router/routes/builtin';
|
||||
import {getRoutePath} from '@/router/elegant/transform';
|
||||
import {useAppStore} from '../app';
|
||||
import {useAuthStore} from '../auth';
|
||||
import {useTabStore} from '../tab';
|
||||
import {
|
||||
filterAuthRoutesByRoles,
|
||||
getBreadcrumbsByRoute,
|
||||
@@ -26,8 +26,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
const appStore = useAppStore();
|
||||
const authStore = useAuthStore();
|
||||
const tabStore = useTabStore();
|
||||
const { bool: isInitConstantRoute, setBool: setIsInitConstantRoute } = useBoolean();
|
||||
const { bool: isInitAuthRoute, setBool: setIsInitAuthRoute } = useBoolean();
|
||||
const {bool: isInitConstantRoute, setBool: setIsInitConstantRoute} = useBoolean();
|
||||
const {bool: isInitAuthRoute, setBool: setIsInitAuthRoute} = useBoolean();
|
||||
|
||||
|
||||
/** Home route key */
|
||||
@@ -146,17 +146,9 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
async function initConstantRoute() {
|
||||
if (isInitConstantRoute.value) return;
|
||||
|
||||
// if (authRouteMode.value === 'static') {
|
||||
const { constantRoutes } = createStaticRoutes();
|
||||
const {constantRoutes} = createStaticRoutes();
|
||||
|
||||
addAuthRoutes(constantRoutes);
|
||||
// } else {
|
||||
// const { data, error } = await fetchGetConstantRoutes();
|
||||
|
||||
// if (!error) {
|
||||
// addAuthRoutes(data);
|
||||
// }
|
||||
// }
|
||||
|
||||
handleAuthRoutes();
|
||||
|
||||
@@ -165,43 +157,24 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
|
||||
/** Init auth route */
|
||||
async function initAuthRoute() {
|
||||
await initStaticAuthRoute();
|
||||
await initDynamicAuthRoute();
|
||||
tabStore.initHomeTab();
|
||||
}
|
||||
|
||||
/** Init static auth route */
|
||||
async function initStaticAuthRoute() {
|
||||
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
|
||||
if (authStore.isStaticSuper) {
|
||||
addAuthRoutes(staticAuthRoutes);
|
||||
} else {
|
||||
const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles ?? []);
|
||||
|
||||
addAuthRoutes(filteredAuthRoutes);
|
||||
}
|
||||
|
||||
handleAuthRoutes();
|
||||
|
||||
setIsInitAuthRoute(true);
|
||||
}
|
||||
|
||||
/** Init dynamic auth route */
|
||||
async function initDynamicAuthRoute() {
|
||||
const { data: routes, error } = await doGetUserRoutes();
|
||||
const {data: routes, error} = await doGetUserRoutes();
|
||||
if (!error) {
|
||||
addAuthRoutes(routes);
|
||||
const {authRoutes: staticAuthRoutes} = createStaticRoutes();
|
||||
const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles ?? []);
|
||||
addAuthRoutes(filteredAuthRoutes.concat(routes));
|
||||
|
||||
handleAuthRoutes();
|
||||
|
||||
setRouteHome('home');
|
||||
|
||||
handleUpdateRootRouteRedirect('manage_role');
|
||||
handleUpdateRootRouteRedirect('home');
|
||||
|
||||
setIsInitAuthRoute(true);
|
||||
} else {
|
||||
await authStore.resetStore();
|
||||
}
|
||||
tabStore.initHomeTab();
|
||||
}
|
||||
|
||||
/** handle auth routes */
|
||||
@@ -249,7 +222,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
const redirect = getRoutePath(redirectKey);
|
||||
|
||||
if (redirect) {
|
||||
const rootRoute: CustomRoute = { ...ROOT_ROUTE, redirect };
|
||||
const rootRoute: CustomRoute = {...ROOT_ROUTE, redirect};
|
||||
|
||||
router.removeRoute(rootRoute.name);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ function sortRouteByOrder(route: ElegantConstRoute) {
|
||||
* @param routes routes
|
||||
*/
|
||||
export function sortRoutesByOrder(routes: ElegantConstRoute[]) {
|
||||
routes.sort((next, prev) => (Number(next.meta?.order) || 0) - (Number(prev.meta?.order) || 0));
|
||||
routes.sort((next, prev) => (Number(next.meta?.order) || 1) - (Number(prev.meta?.order) || 1));
|
||||
routes.forEach(sortRouteByOrder);
|
||||
|
||||
return routes;
|
||||
|
||||
@@ -4,7 +4,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
themeColor: '#646cff',
|
||||
otherColor: { info: '#2080f0', success: '#52c41a', warning: '#faad14', error: '#f5222d' },
|
||||
isInfoFollowPrimary: true,
|
||||
layout: { mode: 'horizontal-mix', scrollMode: 'content' },
|
||||
layout: { mode: 'vertical-mix', scrollMode: 'content' },
|
||||
page: { animate: true, animateMode: 'fade-slide' },
|
||||
header: { height: 56, breadcrumb: { visible: true, showIcon: true } },
|
||||
tab: { visible: false, cache: true, height: 44, mode: 'chrome' },
|
||||
|
||||
6
src/typings/api.d.ts
vendored
6
src/typings/api.d.ts
vendored
@@ -152,6 +152,12 @@ declare namespace Api {
|
||||
interface EmailCaptcha{
|
||||
email:string;
|
||||
}
|
||||
interface CheckBody{
|
||||
username?: string;
|
||||
email?: string;
|
||||
phonenumber?:string;
|
||||
authType: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
403
src/typings/app.d.ts
vendored
403
src/typings/app.d.ts
vendored
@@ -243,408 +243,7 @@ declare namespace App {
|
||||
};
|
||||
|
||||
type Schema = {
|
||||
system: {
|
||||
title: string;
|
||||
};
|
||||
common: {
|
||||
action: string;
|
||||
add: string;
|
||||
addSuccess: string;
|
||||
backToHome: string;
|
||||
batchDelete: string;
|
||||
cancel: string;
|
||||
close: string;
|
||||
check: string;
|
||||
columnSetting: string;
|
||||
config: string;
|
||||
confirm: string;
|
||||
delete: string;
|
||||
deleteSuccess: string;
|
||||
confirmDelete: string;
|
||||
edit: string;
|
||||
index: string;
|
||||
keywordSearch: string;
|
||||
logout: string;
|
||||
logoutConfirm: string;
|
||||
lookForward: string;
|
||||
modify: string;
|
||||
modifySuccess: string;
|
||||
noData: string;
|
||||
operate: string;
|
||||
pleaseCheckValue: string;
|
||||
refresh: string;
|
||||
reset: string;
|
||||
search: string;
|
||||
switch: string;
|
||||
tip: string;
|
||||
trigger: string;
|
||||
update: string;
|
||||
updateSuccess: string;
|
||||
userCenter: string;
|
||||
yesOrNo: {
|
||||
yes: string;
|
||||
no: string;
|
||||
};
|
||||
};
|
||||
request: {
|
||||
logout: string;
|
||||
logoutMsg: string;
|
||||
logoutWithModal: string;
|
||||
logoutWithModalMsg: string;
|
||||
refreshToken: string;
|
||||
tokenExpired: string;
|
||||
};
|
||||
theme: {
|
||||
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
||||
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
||||
themeColor: {
|
||||
title: string;
|
||||
followPrimary: string;
|
||||
} & Theme.ThemeColor;
|
||||
scrollMode: { title: string } & Record<UnionKey.ThemeScrollMode, string>;
|
||||
page: {
|
||||
animate: string;
|
||||
mode: { title: string } & Record<UnionKey.ThemePageAnimateMode, string>;
|
||||
};
|
||||
fixedHeaderAndTab: string;
|
||||
header: {
|
||||
height: string;
|
||||
breadcrumb: {
|
||||
visible: string;
|
||||
showIcon: string;
|
||||
};
|
||||
};
|
||||
tab: {
|
||||
visible: string;
|
||||
cache: string;
|
||||
height: string;
|
||||
mode: { title: string } & Record<UnionKey.ThemeTabMode, string>;
|
||||
};
|
||||
sider: {
|
||||
inverted: string;
|
||||
width: string;
|
||||
collapsedWidth: string;
|
||||
mixWidth: string;
|
||||
mixCollapsedWidth: string;
|
||||
mixChildMenuWidth: string;
|
||||
};
|
||||
footer: {
|
||||
visible: string;
|
||||
fixed: string;
|
||||
height: string;
|
||||
right: string;
|
||||
};
|
||||
themeDrawerTitle: string;
|
||||
pageFunTitle: string;
|
||||
configOperation: {
|
||||
copyConfig: string;
|
||||
copySuccessMsg: string;
|
||||
resetConfig: string;
|
||||
resetSuccessMsg: string;
|
||||
};
|
||||
};
|
||||
route: Record<I18nRouteKey, string>;
|
||||
page: {
|
||||
login: {
|
||||
common: {
|
||||
loginOrRegister: string;
|
||||
userNamePlaceholder: string;
|
||||
phonePlaceholder: string;
|
||||
codePlaceholder: string;
|
||||
passwordPlaceholder: string;
|
||||
confirmPasswordPlaceholder: string;
|
||||
codeLogin: string;
|
||||
confirm: string;
|
||||
back: string;
|
||||
validateSuccess: string;
|
||||
loginSuccess: string;
|
||||
registerSuccess: string;
|
||||
welcomeBack: string;
|
||||
checkCode: string;
|
||||
};
|
||||
pwdLogin: {
|
||||
title: string;
|
||||
rememberMe: string;
|
||||
forgetPassword: string;
|
||||
register: string;
|
||||
otherAccountLogin: string;
|
||||
otherLoginMode: string;
|
||||
superAdmin: string;
|
||||
admin: string;
|
||||
user: string;
|
||||
};
|
||||
codeLogin: {
|
||||
title: string;
|
||||
getCode: string;
|
||||
reGetCode: string;
|
||||
sendCodeSuccess: string;
|
||||
imageCodePlaceholder: string;
|
||||
};
|
||||
register: {
|
||||
title: string;
|
||||
agreement: string;
|
||||
protocol: string;
|
||||
policy: string;
|
||||
};
|
||||
resetPwd: {
|
||||
title: string;
|
||||
};
|
||||
bindWeChat: {
|
||||
title: string;
|
||||
};
|
||||
};
|
||||
about: {
|
||||
title: string;
|
||||
introduction: string;
|
||||
projectInfo: {
|
||||
title: string;
|
||||
version: string;
|
||||
latestBuildTime: string;
|
||||
githubLink: string;
|
||||
previewLink: string;
|
||||
};
|
||||
prdDep: string;
|
||||
devDep: string;
|
||||
};
|
||||
home: {
|
||||
greeting: string;
|
||||
weatherDesc: string;
|
||||
projectCount: string;
|
||||
todo: string;
|
||||
message: string;
|
||||
downloadCount: string;
|
||||
registerCount: string;
|
||||
schedule: string;
|
||||
study: string;
|
||||
work: string;
|
||||
rest: string;
|
||||
entertainment: string;
|
||||
visitCount: string;
|
||||
turnover: string;
|
||||
dealCount: string;
|
||||
projectNews: {
|
||||
title: string;
|
||||
moreNews: string;
|
||||
desc1: string;
|
||||
desc2: string;
|
||||
desc3: string;
|
||||
desc4: string;
|
||||
desc5: string;
|
||||
};
|
||||
creativity: string;
|
||||
};
|
||||
function: {
|
||||
tab: {
|
||||
tabOperate: {
|
||||
title: string;
|
||||
addTab: string;
|
||||
addTabDesc: string;
|
||||
closeTab: string;
|
||||
closeCurrentTab: string;
|
||||
closeAboutTab: string;
|
||||
addMultiTab: string;
|
||||
addMultiTabDesc1: string;
|
||||
addMultiTabDesc2: string;
|
||||
};
|
||||
tabTitle: {
|
||||
title: string;
|
||||
changeTitle: string;
|
||||
change: string;
|
||||
resetTitle: string;
|
||||
reset: string;
|
||||
};
|
||||
};
|
||||
multiTab: {
|
||||
routeParam: string;
|
||||
backTab: string;
|
||||
};
|
||||
toggleAuth: {
|
||||
toggleAccount: string;
|
||||
authHook: string;
|
||||
superAdminVisible: string;
|
||||
adminVisible: string;
|
||||
adminOrUserVisible: string;
|
||||
};
|
||||
};
|
||||
manage: {
|
||||
common: {
|
||||
status: {
|
||||
enable: string;
|
||||
disable: string;
|
||||
};
|
||||
};
|
||||
role: {
|
||||
title: string;
|
||||
roleName: string;
|
||||
roleCode: string;
|
||||
roleStatus: string;
|
||||
roleDesc: string;
|
||||
form: {
|
||||
roleName: string;
|
||||
roleCode: string;
|
||||
roleStatus: string;
|
||||
roleDesc: string;
|
||||
};
|
||||
addRole: string;
|
||||
editRole: string;
|
||||
menuAuth: string;
|
||||
buttonAuth: string;
|
||||
};
|
||||
user: {
|
||||
userName: string;
|
||||
nickName: string;
|
||||
email: string;
|
||||
phonenumber: string;
|
||||
status: string;
|
||||
dept: string;
|
||||
title: string;
|
||||
addUser: string;
|
||||
remark: string;
|
||||
editUser: string;
|
||||
post: string;
|
||||
role: string;
|
||||
password: string;
|
||||
form: {
|
||||
userName: string;
|
||||
email: string;
|
||||
status: string;
|
||||
nickName: string;
|
||||
phonenumber: string;
|
||||
remark: string;
|
||||
password: string;
|
||||
dept: string;
|
||||
};
|
||||
};
|
||||
menu: {
|
||||
home: string;
|
||||
title: string;
|
||||
id: string;
|
||||
parentId: string;
|
||||
menuType: string;
|
||||
menuName: string;
|
||||
routeName: string;
|
||||
routePath: string;
|
||||
routeParams: string;
|
||||
layout: string;
|
||||
page: string;
|
||||
i18nKey: string;
|
||||
icon: string;
|
||||
localIcon: string;
|
||||
iconTypeTitle: string;
|
||||
order: string;
|
||||
keepAlive: string;
|
||||
href: string;
|
||||
hideInMenu: string;
|
||||
activeMenu: string;
|
||||
multiTab: string;
|
||||
fixedIndexInTab: string;
|
||||
button: string;
|
||||
buttonCode: string;
|
||||
buttonDesc: string;
|
||||
menuStatus: string;
|
||||
form: {
|
||||
home: string;
|
||||
menuType: string;
|
||||
menuName: string;
|
||||
routeName: string;
|
||||
routePath: string;
|
||||
layout: string;
|
||||
page: string;
|
||||
i18nKey: string;
|
||||
icon: string;
|
||||
localIcon: string;
|
||||
order: string;
|
||||
keepAlive: string;
|
||||
href: string;
|
||||
hideInMenu: string;
|
||||
activeMenu: string;
|
||||
multiTab: string;
|
||||
fixedInTab: string;
|
||||
fixedIndexInTab: string;
|
||||
button: string;
|
||||
buttonCode: string;
|
||||
buttonDesc: string;
|
||||
menuStatus: string;
|
||||
};
|
||||
addMenu: string;
|
||||
editMenu: string;
|
||||
addChildMenu: string;
|
||||
type: {
|
||||
directory: string;
|
||||
menu: string;
|
||||
};
|
||||
iconType: {
|
||||
iconify: string;
|
||||
local: string;
|
||||
};
|
||||
};
|
||||
dept: {
|
||||
deptName: string;
|
||||
leader: string;
|
||||
status: string;
|
||||
form: {
|
||||
deptName: string;
|
||||
leader: string;
|
||||
status: string;
|
||||
};
|
||||
};
|
||||
post: {
|
||||
addPost: string;
|
||||
editPost: string;
|
||||
postName: string;
|
||||
postCode: string;
|
||||
postSort: string;
|
||||
status: string;
|
||||
remark: string;
|
||||
title: string;
|
||||
form: {
|
||||
postName: string;
|
||||
postCode: string;
|
||||
postSort: string;
|
||||
remark: string;
|
||||
status: string;
|
||||
};
|
||||
};
|
||||
|
||||
dict: {
|
||||
title: string;
|
||||
dictName: string;
|
||||
dictType: string;
|
||||
status: string;
|
||||
remark: string;
|
||||
form: {
|
||||
dictName: string;
|
||||
dictType: string;
|
||||
status: string;
|
||||
remark: string;
|
||||
};
|
||||
addDict: string;
|
||||
editDict: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
form: {
|
||||
required: string;
|
||||
username: FormMsg;
|
||||
phone: FormMsg;
|
||||
pwd: FormMsg;
|
||||
confirmPwd: FormMsg;
|
||||
code: FormMsg;
|
||||
email: FormMsg;
|
||||
};
|
||||
dropdown: Record<Global.DropdownKey, string>;
|
||||
icon: {
|
||||
themeConfig: string;
|
||||
themeSchema: string;
|
||||
lang: string;
|
||||
fullscreen: string;
|
||||
fullscreenExit: string;
|
||||
reload: string;
|
||||
collapse: string;
|
||||
expand: string;
|
||||
pin: string;
|
||||
unpin: string;
|
||||
};
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type GetI18nKey<T extends Record<string, unknown>, K extends keyof T = keyof T> = K extends string
|
||||
|
||||
1
src/typings/auto-imports.d.ts
vendored
1
src/typings/auto-imports.d.ts
vendored
@@ -53,6 +53,7 @@ declare global {
|
||||
const doAddDict: typeof import('../service/api/dict')['doAddDict']
|
||||
const doAddMenu: typeof import('../service/api/menu')['doAddMenu']
|
||||
const doAddPost: typeof import('../service/api/post')['doAddPost']
|
||||
const doCheckUserRepeat: typeof import('../service/api/auth')['doCheckUserRepeat']
|
||||
const doDeleteDept: typeof import('../service/api/dept')['doDeleteDept']
|
||||
const doDeleteDict: typeof import('../service/api/dict')['doDeleteDict']
|
||||
const doDeleteLogout: typeof import('../service/api/auth')['doDeleteLogout']
|
||||
|
||||
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@@ -30,7 +30,6 @@ declare module 'vue' {
|
||||
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
|
||||
3
src/typings/env.d.ts
vendored
3
src/typings/env.d.ts
vendored
@@ -55,8 +55,7 @@ declare namespace Env {
|
||||
* use "," to separate multiple codes
|
||||
*/
|
||||
readonly VITE_SERVICE_EXPIRED_TOKEN_CODES: string;
|
||||
/** when the route mode is static, the defined super role */
|
||||
readonly VITE_STATIC_SUPER_ROLE: string;
|
||||
|
||||
/**
|
||||
* other backend service base url
|
||||
*
|
||||
|
||||
@@ -83,15 +83,15 @@ async function getCheckCode() {
|
||||
<AButton type="primary" block size="large" shape="round" :loading="authStore.loginLoading" @click="handleSubmit">
|
||||
{{ $t('common.confirm') }}
|
||||
</AButton>
|
||||
<div class="flex-y-center justify-between">
|
||||
<!-- <div class="flex-y-center justify-between">-->
|
||||
<!-- <AButton class="h-34px flex-1" block @click="toggleLoginModule('code-login')">-->
|
||||
<!-- {{ t(loginModuleRecord['code-login']) }}-->
|
||||
<!-- </AButton>-->
|
||||
<div class="w-12px"></div>
|
||||
<AButton class="h-34px flex-1" block @click="toggleLoginModule('register')">
|
||||
{{ t(loginModuleRecord.register) }}
|
||||
</AButton>
|
||||
</div>
|
||||
<!-- <div class="w-12px"></div>-->
|
||||
<!-- <AButton class="h-34px flex-1" block @click="toggleLoginModule('register')">-->
|
||||
<!-- {{ t(loginModuleRecord.register) }}-->
|
||||
<!-- </AButton>-->
|
||||
<!-- </div>-->
|
||||
</ASpace>
|
||||
</AForm>
|
||||
</template>
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, reactive, ref} from 'vue';
|
||||
import {useI18n} from 'vue-i18n'; // 添加这行
|
||||
import {useAuthStore} from '@/store/modules/auth';
|
||||
import {useRouterPush} from '@/hooks/common/router';
|
||||
import {useAntdForm, useFormRules} from '@/hooks/common/form';
|
||||
import {useCaptcha} from '@/hooks/business/captcha';
|
||||
import {useWindowSize} from '@vueuse/core';
|
||||
import {registerTerms} from '@/views/_builtin/login/modules/terms';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { useFormRules } from '@/hooks/common/form';
|
||||
import { useCaptcha } from '@/hooks/business/captcha';
|
||||
import { useWindowSize } from '@vueuse/core';
|
||||
import { registerTerms } from '@/views/_builtin/login/modules/terms';
|
||||
import dayjs from 'dayjs';
|
||||
import type {Rule} from 'ant-design-vue/es/form';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
// 添加两个表单引用
|
||||
const basicFormRef = ref();
|
||||
const securityFormRef = ref();
|
||||
|
||||
defineOptions({
|
||||
name: 'Register'
|
||||
});
|
||||
|
||||
const { toggleLoginModule } = useRouterPush();
|
||||
const { formRef, validate } = useAntdForm();
|
||||
const { label, isCounting, loading, getCaptcha } = useCaptcha();
|
||||
|
||||
const { width } = useWindowSize();
|
||||
@@ -29,17 +31,19 @@ const currentStep = ref(0);
|
||||
|
||||
// 是否同意协议
|
||||
const agreeTerms = ref(false);
|
||||
|
||||
// 定义一个统一的数据模型
|
||||
interface RegisterModel {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
fullName: string;
|
||||
age:0,
|
||||
age: number;
|
||||
gender: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
code: string;
|
||||
uuid: string;
|
||||
authType: string;
|
||||
}
|
||||
|
||||
@@ -49,11 +53,12 @@ const model = reactive<RegisterModel>({
|
||||
password: '',
|
||||
email: '',
|
||||
fullName: '',
|
||||
age:0,
|
||||
age: 0,
|
||||
gender: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
code: '',
|
||||
uuid: '',
|
||||
authType: 'u'
|
||||
});
|
||||
|
||||
@@ -64,7 +69,6 @@ interface BasicFormModel {
|
||||
birthDate: string;
|
||||
gender: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
@@ -74,7 +78,6 @@ const basicModel = reactive<BasicFormModel>({
|
||||
birthDate: '',
|
||||
gender: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
address: ''
|
||||
});
|
||||
|
||||
@@ -82,6 +85,7 @@ const basicModel = reactive<BasicFormModel>({
|
||||
interface SecurityFormModel {
|
||||
email: string;
|
||||
code: string;
|
||||
uuid: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
@@ -89,27 +93,85 @@ interface SecurityFormModel {
|
||||
const securityModel = reactive<SecurityFormModel>({
|
||||
email: '',
|
||||
code: '',
|
||||
uuid: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
// 第一步表单验证规则
|
||||
const basicRules = computed<Record<string, Rule | Rule[]>>(() => {
|
||||
const { formRules } = useFormRules();
|
||||
const validateUsername = async (_rule: Rule, value: string) => {
|
||||
if (value && (value.length < 4 || value.length > 20)) {
|
||||
return Promise.reject(t('page.login.register.usernameLengthLimit'));
|
||||
}
|
||||
if (value) {
|
||||
const { exists } = await authStore.checkUserRepeat({ username: value, authType: 'u' });
|
||||
if (exists) {
|
||||
return Promise.reject(t('page.login.register.usernameExists'));
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const validatePhone = async (_rule: Rule, value: string) => {
|
||||
if (!value) return Promise.resolve();
|
||||
|
||||
const phonePattern = /^1[3-9]\d{9}$/;
|
||||
|
||||
if (!phonePattern.test(value)) {
|
||||
return Promise.reject(t('page.login.register.phoneInvalid'));
|
||||
}
|
||||
|
||||
const { exists } = await authStore.checkUserRepeat({ phonenumber: value, authType: 'u' });
|
||||
if (exists) {
|
||||
return Promise.reject(t('page.login.register.phoneExists'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return {
|
||||
username: formRules.username,
|
||||
email: formRules.email,
|
||||
username: [
|
||||
{ required: true, message: t('page.login.register.usernameRequired'), trigger: 'change' },
|
||||
{ validator: validateUsername, trigger: 'blur' }
|
||||
],
|
||||
phone: [{ validator: validatePhone, trigger: 'blur' }],
|
||||
birthDate: [{ required: true, message: t('page.login.register.birthDateRequired') }],
|
||||
phone: [{ pattern: /^1[3-9]\d{9}$/, message: t('form.phone.invalid'), trigger: 'blur' }]
|
||||
fullName: []
|
||||
};
|
||||
});
|
||||
|
||||
// 第三步表单验证规则
|
||||
const securityRules = computed<Record<string, Rule | Rule[]>>(() => {
|
||||
const { formRules, createConfirmPwdRule } = useFormRules();
|
||||
const { createConfirmPwdRule } = useFormRules();
|
||||
|
||||
const validateEmail = async (_rule: Rule, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject(t('page.login.register.emailRequired'));
|
||||
}
|
||||
|
||||
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
|
||||
|
||||
if (!emailPattern.test(value)) {
|
||||
return Promise.reject(t('page.login.register.emailInvalid'));
|
||||
}
|
||||
|
||||
const { exists } = await authStore.checkUserRepeat({ email: value, authType: 'u' });
|
||||
if (exists) {
|
||||
return Promise.reject(t('page.login.register.emailExists'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return {
|
||||
code: formRules.code,
|
||||
password: formRules.pwd,
|
||||
email: [
|
||||
{ required: true, message: t('page.login.register.emailRequired'), trigger: 'change' },
|
||||
{ validator: validateEmail, trigger: 'blur' }
|
||||
],
|
||||
code: [{ required: true, message: t('page.login.register.codeRequired') }],
|
||||
password: [
|
||||
{ required: true, message: t('page.login.register.passwordRequired') },
|
||||
{ min: 6, message: t('page.login.register.passwordLength') }
|
||||
],
|
||||
confirmPassword: createConfirmPwdRule(securityModel.password)
|
||||
};
|
||||
});
|
||||
@@ -122,43 +184,61 @@ const terms = computed(() => {
|
||||
|
||||
// 步骤控制函数
|
||||
async function nextStep() {
|
||||
if (currentStep.value === 0) {
|
||||
await validate();
|
||||
// 复制电话号码到第三步
|
||||
securityModel.email = basicModel.email;
|
||||
}
|
||||
if (currentStep.value === 1) {
|
||||
if (!agreeTerms.value) {
|
||||
window.$message?.error(t('page.login.register.agreeTermsFirst'));
|
||||
return;
|
||||
try {
|
||||
if (currentStep.value === 0) {
|
||||
// 验证第一步表单
|
||||
await basicFormRef.value?.validate();
|
||||
}
|
||||
if (currentStep.value === 1) {
|
||||
if (!agreeTerms.value) {
|
||||
window.$message?.error(t('page.login.register.agreeTermsFirst'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 验证通过后增加步骤
|
||||
currentStep.value += 1;
|
||||
} catch (error) {
|
||||
// 验证失败时不增加步骤
|
||||
console.error('Validation failed:', error);
|
||||
}
|
||||
currentStep.value += 1;
|
||||
}
|
||||
|
||||
// 返回
|
||||
function prevStep() {
|
||||
currentStep.value -= 1;
|
||||
}
|
||||
//注册按钮
|
||||
|
||||
async function handleCaptcha() {
|
||||
const res = await getCaptcha(securityModel.email);
|
||||
if (res) {
|
||||
securityModel.uuid = res.data.uuid;
|
||||
if (res.data?.text) {
|
||||
securityModel.code = res.data.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册按钮
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await validate();
|
||||
await securityFormRef.value?.validate();
|
||||
|
||||
// 整合表单数据
|
||||
model.username = basicModel.username;
|
||||
model.password = securityModel.password;
|
||||
model.email = securityModel.email;
|
||||
model.email = securityModel.email; // 使用第三步的 email
|
||||
model.fullName = basicModel.fullName;
|
||||
model.gender = basicModel.gender; // 直接使用 gender 值
|
||||
model.gender = basicModel.gender;
|
||||
model.phone = basicModel.phone;
|
||||
model.address = basicModel.address;
|
||||
model.code = securityModel.code;
|
||||
model.uuid = securityModel.uuid;
|
||||
|
||||
const success = await authStore.register({
|
||||
...model,
|
||||
age: dayjs().diff(dayjs(basicModel.birthDate), 'year'),
|
||||
sex: model.gender, // 直接使用 gender 值,不需要转换
|
||||
phonenumber: model.phone,
|
||||
sex: model.gender,
|
||||
phonenumber: model.phone
|
||||
});
|
||||
|
||||
if (success) {
|
||||
@@ -175,187 +255,196 @@ const showSteps = computed(() => !isMobile.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ASteps
|
||||
v-if="showSteps"
|
||||
:current="currentStep"
|
||||
size="small"
|
||||
class="max-w-full mb-16px"
|
||||
direction="horizontal"
|
||||
:responsive="false"
|
||||
>
|
||||
<AStep :title="t('page.login.register.basicInfo')" />
|
||||
<AStep :title="t('page.login.register.terms')" />
|
||||
<AStep :title="t('page.login.register.security')" />
|
||||
</ASteps>
|
||||
<div class="register-container">
|
||||
<ASteps
|
||||
v-if="showSteps"
|
||||
:current="currentStep"
|
||||
size="small"
|
||||
class="max-w-full mb-16px"
|
||||
direction="horizontal"
|
||||
:responsive="false"
|
||||
>
|
||||
<AStep :title="t('page.login.register.basicInfo')" />
|
||||
<AStep :title="t('page.login.register.terms')" />
|
||||
<AStep :title="t('page.login.register.security')" />
|
||||
</ASteps>
|
||||
|
||||
<div v-else class="mobile-step-indicator mb-16px text-center">
|
||||
{{ currentStep + 1 }}/3: {{
|
||||
currentStep === 0
|
||||
? t('page.login.register.basicInfo')
|
||||
: currentStep === 1
|
||||
? t('page.login.register.terms')
|
||||
: t('page.login.register.security')
|
||||
}}
|
||||
</div>
|
||||
<div v-else class="mobile-step-indicator mb-16px text-center">
|
||||
{{ currentStep + 1 }}/3: {{
|
||||
currentStep === 0
|
||||
? t('page.login.register.basicInfo')
|
||||
: currentStep === 1
|
||||
? t('page.login.register.terms')
|
||||
: t('page.login.register.security')
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div class="step-content">
|
||||
<!-- 第一步:基本信息 -->
|
||||
<div v-if="currentStep === 0">
|
||||
<AForm
|
||||
ref="formRef"
|
||||
:model="basicModel"
|
||||
:rules="basicRules"
|
||||
:label-wrap="true"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<ARow :gutter="[8,2]">
|
||||
<ACol :span="24" :lg="24">
|
||||
<AFormItem name="username" :label="t('page.login.register.username')">
|
||||
<AInput v-model:value="basicModel.username" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :span="24" :lg="24">
|
||||
<AFormItem name="fullName" :label="t('page.login.register.fullName')">
|
||||
<AInput v-model:value="basicModel.fullName" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :xs="12" :sm="12" :lg="24">
|
||||
<AFormItem name="birthDate" :label="t('page.login.register.birthDate')">
|
||||
<ADatePicker
|
||||
v-model:value="basicModel.birthDate"
|
||||
class="!w-full birth-date-picker"
|
||||
:placeholder="t('page.login.register.birthDatePlaceholder')"
|
||||
:disabled-date="(current: dayjs.Dayjs) => current && current.isAfter(dayjs())"
|
||||
<div class="step-content">
|
||||
<!-- 第一步:基本信息 -->
|
||||
<div v-show="currentStep === 0">
|
||||
<AForm
|
||||
ref="basicFormRef"
|
||||
:model="basicModel"
|
||||
:rules="basicRules"
|
||||
:label-wrap="true"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<ARow :gutter="[8,2]">
|
||||
<ACol :span="24" :lg="24">
|
||||
<AFormItem name="username" :label="t('page.login.register.username')">
|
||||
<AInput v-model:value="basicModel.username" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :span="24" :lg="24">
|
||||
<AFormItem name="fullName" :label="t('page.login.register.fullName')">
|
||||
<AInput v-model:value="basicModel.fullName" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :xs="12" :sm="12" :lg="24">
|
||||
<AFormItem name="birthDate" :label="t('page.login.register.birthDate')">
|
||||
<ADatePicker
|
||||
v-model:value="basicModel.birthDate"
|
||||
class="!w-full birth-date-picker"
|
||||
:placeholder="t('page.login.register.birthDatePlaceholder')"
|
||||
:disabled-date="(current: dayjs.Dayjs) => current && current.isAfter(dayjs())"
|
||||
/>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :xs="12" :sm="12" :lg="24">
|
||||
<AFormItem name="gender" :label="t('page.login.register.gender')">
|
||||
<ASelect v-model:value="basicModel.gender">
|
||||
<ASelectOption value="0">{{ t('page.login.register.male') }}</ASelectOption>
|
||||
<ASelectOption value="1">{{ t('page.login.register.female') }}</ASelectOption>
|
||||
</ASelect>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :lg="24" :span="24">
|
||||
<AFormItem name="phone" :label="t('page.login.register.phone')">
|
||||
<AInput v-model:value="basicModel.phone" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :lg="24" :span="24">
|
||||
<AFormItem name="address" :label="t('page.login.register.address')">
|
||||
<ATextarea v-model:value="basicModel.address" :rows="2" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :lg="24" :span="24">
|
||||
<ASpace direction="vertical" size="small" class="w-full">
|
||||
<AButton type="primary" block size="small" @click="nextStep">
|
||||
{{ t('page.login.register.next') }}
|
||||
</AButton>
|
||||
<AButton block size="small" @click="toggleLoginModule('pwd-login')">
|
||||
{{ t('page.login.common.back') }}
|
||||
</AButton>
|
||||
</ASpace>
|
||||
</ACol>
|
||||
</ARow>
|
||||
</AForm>
|
||||
</div>
|
||||
|
||||
<!-- 第二步:协议 -->
|
||||
<div v-show="currentStep === 1">
|
||||
<ATextarea
|
||||
:value="terms"
|
||||
:rows="12"
|
||||
readonly
|
||||
class="mb-16px"
|
||||
size="small"
|
||||
:style="{ fontSize: '14px', lineHeight: '1.6' }"
|
||||
/>
|
||||
<div class="mb-16px">
|
||||
<ACheckbox
|
||||
v-model:checked="agreeTerms"
|
||||
class="terms-checkbox"
|
||||
>
|
||||
{{ t('page.login.register.agreeTerms') }}
|
||||
</ACheckbox>
|
||||
</div>
|
||||
<ASpace direction="vertical" size="small" class="w-full">
|
||||
<AButton type="primary" block size="small" :disabled="!agreeTerms" @click="nextStep">
|
||||
{{ t('page.login.register.next') }}
|
||||
</AButton>
|
||||
<AButton block size="small" @click="prevStep">
|
||||
{{ t('page.login.register.prev') }}
|
||||
</AButton>
|
||||
</ASpace>
|
||||
</div>
|
||||
|
||||
<!-- 第三步:安全信息 -->
|
||||
<div v-show="currentStep === 2">
|
||||
<AForm
|
||||
ref="securityFormRef"
|
||||
:model="securityModel"
|
||||
:rules="securityRules"
|
||||
:label-wrap="true"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
class="compact-form"
|
||||
>
|
||||
<AFormItem name="email" :label="t('page.login.register.email')">
|
||||
<AInput
|
||||
v-model:value="securityModel.email"
|
||||
:placeholder="t('page.login.common.emailPlaceholder')"
|
||||
/>
|
||||
</AFormItem>
|
||||
<AFormItem name="code" :label="t('page.login.register.code')">
|
||||
<div class="w-full flex-y-center gap-8px">
|
||||
<AInput
|
||||
v-model:value="securityModel.code"
|
||||
:placeholder="t('page.login.common.codePlaceholder')"
|
||||
/>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :xs="12" :sm="12" :lg="24">
|
||||
<AFormItem name="gender" :label="t('page.login.register.gender')">
|
||||
<ASelect v-model:value="basicModel.gender">
|
||||
<ASelectOption value="male">{{ t('page.login.register.male') }}</ASelectOption>
|
||||
<ASelectOption value="female">{{ t('page.login.register.female') }}</ASelectOption>
|
||||
</ASelect>
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :lg="24" :span="24">
|
||||
<AFormItem name="phone" :label="t('page.login.register.phone')">
|
||||
<AInput v-model:value="basicModel.phone" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :lg="24" :span="24">
|
||||
<AFormItem name="email" :label="t('page.login.register.email')">
|
||||
<AInput v-model:value="basicModel.email" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :lg="24" :span="24">
|
||||
<AFormItem name="address" :label="t('page.login.register.address')">
|
||||
<ATextarea v-model:value="basicModel.address" :rows="2" />
|
||||
</AFormItem>
|
||||
</ACol>
|
||||
<ACol :lg="24" :span="24">
|
||||
<ASpace direction="vertical" size="small" class="w-full">
|
||||
<AButton type="primary" block size="small" @click="nextStep">
|
||||
{{ t('page.login.register.next') }}
|
||||
<AButton
|
||||
size="small"
|
||||
:disabled="isCounting"
|
||||
:loading="loading"
|
||||
@click="handleCaptcha()"
|
||||
>
|
||||
{{ label }}
|
||||
</AButton>
|
||||
<AButton block size="small" @click="toggleLoginModule('pwd-login')">
|
||||
{{ t('page.login.common.back') }}
|
||||
</div>
|
||||
</AFormItem>
|
||||
<AFormItem name="password" :label="t('page.login.register.password')">
|
||||
<AInputPassword
|
||||
v-model:value="securityModel.password"
|
||||
:placeholder="t('page.login.common.passwordPlaceholder')"
|
||||
/>
|
||||
</AFormItem>
|
||||
<AFormItem name="confirmPassword" :label="t('page.login.register.confirmPassword')">
|
||||
<AInputPassword
|
||||
v-model:value="securityModel.confirmPassword"
|
||||
:placeholder="t('page.login.common.confirmPasswordPlaceholder')"
|
||||
/>
|
||||
</AFormItem>
|
||||
<AFormItem :wrapper-col="{ xs: { span: 24 }, sm: { span: 24 } }">
|
||||
<ASpace direction="vertical" size="small" class="w-full">
|
||||
<AButton type="primary" block size="small" @click="handleSubmit">
|
||||
{{ t('common.confirm') }}
|
||||
</AButton>
|
||||
<AButton block size="small" @click="prevStep">
|
||||
{{ t('page.login.register.prev') }}
|
||||
</AButton>
|
||||
</ASpace>
|
||||
</ACol>
|
||||
</ARow>
|
||||
</AForm>
|
||||
</div>
|
||||
|
||||
<!-- 第二步:协议 -->
|
||||
<div v-if="currentStep === 1">
|
||||
<ATextarea
|
||||
:value="terms"
|
||||
:rows="12"
|
||||
readonly
|
||||
class="mb-16px"
|
||||
size="small"
|
||||
:style="{ fontSize: '14px', lineHeight: '1.6' }"
|
||||
/>
|
||||
<div class="mb-16px">
|
||||
<ACheckbox
|
||||
v-model:checked="agreeTerms"
|
||||
class="terms-checkbox"
|
||||
>
|
||||
{{ t('page.login.register.agreeTerms') }}
|
||||
</ACheckbox>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</div>
|
||||
<ASpace direction="vertical" size="small" class="w-full">
|
||||
<AButton type="primary" block size="small" :disabled="!agreeTerms" @click="nextStep">
|
||||
{{ t('page.login.register.next') }}
|
||||
</AButton>
|
||||
<AButton block size="small" @click="prevStep">
|
||||
{{ t('page.login.register.prev') }}
|
||||
</AButton>
|
||||
</ASpace>
|
||||
</div>
|
||||
|
||||
<!-- 第三步:安全信息 -->
|
||||
<div v-if="currentStep === 2">
|
||||
<AForm
|
||||
ref="formRef"
|
||||
:model="securityModel"
|
||||
:rules="securityRules"
|
||||
:label-wrap="true"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
class="compact-form"
|
||||
>
|
||||
<AFormItem name="email" :label="t('page.login.register.email')">
|
||||
<AInput
|
||||
v-model:value="securityModel.email"
|
||||
:placeholder="t('page.login.common.emailPlaceholder')"
|
||||
/>
|
||||
</AFormItem>
|
||||
<AFormItem name="code" :label="t('page.login.register.code')">
|
||||
<div class="w-full flex-y-center gap-8px">
|
||||
<AInput
|
||||
v-model:value="securityModel.code"
|
||||
:placeholder="t('page.login.common.codePlaceholder')"
|
||||
/>
|
||||
<AButton
|
||||
size="small"
|
||||
:disabled="isCounting"
|
||||
:loading="loading"
|
||||
@click="getCaptcha(securityModel.email)"
|
||||
>
|
||||
{{ label }}
|
||||
</AButton>
|
||||
</div>
|
||||
</AFormItem>
|
||||
<AFormItem name="password" :label="t('page.login.register.password')">
|
||||
<AInputPassword
|
||||
v-model:value="securityModel.password"
|
||||
:placeholder="t('page.login.common.passwordPlaceholder')"
|
||||
/>
|
||||
</AFormItem>
|
||||
<AFormItem name="confirmPassword" :label="t('page.login.register.confirmPassword')">
|
||||
<AInputPassword
|
||||
v-model:value="securityModel.confirmPassword"
|
||||
:placeholder="t('page.login.common.confirmPasswordPlaceholder')"
|
||||
/>
|
||||
</AFormItem>
|
||||
<AFormItem :wrapper-col="{ xs: { span: 24 }, sm: { span: 24 } }">
|
||||
<ASpace direction="vertical" size="small" class="w-full">
|
||||
<AButton type="primary" block size="small" @click="handleSubmit">
|
||||
{{ t('common.confirm') }}
|
||||
</AButton>
|
||||
<AButton block size="small" @click="prevStep">
|
||||
{{ t('page.login.register.prev') }}
|
||||
</AButton>
|
||||
</ASpace>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.register-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.step-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
:deep(.ant-form) {
|
||||
.ant-form-item {
|
||||
@@ -433,6 +522,7 @@ const showSteps = computed(() => !isMobile.value);
|
||||
:deep(.ant-form-item-label) {
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
|
||||
> label {
|
||||
height: auto !important;
|
||||
padding-bottom: 4px;
|
||||
|
||||
@@ -80,10 +80,18 @@ async function init() {
|
||||
function clearChecks() {
|
||||
menuIds.value = [];
|
||||
}
|
||||
|
||||
const rootIds = ref([]);
|
||||
function fnModalTreeChecked(keys: any, info: any ) {
|
||||
let ids = Array.isArray(keys) ? keys : keys.checked;
|
||||
// ids = ids.concat(info.halfCheckedKeys);
|
||||
rootIds.value = info.halfCheckedKeys;
|
||||
menuIds.value = ids;
|
||||
}
|
||||
defineExpose({
|
||||
clearChecks,
|
||||
checkedKeys: menuIds,
|
||||
checkedKeys: ()=> {
|
||||
return menuIds.value.concat(rootIds.value).concat()
|
||||
},
|
||||
tree
|
||||
});
|
||||
</script>
|
||||
@@ -92,13 +100,15 @@ defineExpose({
|
||||
<div class="border-0.5 border-gray-300 rounded-md p-2 transition-all dark:border-dark-300" hover="border-gray-500">
|
||||
<SimpleScrollbar>
|
||||
<ATree
|
||||
v-model:checked-keys="menuIds"
|
||||
:selectable="false"
|
||||
:virtual="false"
|
||||
:tree-data="tree"
|
||||
checkable
|
||||
block-node
|
||||
/>
|
||||
:selectable="false"
|
||||
@check="fnModalTreeChecked"
|
||||
v-model:checked-keys="menuIds"
|
||||
:check-strictly="false"
|
||||
:tree-data="tree"
|
||||
>
|
||||
</ATree>
|
||||
</SimpleScrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { useAntdForm, useFormRules } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusOptions } from '@/constants/business';
|
||||
import { doPostRole, doPutRole } from '@/service/api/role';
|
||||
import {SimpleScrollbar} from '@sa/materials';
|
||||
import {useAntdForm, useFormRules} from '@/hooks/common/form';
|
||||
import {$t} from '@/locales';
|
||||
import {enableStatusOptions} from '@/constants/business';
|
||||
import {doPostRole, doPutRole} from '@/service/api/role';
|
||||
import MenuAuth from './menu-auth.vue';
|
||||
|
||||
defineOptions({
|
||||
@@ -32,8 +32,8 @@ const menuAuthRef = ref<InstanceType<typeof MenuAuth> | null>(null);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const { formRef, validate, resetFields } = useAntdForm();
|
||||
const { defaultRequiredRule } = useFormRules();
|
||||
const {formRef, validate, resetFields} = useAntdForm();
|
||||
const {defaultRequiredRule} = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<AntDesign.TableOperateType, string> = {
|
||||
@@ -73,7 +73,7 @@ function handleUpdateModelWhenEdit() {
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit' && props.rowData) {
|
||||
model.value = { ...props.rowData, menuIds: [] };
|
||||
model.value = {...props.rowData, menuIds: []};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +83,9 @@ function closeDrawer() {
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
const menuIds = transformMenuChildWithRootIds(menuAuthRef.value?.tree || [], model.value.menuIds);
|
||||
const menuIds = menuAuthRef.value?.checkedKeys();
|
||||
|
||||
const { error } = await (props.operateType === 'edit' ? doPutRole : doPostRole)({
|
||||
const {error} = await (props.operateType === 'edit' ? doPutRole : doPostRole)({
|
||||
...model.value,
|
||||
menuIds,
|
||||
menuCheckStrictly: true
|
||||
@@ -122,10 +122,10 @@ watch(
|
||||
<SimpleScrollbar>
|
||||
<AForm ref="formRef" py-20px pr-20px layout="vertical" :model="model" :rules="rules">
|
||||
<AFormItem :label="$t('page.manage.role.roleName')" name="roleName">
|
||||
<AInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" />
|
||||
<AInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')"/>
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.role.roleCode')" name="roleKey">
|
||||
<AInput v-model:value="model.roleKey" :placeholder="$t('page.manage.role.form.roleCode')" />
|
||||
<AInput v-model:value="model.roleKey" :placeholder="$t('page.manage.role.form.roleCode')"/>
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.role.roleStatus')" name="status">
|
||||
<ARadioGroup v-model:value="model.status">
|
||||
@@ -135,7 +135,7 @@ watch(
|
||||
</ARadioGroup>
|
||||
</AFormItem>
|
||||
<AFormItem :label="$t('page.manage.role.roleDesc')" name="roleDesc">
|
||||
<AInput v-model:value="model.remark" :placeholder="$t('page.manage.role.form.roleDesc')" />
|
||||
<AInput v-model:value="model.remark" :placeholder="$t('page.manage.role.form.roleDesc')"/>
|
||||
</AFormItem>
|
||||
|
||||
<AFormItem>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default defineConfig(configEnv => {
|
||||
plugins: setupVitePlugins(viteEnv),
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 8085,
|
||||
port: 8086,
|
||||
open: false,
|
||||
proxy: createViteProxy(viteEnv, configEnv.command === 'serve'),
|
||||
fs: {
|
||||
|
||||
Reference in New Issue
Block a user