chore: update app name

This commit is contained in:
vben
2024-06-08 16:33:49 +08:00
parent 77d40dc763
commit d584d4cf4e
57 changed files with 4 additions and 4 deletions

4
apps/web-antd/.env Normal file
View File

@@ -0,0 +1,4 @@
# spa-title
VITE_GLOB_APP_TITLE = Vben Admin Pro
VITE_APP_NAMESPACE = web-antd

View File

@@ -0,0 +1,6 @@
# public path
VITE_PUBLIC_PATH = /
# Basic interface address SPA
VITE_GLOB_API_URL=/vben-api

View File

@@ -0,0 +1,3 @@
VITE_PUBLIC_PATH = /
VITE_GLOB_API_URL=/vben-api

View File

@@ -0,0 +1,5 @@
# public path
VITE_PUBLIC_PATH = /
# Basic interface address SPA
VITE_GLOB_API_URL=/vben-api

22
apps/web-antd/index.html Normal file
View File

@@ -0,0 +1,22 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="description" content="A Modern Back-end Management System" />
<meta name="keywords" content="Vben Admin Pro Vue3 Vite" />
<meta name="author" content="Vben" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<!-- 由 vite 注入 VITE_GLOB_APP_TITLE 变量,在 . env 内配置 -->
<title><%= VITE_GLOB_APP_TITLE %></title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,33 @@
function resultSuccess<T = Record<string, any>>(
result: T,
{ message = 'ok' } = {},
) {
return {
code: 0,
message,
result,
type: 'success',
};
}
function resultError(
message = 'Request failed',
{ code = -1, result = null } = {},
) {
return {
code,
message,
result,
type: 'error',
};
}
/**
* @zh_CN 本函数用于从request数据中获取token请根据项目的实际情况修改
*
*/
function getRequestToken({ headers }: any): string | undefined {
return headers?.authorization;
}
export { getRequestToken, resultError, resultSuccess };

101
apps/web-antd/mock/user.ts Normal file
View File

@@ -0,0 +1,101 @@
import { getRequestToken, resultError, resultSuccess } from './_util';
const fakeUserList = [
{
accessToken: 'fakeAdminToken',
avatar: '',
desc: 'manager',
homePath: '/welcome',
password: '123456',
realName: 'Vben Admin',
roles: [
{
roleName: 'Super Admin',
value: 'super',
},
],
userId: '1',
username: 'vben',
},
{
accessToken: 'fakeTestToken',
avatar: '',
desc: 'tester',
homePath: '/welcome',
password: '123456',
realName: 'test user',
roles: [
{
roleName: 'Tester',
value: 'test',
},
],
userId: '2',
username: 'test',
},
];
export default [
{
method: 'post',
response: ({ body }: any) => {
const { password, username } = body;
const checkUser = fakeUserList.find(
(item) => item.username === username && password === item.password,
);
if (!checkUser) {
return resultError('Incorrect account or password');
}
const {
accessToken,
desc,
realName,
roles,
userId,
username: _username,
} = checkUser;
return resultSuccess({
accessToken,
desc,
realName,
roles,
userId,
username: _username,
});
},
timeout: 200,
url: '/vben-api/login',
},
{
method: 'get',
response: (request: any) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = fakeUserList.find((item) => item.accessToken === token);
if (!checkUser) {
return resultError(
'The corresponding user information was not obtained!',
);
}
const { accessToken: _token, password: _pwd, ...rest } = checkUser;
return resultSuccess(rest);
},
url: '/vben-api/getUserInfo',
},
{
method: 'get',
response: (request: any) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = fakeUserList.find((item) => item.accessToken === token);
if (!checkUser) {
return resultError('Invalid token!');
}
return resultSuccess(undefined, {
message: 'Token has been destroyed',
});
},
timeout: 200,
url: '/vben-api/logout',
},
];

View File

@@ -0,0 +1,49 @@
{
"name": "@vben/antd-view",
"version": "5.0.0-alpha.1",
"author": {
"name": "vben",
"email": "anncwb@126.com",
"url": "https://github.com/anncwb"
},
"type": "module",
"license": "MIT",
"homepage": "https://github.com/vbenjs/vue-vben-admin",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "apps/vben-admin"
},
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"scripts": {
"build": "pnpm vite build",
"dev": "pnpm vite",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
},
"dependencies": {
"@vben-core/helpers": "workspace:*",
"@vben-core/preferences": "workspace:*",
"@vben-core/request": "workspace:*",
"@vben-core/stores": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/constants": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "^10.10.0",
"ant-design-vue": "^4.2.1",
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"pinia": "2.1.7",
"vue": "3.4.27",
"vue-router": "^4.3.2"
},
"devDependencies": {
"vite-plugin-mock": "^3.0.2"
}
}

View File

@@ -0,0 +1 @@
export { default } from '@vben/tailwind-config/postcss';

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

View File

@@ -0,0 +1,2 @@
export * from './modules';
export type * from './types';

View File

@@ -0,0 +1 @@
export * from './user';

View File

@@ -0,0 +1,22 @@
import type { UserApiType } from '@/apis/types';
import type { UserInfo } from '@vben/types';
import { request } from '@/forward';
/**
* 登录
*/
async function userLogin(data: UserApiType.LoginParams) {
return request<UserApiType.LoginResult>('/login', { data, method: 'post' });
}
/**
* 获取用户信息
*/
async function getUserInfo() {
return request<UserInfo>('/getUserInfo', { method: 'get' });
}
export { getUserInfo, userLogin };
export * from './user';

View File

@@ -0,0 +1 @@
export type * from './user';

View File

@@ -0,0 +1,18 @@
namespace UserApiType {
/** 登录接口参数 */
export interface LoginParams {
password: string;
username: string;
}
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
desc: string;
realName: string;
userId: string;
username: string;
}
}
export type { UserApiType };

40
apps/web-antd/src/app.vue Normal file
View File

@@ -0,0 +1,40 @@
<script lang="ts" setup>
import 'dayjs/locale/zh-cn';
import { preferences, usePreferences } from '@vben-core/preferences';
import { GlobalProvider } from '@vben/common-ui';
import { ConfigProvider, theme } from 'ant-design-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import dayjs from 'dayjs';
import { computed } from 'vue';
defineOptions({ name: 'App' });
dayjs.locale(zhCN.locale);
const { isDark } = usePreferences();
const tokenTheme = computed(() => {
const algorithms = isDark.value
? [theme.darkAlgorithm]
: [theme.defaultAlgorithm];
// antd 紧凑模式算法
if (preferences.app.compact) {
algorithms.push(theme.compactAlgorithm);
}
return {
algorithms,
token: { colorPrimary: preferences.theme.colorPrimary },
};
});
</script>
<template>
<GlobalProvider>
<ConfigProvider :locale="zhCN" :theme="tokenTheme">
<RouterView />
</ConfigProvider>
</GlobalProvider>
</template>

View File

@@ -0,0 +1,34 @@
import '@vben/styles';
import { preferences } from '@vben-core/preferences';
import { setupStore } from '@/store';
import { setupI18n } from '@vben/locales';
import { createApp } from 'vue';
import App from './app.vue';
import { router } from './router';
async function bootstrap(namespace: string) {
const app = createApp(App);
// 国际化 i18n 配置
await setupI18n(app, { defaultLocale: preferences.app.locale });
// 配置 pinia-store
await setupStore(app, { namespace });
// 配置路由及路由守卫
app.use(router);
app.mount('#app');
// production mock server
if (import.meta.env.PROD) {
import('./mock-prod-server').then(({ setupProdMockServer }) => {
setupProdMockServer();
});
}
}
export { bootstrap };

View File

@@ -0,0 +1 @@
export * from './request';

View File

@@ -0,0 +1,81 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { AxiosResponse } from '@vben-core/request';
import { RequestClient, isCancelError } from '@vben-core/request';
import { useAccessStore } from '@vben-core/stores';
import { message } from 'ant-design-vue';
interface HttpResponse<T = any> {
code: number;
message: string;
result: T;
}
function createRequestClient() {
const client = new RequestClient({
baseURL: import.meta.env.VITE_GLOB_API_URL,
// 为每个请求携带 Authorization
makeAuthorization: () => {
return {
handle: () => {
const accessStore = useAccessStore();
return accessStore.getAccessToken;
},
// 默认
// key: 'Authorization',
};
},
});
setupRequestInterceptors(client);
const request = client.request.bind(client);
const get = client.get.bind(client);
const post = client.post.bind(client);
return {
get,
post,
request,
};
}
function setupRequestInterceptors(client: RequestClient) {
client.addResponseInterceptor(
(response: AxiosResponse<HttpResponse>) => {
const { data: responseData, status } = response;
const { code, message: msg, result } = responseData;
if (status === 200 && code === 0) {
return result;
} else {
message.error(msg);
throw new Error(msg);
}
},
(error: any) => {
if (isCancelError(error)) {
return Promise.reject(error);
}
const err: string = error?.toString?.() ?? '';
let errMsg = '';
if (err?.includes('Network Error')) {
errMsg = '网络错误。';
} else if (error?.message?.includes?.('timeout')) {
errMsg = '请求超时。';
} else {
errMsg = error?.response?.data?.error?.message ?? '';
}
message.error(errMsg);
return Promise.reject(error);
},
);
}
const { request } = createRequestClient();
// 其他配置的请求方法
// const { request: xxxRequest } = createRequest();
export { request };

View File

@@ -0,0 +1,113 @@
<script lang="ts" setup>
import type { NotificationItem } from '@vben/common-ui';
import { preferences } from '@vben-core/preferences';
import { useAccessStore } from '@vben-core/stores';
import { Notification, UserDropdown } from '@vben/common-ui';
import { IcRoundCreditScore, MdiDriveDocument, MdiGithub } from '@vben/icons';
import { BasicLayout } from '@vben/layouts';
import { $t } from '@vben/locales';
import { openWindow } from '@vben/utils';
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
// https://avatar.vercel.sh/vercel.svg?text=Vaa
// https://avatar.vercel.sh/1
// https://avatar.vercel.sh/nextjs
// https://avatar.vercel.sh/satori
const notifications = ref<NotificationItem[]>([
{
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
date: '3小时前',
isRead: true,
message: '描述信息描述信息描述信息',
title: '收到了 14 份新周报',
},
{
avatar: 'https://avatar.vercel.sh/1',
date: '刚刚',
isRead: false,
message: '描述信息描述信息描述信息',
title: '朱偏右 回复了你',
},
{
avatar: 'https://avatar.vercel.sh/1',
date: '2024-01-01',
isRead: false,
message: '描述信息描述信息描述信息',
title: '曲丽丽 评论了你',
},
{
avatar: 'https://avatar.vercel.sh/satori',
date: '1天前',
isRead: false,
message: '描述信息描述信息描述信息',
title: '代办提醒',
},
]);
const menus = computed(() => [
{
handler: () => {
openWindow('https://github.com/vbenjs/vue-vben-admin', {
target: '_blank',
});
},
icon: MdiDriveDocument,
text: $t('widgets.document'),
},
{
handler: () => {
openWindow('https://github.com/vbenjs/vue-vben-admin', {
target: '_blank',
});
},
icon: MdiGithub,
text: 'GitHub',
},
{
handler: () => {
openWindow('https://github.com/vbenjs/vue-vben-admin/issues', {
target: '_blank',
});
},
icon: IcRoundCreditScore,
text: $t('widgets.qa'),
},
]);
const accessStore = useAccessStore();
const router = useRouter();
function handleLogout() {
accessStore.$reset();
router.replace('/auth/login');
}
function handleNoticeClear() {
notifications.value = [];
}
</script>
<template>
<BasicLayout>
<template #user-dropdown>
<UserDropdown
:avatar="preferences.app.defaultAvatar"
:menus="menus"
text="Vben Admin"
description="ann.vben@gmail.com"
tag-text="Pro"
@logout="handleLogout"
/>
</template>
<template #notification>
<Notification
dot
:notifications="notifications"
@clear="handleNoticeClear"
/>
</template>
</BasicLayout>
</template>

View File

@@ -0,0 +1,8 @@
const BasicLayout = () => import('./basic.vue');
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
const AuthPageLayoutType = () =>
import('@vben/layouts').then((m) => m.AuthPageLayoutType);
export { AuthPageLayoutType, BasicLayout, IFrameView };

54
apps/web-antd/src/main.ts Normal file
View File

@@ -0,0 +1,54 @@
import { preferencesManager } from '@vben-core/preferences';
import { overridesPreferences } from './preferences';
/**
* 应用初始化完成之后再进行页面加载渲染
*/
async function initApplication() {
// name用于指定项目唯一标识
// 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据
const env = import.meta.env.PROD ? 'prod' : 'dev';
const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${env}`;
// app偏好设置初始化
await preferencesManager.initPreferences({
namespace,
overrides: overridesPreferences,
});
// 启动应用并挂载
// vue应用主要逻辑及视图
const { bootstrap } = await import('./bootstrap');
await bootstrap(namespace);
// 移除并销毁loading
destoryAppLoading();
}
/**
* 移除并销毁loading
* 放在这里是而不是放在 index.html 的app标签内主要是因为这样比较不会生硬渲染过快可能会有闪烁
* 通过先添加css动画隐藏在动画结束后在移除loading节点来改善体验
*/
function destoryAppLoading() {
// 全局搜索文件 loading.html, 找到对应的节点
const loadingElement = document.querySelector('#__app-loading__');
if (loadingElement) {
loadingElement.classList.add('hidden');
const injectLoadingElements = document.querySelectorAll(
'[data-app-loading^="inject"]',
);
// 过渡动画结束后移除loading节点
loadingElement.addEventListener(
'transitionend',
() => {
loadingElement.remove();
injectLoadingElements.forEach((el) => el?.remove());
},
{ once: true },
);
}
}
initApplication();

View File

@@ -0,0 +1,10 @@
import { createProdMockServer } from 'vite-plugin-mock/client';
// 逐一导入您的mock.ts文件
// 如果使用vite.mock.config.ts只需直接导入文件
// 可以使用 import.meta.glob功能来进行全部导入
import userModule from '../mock/user';
export function setupProdMockServer() {
createProdMockServer([...userModule]);
}

View File

@@ -0,0 +1,8 @@
import type { DeepPartial } from '@vben/types';
import type { Preferences } from '@vben-core/preferences';
/**
* @description 项目配置文件
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
*/
export const overridesPreferences: DeepPartial<Preferences> = {};

View File

@@ -0,0 +1,127 @@
import { generatorMenus, generatorRoutes } from '@vben-core/helpers';
import { preferences } from '@vben-core/preferences';
import { useAccessStore } from '@vben-core/stores';
import type { RouteLocationNormalized, Router } from 'vue-router';
import { LOGIN_PATH } from '@vben/constants';
import { $t } from '@vben/locales';
import { startProgress, stopProgress } from '@vben/utils';
import { useTitle } from '@vueuse/core';
import { dynamicRoutes } from '@/router/routes';
/**
* 通用守卫配置
* @param router
*/
function setupCommonGuard(router: Router) {
// 记录已经加载的页面
const loadedPaths = new Set<string>();
router.beforeEach(async (to) => {
// 页面加载进度条
if (preferences.transition.progress) {
startProgress();
}
to.meta.loaded = loadedPaths.has(to.path);
return true;
});
router.afterEach((to) => {
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
loadedPaths.add(to.path);
// 关闭页面加载进度条
if (preferences.transition.progress) {
stopProgress();
}
// 动态修改标题
if (preferences.app.dynamicTitle) {
const { title } = to.meta;
useTitle(`${$t(title)} - ${preferences.app.name}`);
}
});
}
/**
* 权限访问守卫配置
* @param router
*/
function setupAccessGuard(router: Router) {
router.beforeEach(async (to, from) => {
const accessStore = useAccessStore();
const accessToken = accessStore.getAccessToken;
// accessToken 检查
if (!accessToken) {
if (to.path === '/') {
return loginPageMeta(to);
}
// 明确声明忽略权限访问权限,则可以访问
if (to.meta.ignoreAccess) {
return true;
}
// 没有访问权限,跳转登录页面
if (to.fullPath !== LOGIN_PATH) {
return loginPageMeta(to);
}
return to;
}
const accessRoutes = accessStore.getAccessRoutes;
// 是否已经生成过动态路由
if (accessRoutes && accessRoutes.length > 0) {
return true;
}
// 生成路由表
// 当前登录用户拥有的角色标识列表
const userRoles = accessStore.getUserRoles;
const routes = await generatorRoutes(dynamicRoutes, userRoles);
// 动态添加到router实例内
routes.forEach((route) => router.addRoute(route));
const menus = await generatorMenus(routes, router);
// 保存菜单信息和路由信息
accessStore.setAccessMenus(menus);
accessStore.setAccessRoutes(routes);
const redirectPath = (from.query.redirect ?? to.path) as string;
return {
path: decodeURIComponent(redirectPath),
replace: true,
};
});
}
/**
* 登录页面信息
* @param to
*/
function loginPageMeta(to: RouteLocationNormalized) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
}
/**
* 项目守卫配置
* @param router
*/
function createRouterGuard(router: Router) {
/** 通用 */
setupCommonGuard(router);
/** 权限访问 */
setupAccessGuard(router);
}
export { createRouterGuard };

View File

@@ -0,0 +1,57 @@
import type { RouteRecordName, RouteRecordRaw } from 'vue-router';
import { traverseTreeValues } from '@vben/utils';
import { createRouter, createWebHashHistory } from 'vue-router';
import { createRouterGuard } from './guard';
import { routes } from './routes';
/**
* @zh_CN 创建vue-router实例
*/
const router = createRouter({
history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH),
// 应该添加到路由的初始路由列表。
routes,
scrollBehavior: (_to, _from, savedPosition) => {
// if (to.path !== from.path) {
// const app = document.querySelector('#app');
// if (app) {
// app.scrollTop = 0;
// }
// }
return savedPosition || { left: 0, top: 0 };
},
});
/**
* @zh_CN 重置所有路由,如有指定白名单除外
*/
function resetRoutes() {
// 获取静态路由所有节点包含子节点的 name并排除不存在 name 字段的路由
const staticRouteNames = traverseTreeValues<
RouteRecordRaw,
RouteRecordName | undefined
>(routes, (route) => {
// 这些路由需要指定 name防止在路由重置时不能删除没有指定 name 的路由
if (import.meta.env.DEV && !route.name) {
console.warn(
`The route with the path ${route.path} needs to have the field name specified.`,
);
}
return route.name;
});
const { getRoutes, hasRoute, removeRoute } = router;
const allRoutes = getRoutes();
allRoutes.forEach(({ name }) => {
// 存在于路由表且非白名单才需要删除
if (name && !staticRouteNames.includes(name) && hasRoute(name)) {
removeRoute(name);
}
});
}
// 创建路由守卫
createRouterGuard(router);
export { resetRoutes, router };

View File

@@ -0,0 +1,85 @@
import type { RouteRecordRaw } from 'vue-router';
import { AuthPageLayoutType } from '@/layouts';
import { Fallback } from '@vben/common-ui';
import { $t } from '@vben/locales';
import Login from '@/views/_essential/authentication/login.vue';
/** 基本路由,这些路由是必须存在的 */
const essentialRoutes: RouteRecordRaw[] = [
{
component: AuthPageLayoutType,
meta: {
title: 'Authentication',
},
name: 'Authentication',
path: '/auth',
children: [
{
name: 'Login',
path: 'login',
component: Login,
meta: {
ignoreAccess: true,
title: $t('page.login'),
},
},
{
name: 'CodeLogin',
path: 'code-login',
component: () =>
import('@/views/_essential/authentication/code-login.vue'),
meta: {
ignoreAccess: true,
title: $t('page.code-login'),
},
},
{
name: 'QrCodeLogin',
path: 'qrcode-login',
component: () =>
import('@/views/_essential/authentication/qrcode-login.vue'),
meta: {
ignoreAccess: true,
title: $t('page.qrcode-login'),
},
},
{
name: 'ForgetPassword',
path: 'forget-password',
component: () =>
import('@/views/_essential/authentication/forget-password.vue'),
meta: {
ignoreAccess: true,
title: $t('page.forget-password'),
},
},
{
name: 'Register',
path: 'register',
component: () =>
import('@/views/_essential/authentication/register.vue'),
meta: {
ignoreAccess: true,
title: $t('page.register'),
},
},
],
},
// 错误页
{
component: Fallback,
meta: {
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
// ignoreAccess: true,
title: 'Fallback',
},
name: 'Fallback',
path: '/:path(.*)*',
},
];
export { essentialRoutes };

View File

@@ -0,0 +1,73 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
keepAlive: true,
title: '多级菜单',
},
name: 'Nested',
path: '/nested',
children: [
{
name: 'Menu1',
path: 'menu1',
component: () => import('@/views/nested/menu-1.vue'),
meta: {
keepAlive: true,
title: '菜单1',
},
},
{
name: 'Menu2',
path: 'menu2',
component: () => import('@/views/nested/menu-2.vue'),
meta: {
keepAlive: true,
title: '菜单2',
},
},
{
name: 'Menu3',
path: 'menu3',
meta: {
title: '菜单3',
},
children: [
{
name: 'Menu31',
path: 'menu3-1',
component: () => import('@/views/nested/menu-3-1.vue'),
meta: {
keepAlive: true,
title: '菜单3-1',
},
},
{
name: 'Menu32',
path: 'menu3-2',
meta: {
title: '菜单3-2',
},
children: [
{
name: 'Menu321',
path: 'menu3-2-1',
component: () => import('@/views/nested/menu-3-2-1.vue'),
meta: {
keepAlive: true,
title: '菜单3-2-1',
},
},
],
},
],
},
],
},
];
export default routes;

View File

@@ -0,0 +1,39 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout, IFrameView } from '@/layouts';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
title: '外部页面',
},
name: 'Outside',
path: '/outside',
redirect: '/outside/document',
children: [
{
name: 'Document',
path: 'document',
component: IFrameView,
meta: {
iframeSrc: 'https://doc.vvbin.cn/',
// keepAlive: true,
title: '项目文档',
},
},
{
name: 'IFrameView',
path: 'vue-document',
component: IFrameView,
meta: {
iframeSrc: 'https://cn.vuejs.org/',
keepAlive: true,
title: 'Vue 文档(缓存)',
},
},
],
},
];
export default routes;

View File

@@ -0,0 +1,30 @@
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '@/layouts';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
hideChildrenInMenu: true,
order: -1,
title: '首页',
},
name: 'Home',
path: '/',
redirect: '/welcome',
children: [
{
name: 'Welcome',
path: '/welcome',
component: () => import('@/views/dashboard/index.vue'),
meta: {
affixTab: true,
title: 'Welcome',
},
},
],
},
];
export default routes;

View File

@@ -0,0 +1,53 @@
import { preferences } from '@vben-core/preferences';
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout, IFrameView } from '@/layouts';
import { VBEN_GITHUB_URL } from '@vben/constants';
import { $t } from '@vben/locales/helper';
const routes: RouteRecordRaw[] = [
{
component: BasicLayout,
meta: {
icon: preferences.logo.source,
title: 'Vben',
},
name: 'AboutLayout',
path: '/vben-admin',
redirect: '/vben-admin/about',
children: [
{
name: 'About',
path: 'about',
component: () => import('@/views/about/index.vue'),
meta: {
icon: 'mdi:creative-commons',
title: $t('page.about'),
},
},
{
name: 'AboutDocument',
path: 'document',
component: IFrameView,
meta: {
icon: 'mdi:flame-circle',
iframeSrc: 'https://doc.vvbin.cn/',
keepAlive: true,
title: $t('page.document'),
},
},
{
name: 'Github',
path: 'github',
component: IFrameView,
meta: {
icon: 'mdi:github',
target: VBEN_GITHUB_URL,
title: 'Github',
},
},
],
},
];
export default routes;

View File

View File

@@ -0,0 +1,28 @@
import { mergeRouteModules } from '@vben-core/helpers';
import type { RouteRecordRaw } from 'vue-router';
import { essentialRoutes } from './_essential';
const dynamicRouteFiles = import.meta.glob('./dynamic/**/*.ts', {
eager: true,
});
const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
const externalRouteFiles = import.meta.glob('./external/**/*.ts', {
eager: true,
});
/** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
/** 静态路由列表,访问这些页面可以不需要权限 */
const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
/** 排除在主框架外的路由,这些路由没有菜单和顶部及其他框架内容 */
const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
/** 路由列表,由基本路由+静态路由组成 */
const routes: RouteRecordRaw[] = [...essentialRoutes, ...staticRoutes];
export { dynamicRoutes, externalRoutes, routes };

View File

@@ -0,0 +1,16 @@
import type { InitStoreOptions } from '@vben-core/stores';
import { initStore } from '@vben-core/stores';
import type { App } from 'vue';
/**
* @zh_CN 初始化pinia
* @param app vue app 实例
*/
async function setupStore(app: App, options: InitStoreOptions) {
const pinia = await initStore(options);
app.use(pinia);
}
export { setupStore };

View File

@@ -0,0 +1,17 @@
import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it } from 'vitest';
import { useCounterStore } from './example';
describe('useCounterStore', () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it('count test', () => {
setActivePinia(createPinia());
const counterStore = useCounterStore();
expect(counterStore.count).toBe(0);
});
});

View File

@@ -0,0 +1,14 @@
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
actions: {
increment() {
this.count++;
},
},
getters: {
double: (state) => state.count * 2,
},
persist: [],
state: () => ({ count: 0 }),
});

View File

@@ -0,0 +1,3 @@
# \_essential
此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。

View File

@@ -0,0 +1,24 @@
<script lang="ts" setup>
import type { LoginCodeParams } from '@vben/common-ui';
import { AuthenticationCodeLogin } from '@vben/common-ui';
import { ref } from 'vue';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
/**
* 异步处理登录操作
* Asynchronously handle the login process
* @param values 登录表单数据
*/
async function handleLogin(values: LoginCodeParams) {
// eslint-disable-next-line no-console
console.log(values);
}
</script>
<template>
<AuthenticationCodeLogin :loading="loading" @submit="handleLogin" />
</template>

View File

@@ -0,0 +1,17 @@
<script lang="ts" setup>
import { AuthenticationForgetPassword } from '@vben/common-ui';
import { ref } from 'vue';
defineOptions({ name: 'ForgetPassword' });
const loading = ref(false);
function handleSubmit(value: string) {
// eslint-disable-next-line no-console
console.log('reset email:', value);
}
</script>
<template>
<AuthenticationForgetPassword :loading="loading" @submit="handleSubmit" />
</template>

View File

@@ -0,0 +1,76 @@
<script lang="ts" setup>
import type { LoginAndRegisterParams } from '@vben/common-ui';
import { useRequest } from '@vben-core/request';
import { useAccessStore } from '@vben-core/stores';
import { getUserInfo, userLogin } from '@/apis';
import { AuthenticationLogin } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { notification } from 'ant-design-vue';
import { computed } from 'vue';
import { useRouter } from 'vue-router';
defineOptions({ name: 'Login' });
const router = useRouter();
const accessStore = useAccessStore();
const { loading, runAsync: runUserLogin } = useRequest(userLogin, {
manual: true,
});
const { loading: userInfoLoading, runAsync: runGetUserInfo } = useRequest(
getUserInfo,
{
manual: true,
},
);
/**
* 异步处理登录操作
* Asynchronously handle the login process
* @param values 登录表单数据
*/
async function handleLogin(values: LoginAndRegisterParams) {
// 异步处理用户登录操作并获取 accessToken
// Asynchronously handle the user login operation and obtain the accessToken
const { accessToken } = await runUserLogin(values);
// 如果成功获取到 accessToken
// If accessToken is successfully obtained
if (accessToken) {
// 将 accessToken 存储到 accessStore 中
// Store the accessToken in accessStore
accessStore.setAccessToken(accessToken);
// 获取用户信息并存储到 accessStore 中
// Get user information and store it in accessStore
const userInfo = await runGetUserInfo();
accessStore.setUserInfo(userInfo);
// 跳转到用户信息中定义的 homePath 路径
// Redirect to the homePath defined in the user information
await router.push(userInfo.homePath);
notification.success({
description: `${$t('authentication.login-success-desc')}:${userInfo.realName}`,
duration: 3,
message: $t('authentication.login-success'),
});
}
}
const loginLoading = computed(() => {
return loading.value || userInfoLoading.value;
});
</script>
<template>
<AuthenticationLogin
username-placeholder="vben"
password-placeholder="123456"
:loading="loginLoading"
@submit="handleLogin"
/>
</template>

View File

@@ -0,0 +1,9 @@
<script lang="ts" setup>
import { AuthenticationQrCodeLogin } from '@vben/common-ui';
defineOptions({ name: 'QrCodeLogin' });
</script>
<template>
<AuthenticationQrCodeLogin />
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
import type { LoginAndRegisterParams } from '@vben/common-ui';
import { AuthenticationRegister } from '@vben/common-ui';
import { ref } from 'vue';
defineOptions({ name: 'Register' });
const loading = ref(false);
function handleSubmit(value: LoginAndRegisterParams) {
// eslint-disable-next-line no-console
console.log('register submit:', value);
}
</script>
<template>
<AuthenticationRegister :loading="loading" @submit="handleSubmit" />
</template>

View File

@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
defineOptions({ name: 'About' });
onMounted(() => {
// eslint-disable-next-line no-console
console.log('About');
});
</script>
<template>
<div>
about
<input class="bg-background border-border" />
</div>
</template>

View File

@@ -0,0 +1,7 @@
<script lang="ts" setup>
defineOptions({ name: 'WelCome' });
</script>
<template>
<div>dashboard</div>
</template>

View File

@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
defineOptions({ name: 'Menu1' });
onMounted(() => {
// eslint-disable-next-line no-console
console.log('Menu1');
});
</script>
<template>
<div class="p-5">
menu1
<input class="bg-background border-border" />
</div>
</template>

View File

@@ -0,0 +1,16 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
defineOptions({ name: 'Menu2' });
onMounted(() => {
// eslint-disable-next-line no-console
console.log('Menu2');
});
</script>
<template>
<div class="p-5">
menu2
<input class="bg-background border-border" />
</div>
</template>

View File

@@ -0,0 +1,15 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
defineOptions({ name: 'Menu31' });
onMounted(() => {
// eslint-disable-next-line no-console
console.log('Menu3-1');
});
</script>
<template>
<div class="p-5">
Menu3-1
<input class="bg-background border-border" />
</div>
</template>

View File

@@ -0,0 +1,15 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
defineOptions({ name: 'Menu321' });
onMounted(() => {
// eslint-disable-next-line no-console
console.log('Menu3-2-1');
});
</script>
<template>
<div class="p-5">
menu-3-2-1
<input class="bg-background border-border" />
</div>
</template>

View File

@@ -0,0 +1 @@
export { default } from '@vben/tailwind-config';

View File

@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web-app.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"references": [{ "path": "./tsconfig.node.json" }],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/node.json",
"compilerOptions": {
"composite": true,
"noEmit": false
},
"include": ["vite.config.mts"]
}

View File

@@ -0,0 +1,35 @@
import { defineConfig } from '@vben/vite-config';
export default defineConfig({
application: {
compress: false,
compressTypes: ['brotli', 'gzip'],
importmap: false,
importmapOptions: {
// 通过 Importmap CDN 方式引入,
// 目前只有esm.sh源兼容性好一点jspm.io对于 esm 入口要求高
defaultProvider: 'esm.sh',
importmap: [
{ name: 'vue' },
{ name: 'pinia' },
{ name: 'vue-router' },
{ name: 'vue-i18n' },
{ name: 'dayjs' },
{ name: 'vue-demi' },
],
},
visualizer: false,
},
vite: {
server: {
proxy: {
'/vben-api': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/vben-api/, ''),
target: 'http://localhost:3000',
ws: true,
},
},
},
},
});