feat: Feature/pro docs (#70)

* chore: merge main

* feat: update docs

* feat: remove coze-assistant

* feat: add watermark plugin

* feat: update preferences

* feat: update docs

---------

Co-authored-by: vince <vince292007@gmail.com>
pull/48/MERGE
Vben 2024-07-28 14:29:05 +08:00 committed by GitHub
parent 14538f7ed5
commit 376fd17a61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
225 changed files with 7731 additions and 1784 deletions

View File

@ -11,7 +11,7 @@
},
"privatePackages": { "version": true, "tag": true },
"linked": [],
"access": "restricted",
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []

View File

@ -45,5 +45,5 @@ jobs:
- name: Test and Build
run: |
pnpm run test
pnpm run test:unit
pnpm run build

View File

@ -68,7 +68,7 @@ jobs:
# run: git config --global user.email "you@example.com" && git config --global user.name "Your Name"
- name: Vitest tests
run: pnpm run test
run: pnpm run test:unit
# - name: Upload coverage
# uses: codecov/codecov-action@v4

View File

@ -7,7 +7,7 @@ ls:
.css: kebab-case | pointcase
.d.ts: kebab-case | pointcase
# shadcn 自动生成文件为 PascalCase 格式
packages/@vben-core/ui-kit/shadcn-ui/src/components/ui:
packages/@core/ui-kit/shadcn-ui/src/components/ui:
.vue: PascalCase
ignore:
@ -25,3 +25,4 @@ ignore:
- .vscode
- .idea
- node_modules
- .cache

View File

@ -71,7 +71,8 @@
"**/CVS": true,
"**/.stylelintcache": true,
"**/.DS_Store": true,
"**/vite.config.mts.*": true
"**/vite.config.mts.*": true,
"**/tea.yaml": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
@ -190,7 +191,7 @@
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,.ls-lint*,cspell.json",
"tailwind.config.mjs": "postcss.*"
},
"commentTranslate.hover.enabled": true,
"commentTranslate.hover.enabled": false,
"i18n-ally.keystyle": "nested",
"commentTranslate.multiLineMerge": true
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024-present, Vben Admin
Copyright (c) 2024-present, Vben
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -2,7 +2,7 @@
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE)
<h1>Vue vben admin</h1>
<h1>Vue Vben Admin</h1>
</div>
**English** | [中文](./README.zh-CN.md)

View File

@ -0,0 +1,14 @@
export default defineEventHandler((event) => {
// setResponseHeaders(event, {
// 'Access-Control-Allow-Credentials': 'true',
// 'Access-Control-Allow-Headers': '*',
// 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
// 'Access-Control-Allow-Origin': '*',
// 'Access-Control-Expose-Headers': '*',
// });
if (event.method === 'OPTIONS') {
event.node.res.statusCode = 204;
event.node.res.statusMessage = 'No Content.';
return 'OK';
}
});

View File

@ -3,4 +3,16 @@ import errorHandler from './error';
export default defineNitroConfig({
devErrorHandler: errorHandler,
errorHandler: '~/error',
routeRules: {
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': '*',
},
},
},
});

View File

@ -80,8 +80,8 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
icon: 'mdi:button-cursor',
title: 'page.demos.access.adminVisible',
},
name: 'AccessAdminVisible',
path: 'admin-visible',
name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible',
},
super: {
component: '/demos/access/super-visible',
@ -89,8 +89,8 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
icon: 'mdi:button-cursor',
title: 'page.demos.access.superVisible',
},
name: 'AccessSuperVisible',
path: 'super-visible',
name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible',
},
user: {
component: '/demos/access/user-visible',
@ -98,8 +98,8 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
icon: 'mdi:button-cursor',
title: 'page.demos.access.userVisible',
},
name: 'AccessUserVisible',
path: 'user-visible',
name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible',
},
};
@ -117,8 +117,8 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
redirect: '/demos/access',
children: [
{
name: 'Access',
path: 'access',
name: 'AccessDemos',
path: '/demosaccess',
meta: {
icon: 'mdi:cloud-key-outline',
title: 'page.demos.access.backendPermissions',
@ -126,8 +126,8 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
redirect: '/demos/access/page-control',
children: [
{
name: 'AccessPageControl',
path: 'page-control',
name: 'AccessPageControlDemo',
path: '/demos/access/page-control',
component: '/demos/access/index',
meta: {
icon: 'mdi:page-previous-outline',
@ -135,8 +135,8 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
},
},
{
name: 'AccessButtonControl',
path: 'button-control',
name: 'AccessButtonControlDemo',
path: '/demos/access/button-control',
component: '/demos/access/button-control',
meta: {
icon: 'mdi:button-cursor',
@ -144,8 +144,8 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
},
},
{
name: 'AccessMenuVisible403',
path: 'menu-visible-403',
name: 'AccessMenuVisible403Demo',
path: '/demos/access/menu-visible-403',
component: '/demos/access/menu-visible-403',
meta: {
authority: ['no-body'],

View File

@ -1,6 +1,5 @@
VITE_PORT = 5555
# 应用标题
VITE_GLOB_APP_TITLE=Vben Admin
# spa-title
VITE_GLOB_APP_TITLE = Vben Admin
VITE_APP_NAMESPACE = web-antd
# 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=vben-web-antd

View File

@ -1,8 +1,7 @@
# public path
VITE_PUBLIC_PATH = /
VITE_BASE=/
# Basic interface address SPA
VITE_GLOB_API_URL=/api
VITE_VISUALIZER = true
VITE_VISUALIZER=true

View File

@ -1,5 +1,16 @@
VITE_PUBLIC_PATH = /
# 端口号
VITE_PORT=5555
VITE_BASE=/
# 接口地址
VITE_GLOB_API_URL=/api
VITE_NITRO_MOCK = true
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=true
# 是否打开 devtoolstrue 为打开false 为关闭
VITE_DEVTOOLS=false
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true

View File

@ -1,9 +1,16 @@
# public path
VITE_PUBLIC_PATH = /
VITE_BASE=/
# Basic interface address SPA
VITE_GLOB_API_URL = https://mock-napi.vben.pro/api
# 接口地址
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
VITE_COMPRESS = gzip
# 是否开启压缩,可以设置为 none, brotli, gzip
VITE_COMPRESS=gzip
VITE_PWA = false
# 是否开启 PWA
VITE_PWA=false
# vue-router 的模式
VITE_ROUTER_HISTORY=hash
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true

View File

@ -14,6 +14,19 @@
<!-- 由 vite 注入 VITE_GLOB_APP_TITLE 变量,在 .env 文件内配置 -->
<title><%= VITE_GLOB_APP_TITLE %></title>
<link rel="icon" href="/favicon.ico" />
<script>
// 生产环境下注入百度统计
if (window._VBEN_ADMIN_PRO_APP_CONF_) {
var _hmt = _hmt || [];
(function () {
var hm = document.createElement('script');
hm.src =
'https://hm.baidu.com/hm.js?d20a01273820422b6aa2ee41b6c9414d';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();
}
</script>
</head>
<body>
<div id="app"></div>

View File

@ -44,7 +44,7 @@
"ant-design-vue": "^4.2.3",
"dayjs": "^1.11.12",
"pinia": "2.1.7",
"vue": "^3.4.33",
"vue": "^3.4.34",
"vue-router": "^4.4.0"
}
}

View File

@ -0,0 +1,33 @@
import { requestClient } from '#/api/request';
export namespace AuthApi {
/** 登录接口参数 */
export interface LoginParams {
password: string;
username: string;
}
/** 登录接口返回值 */
export interface LoginResult {
accessToken: string;
desc: string;
realName: string;
refreshToken: string;
userId: string;
username: string;
}
}
/**
*
*/
export async function login(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
}
/**
*
*/
export async function getAccessCodes() {
return requestClient.get<string[]>('/auth/codes');
}

View File

@ -1,6 +1,6 @@
import type { RouteRecordStringComponent } from '@vben/types';
import { requestClient } from '#/apis/request';
import { requestClient } from '#/api/request';
/**
*

View File

@ -1,6 +1,6 @@
import type { UserInfo } from '@vben/types';
import { requestClient } from '#/apis/request';
import { requestClient } from '#/api/request';
/**
*

View File

@ -1,4 +1,4 @@
import { requestClient } from '#/apis/request';
import { requestClient } from '#/api/request';
/**
*

View File

@ -1,3 +1,2 @@
export * from './core';
export * from './demos';
export type * from './types';

View File

@ -3,6 +3,7 @@
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences';
import { RequestClient } from '@vben/request';
@ -10,13 +11,11 @@ import { message } from 'ant-design-vue';
import { useAccessStore } from '#/store';
/**
*
* Create a request instance
*/
function createRequestClient() {
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string) {
const client = new RequestClient({
baseURL: import.meta.env.VITE_GLOB_API_URL,
baseURL,
// 为每个请求携带 Authorization
makeAuthorization: () => {
return {
@ -43,6 +42,13 @@ function createRequestClient() {
};
},
makeErrorMessage: (msg) => message.error(msg),
makeRequestHeaders: () => {
return {
// 为每个请求携带 Accept-Language
'Accept-Language': preferences.app.locale,
};
},
});
client.addResponseInterceptor<HttpResponse>((response) => {
const { data: responseData, status } = response;
@ -56,9 +62,4 @@ function createRequestClient() {
return client;
}
const requestClient = createRequestClient();
// 其他配置的请求方法
// const { request: xxxRequest } = createRequest();
export { requestClient };
export const requestClient = createRequestClient(apiURL);

View File

@ -1,17 +0,0 @@
import type { UserApi } from '../types';
import { requestClient } from '#/apis/request';
/**
*
*/
export async function login(data: UserApi.LoginParams) {
return requestClient.post<UserApi.LoginResult>('/auth/login', data);
}
/**
*
*/
export async function getAccessCodes() {
return requestClient.get<string[]>('/auth/codes');
}

View File

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

View File

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

View File

@ -1,10 +1,9 @@
import { createApp } from 'vue';
import { preferences } from '@vben/preferences';
import '@vben/styles';
import '@vben/styles/antd';
import { loadMessages, setupI18n } from '#/locales';
import { setupI18n } from '#/locales';
import { setupStore } from '#/store';
import App from './app.vue';
@ -14,11 +13,7 @@ async function bootstrap(namespace: string) {
const app = createApp(App);
// 国际化 i18n 配置
await setupI18n(app, {
defaultLocale: preferences.app.locale,
loadMessages,
missingWarn: !import.meta.env.PROD,
});
await setupI18n(app);
// 配置 pinia-store
await setupStore(app, { namespace });

View File

@ -1,9 +1,11 @@
import type { SupportedLanguagesType } from '@vben/types';
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
import type { Locale } from 'ant-design-vue/es/locale';
import type { App } from 'vue';
import { ref } from 'vue';
import { $t, loadLocalesMap, setupI18n } from '@vben/locales';
import { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
import { preferences } from '@vben/preferences';
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';
@ -76,4 +78,13 @@ async function loadAntdLocale(lang: SupportedLanguagesType) {
}
}
async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
await coreSetup(app, {
defaultLocale: preferences.app.locale,
loadMessages,
missingWarn: !import.meta.env.PROD,
...options,
});
}
export { $t, antdLocale, loadMessages, setupI18n };

View File

@ -33,11 +33,17 @@
"text": "Text Badge",
"color": "Badge Color"
},
"activeIcon": {
"title": "Active Menu Icon",
"children": "Children Active Icon"
},
"fallback": { "title": "Fallback Page" },
"features": {
"title": "Features",
"hideChildrenInMenu": "Hide Menu Children",
"loginExpired": "Login Expired",
"icons": "Icons",
"watermark": "Watermark",
"tabs": "Tabs",
"tabDetail": "Tab Detail Page"
},

View File

@ -33,6 +33,10 @@
"text": "文本徽标",
"color": "徽标颜色"
},
"activeIcon": {
"title": "菜单激活图标",
"children": "子级激活图标"
},
"fallback": {
"title": "缺省页"
},
@ -40,6 +44,8 @@
"title": "功能",
"hideChildrenInMenu": "隐藏子菜单",
"loginExpired": "登录过期",
"icons": "图标",
"watermark": "水印",
"tabs": "标签页",
"tabDetail": "标签详情页"
},

View File

@ -1,3 +1,4 @@
import { unmountGlobalLoading } from '@vben/hooks';
import { initPreferences } from '@vben/preferences';
import { overridesPreferences } from './preferences';
@ -9,7 +10,8 @@ async function initApplication() {
// name用于指定项目唯一标识
// 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据
const env = import.meta.env.PROD ? 'prod' : 'dev';
const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${env}`;
const appVersion = import.meta.env.VITE_APP_VERSION;
const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;
// app偏好设置初始化
await initPreferences({
@ -23,38 +25,7 @@ async function initApplication() {
await bootstrap(namespace);
// 移除并销毁loading
destroyAppLoading();
}
/**
* loading
* index.html app
* cssloading
*
*/
function destroyAppLoading() {
// 查找全局 loading 元素
const loadingElement = document.querySelector('#__app-loading__');
if (loadingElement) {
// 添加隐藏类,触发过渡动画
loadingElement.classList.add('hidden');
// 查找所有需要移除的注入 loading 元素
const injectLoadingElements = document.querySelectorAll(
'[data-app-loading^="inject"]',
);
// 当过渡动画结束时,移除 loading 元素和所有注入的 loading 元素
loadingElement.addEventListener(
'transitionend',
() => {
loadingElement.remove(); // 移除 loading 元素
injectLoadingElements.forEach((el) => el.remove()); // 移除所有注入的 loading 元素
},
{ once: true },
); // 确保事件只触发一次
}
unmountGlobalLoading();
}
initApplication();

View File

@ -1,9 +1,15 @@
import { useAppConfig } from '@vben/hooks';
import { defineOverridesPreferences } from '@vben/preferences';
const { appTitle } = useAppConfig(import.meta.env, import.meta.env.PROD);
/**
* @description
* 使
*/
export const overridesPreferences = defineOverridesPreferences({
// overrides
app: {
name: appTitle,
},
});

View File

@ -8,7 +8,7 @@ import { preferences } from '@vben/preferences';
import { message } from 'ant-design-vue';
import { getAllMenus } from '#/apis';
import { getAllMenus } from '#/api';
import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales';

View File

@ -1,6 +1,10 @@
import type { RouteRecordName, RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import {
createRouter,
createWebHashHistory,
createWebHistory,
} from 'vue-router';
import { traverseTreeValues } from '@vben/utils';
@ -11,11 +15,14 @@ import { routes } from './routes';
* @zh_CN vue-router
*/
const router = createRouter({
history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH),
history:
import.meta.env.VITE_ROUTER_HISTORY === 'hash'
? createWebHashHistory(import.meta.env.VITE_BASE)
: createWebHistory(import.meta.env.VITE_BASE),
// 应该添加到路由的初始路由列表。
routes,
scrollBehavior: () => ({ left: 0, top: 0 }),
// 是否应该禁止尾部斜杠。默认为假
// 是否应该禁止尾部斜杠。
// strict: true,
});

View File

@ -13,11 +13,10 @@ const routes: RouteRecordRaw[] = [
},
name: 'Dashboard',
path: '/',
redirect: '/analytics',
children: [
{
name: 'Analytics',
path: 'analytics',
path: '/analytics',
component: () => import('#/views/dashboard/analytics/index.vue'),
meta: {
affixTab: true,
@ -27,7 +26,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'Workspace',
path: 'workspace',
path: '/workspace',
component: () => import('#/views/dashboard/workspace/index.vue'),
meta: {
title: $t('page.dashboard.workspace'),

View File

@ -14,7 +14,6 @@ const routes: RouteRecordRaw[] = [
},
name: 'Demos',
path: '/demos',
redirect: '/demos/access',
children: [
// 权限控制
{
@ -22,13 +21,12 @@ const routes: RouteRecordRaw[] = [
icon: 'mdi:shield-key-outline',
title: $t('page.demos.access.frontendPermissions'),
},
name: 'Access',
path: 'access',
redirect: '/demos/access/page-control',
name: 'AccessDemos',
path: '/demos/access',
children: [
{
name: 'AccessPageControl',
path: 'page-control',
name: 'AccessPageControlDemo',
path: '/demos/access/page-control',
component: () => import('#/views/demos/access/index.vue'),
meta: {
icon: 'mdi:page-previous-outline',
@ -36,8 +34,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'AccessButtonControl',
path: 'button-control',
name: 'AccessButtonControlDemo',
path: '/demos/access/button-control',
component: () => import('#/views/demos/access/button-control.vue'),
meta: {
icon: 'mdi:button-cursor',
@ -45,8 +43,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'AccessMenuVisible403',
path: 'menu-visible-403',
name: 'AccessMenuVisible403Demo',
path: '/demos/access/menu-visible-403',
component: () =>
import('#/views/demos/access/menu-visible-403.vue'),
meta: {
@ -57,8 +55,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'AccessSuperVisible',
path: 'super-visible',
name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible',
component: () => import('#/views/demos/access/super-visible.vue'),
meta: {
authority: ['super'],
@ -67,8 +65,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'AccessAdminVisible',
path: 'admin-visible',
name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible',
component: () => import('#/views/demos/access/admin-visible.vue'),
meta: {
authority: ['admin'],
@ -77,8 +75,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'AccessUserVisible',
path: 'user-visible',
name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible',
component: () => import('#/views/demos/access/user-visible.vue'),
meta: {
authority: ['user'],
@ -94,13 +92,39 @@ const routes: RouteRecordRaw[] = [
icon: 'mdi:feature-highlight',
title: $t('page.demos.features.title'),
},
name: 'Features',
path: 'features',
redirect: '/demos/features/tabs',
name: 'FeaturesDemos',
path: '/demos/features',
children: [
{
name: 'LoginExpiredDemo',
path: '/demos/features/login-expired',
component: () =>
import('#/views/demos/features/login-expired/index.vue'),
meta: {
icon: 'mdi:encryption-expiration',
title: $t('page.demos.features.loginExpired'),
},
},
{
name: 'IconsDemo',
path: '/demos/features/icons',
component: () => import('#/views/demos/features/icons/index.vue'),
meta: {
title: $t('page.demos.features.icons'),
},
},
{
name: 'WatermarkDemo',
path: '/demos/features/watermark',
component: () =>
import('#/views/demos/features/watermark/index.vue'),
meta: {
title: $t('page.demos.features.watermark'),
},
},
{
name: 'FeatureTabsDemo',
path: 'tabs',
path: '/demos/features/tabs',
component: () => import('#/views/demos/features/tabs/index.vue'),
meta: {
icon: 'lucide:app-window',
@ -109,7 +133,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'FeatureTabDetailDemo',
path: 'tabs/detail/:id',
path: '/demos/features/tabs/detail/:id',
component: () =>
import('#/views/demos/features/tabs/tab-detail.vue'),
meta: {
@ -120,19 +144,19 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'HideChildrenInMenuParent',
path: 'hide-menu-children',
name: 'HideChildrenInMenuParentDemo',
path: '/demos/features/hide-menu-children',
component: () =>
import('#/views/demos/features/hide-menu-children/parent.vue'),
meta: {
hideChildrenInMenu: true,
icon: 'ic:round-menu',
title: 'page.demos.features.hideChildrenInMenu',
title: $t('page.demos.features.hideChildrenInMenu'),
},
children: [
{
name: 'HideChildrenInMenuChildren',
path: 'hide-children-in-menu',
name: 'HideChildrenInMenuChildrenDemo',
path: '/demos/features/hide-menu-children/children',
component: () =>
import(
'#/views/demos/features/hide-menu-children/children.vue'
@ -140,31 +164,20 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
name: 'LoginExpired',
path: 'login-expired',
component: () =>
import('#/views/demos/features/login-expired/index.vue'),
meta: {
icon: 'mdi:encryption-expiration',
title: $t('page.demos.features.loginExpired'),
},
},
],
},
// 面包屑导航
{
name: 'BreadcrumbDemos',
path: 'breadcrumb',
path: '/demos/breadcrumb',
meta: {
icon: 'lucide:navigation',
title: $t('page.demos.breadcrumb.navigation'),
},
redirect: '/demos/breadcrumb/lateral',
children: [
{
name: 'BreadcrumbLateral',
path: 'lateral',
name: 'BreadcrumbLateralDemo',
path: '/demos/breadcrumb/lateral',
component: () => import('#/views/demos/breadcrumb/lateral.vue'),
meta: {
icon: 'lucide:navigation',
@ -172,8 +185,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'BreadcrumbLateralDetail',
path: 'lateral-detail',
name: 'BreadcrumbLateralDetailDemo',
path: '/demos/breadcrumb/lateral-detail',
component: () =>
import('#/views/demos/breadcrumb/lateral-detail.vue'),
meta: {
@ -183,17 +196,16 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'BreadcrumbLevel',
path: 'level',
name: 'BreadcrumbLevelDemo',
path: '/demos/breadcrumb/level',
meta: {
icon: 'lucide:navigation',
title: $t('page.demos.breadcrumb.level'),
},
redirect: '/demos/breadcrumb/level/detail',
children: [
{
name: 'BreadcrumbLevelDetail',
path: 'detail',
name: 'BreadcrumbLevelDetailDemo',
path: '/demos/breadcrumb/level/detail',
component: () =>
import('#/views/demos/breadcrumb/level-detail.vue'),
meta: {
@ -210,13 +222,12 @@ const routes: RouteRecordRaw[] = [
icon: 'mdi:lightbulb-error-outline',
title: $t('page.demos.fallback.title'),
},
name: 'Fallback',
path: 'fallback',
redirect: '/demos/fallback/403',
name: 'FallbackDemos',
path: '/demos/fallback',
children: [
{
name: 'Fallback403',
path: '403',
name: 'Fallback403Demo',
path: '/demos/fallback/403',
component: () => import('#/views/_core/fallback/forbidden.vue'),
meta: {
icon: 'mdi:do-not-disturb-alt',
@ -224,8 +235,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'Fallback404',
path: '404',
name: 'Fallback404Demo',
path: '/demos/fallback/404',
component: () => import('#/views/_core/fallback/not-found.vue'),
meta: {
icon: 'mdi:table-off',
@ -233,8 +244,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'Fallback500',
path: '500',
name: 'Fallback500Demo',
path: '/demos/fallback/500',
component: () =>
import('#/views/_core/fallback/internal-error.vue'),
meta: {
@ -243,8 +254,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'FallbackOffline',
path: 'offline',
name: 'FallbackOfflineDemo',
path: '/demos/fallback/offline',
component: () => import('#/views/_core/fallback/offline.vue'),
meta: {
icon: 'mdi:offline',
@ -261,14 +272,13 @@ const routes: RouteRecordRaw[] = [
icon: 'lucide:circle-dot',
title: $t('page.demos.badge.title'),
},
name: 'BadgeDemo',
path: 'badge',
redirect: '/demos/badge/dot',
name: 'BadgeDemos',
path: '/demos/badge',
children: [
{
name: 'BadgeDotDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: 'dot',
path: '/demos/badge/dot',
meta: {
badgeType: 'dot',
icon: 'lucide:square-dot',
@ -278,7 +288,7 @@ const routes: RouteRecordRaw[] = [
{
name: 'BadgeTextDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: 'text',
path: '/demos/badge/text',
meta: {
badge: '10',
icon: 'lucide:square-dot',
@ -288,7 +298,7 @@ const routes: RouteRecordRaw[] = [
{
name: 'BadgeColorDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: 'color',
path: '/demos/badge/color',
meta: {
badge: 'Hot',
badgeVariants: 'destructive',
@ -298,28 +308,48 @@ const routes: RouteRecordRaw[] = [
},
],
},
// 菜单激活图标
{
meta: {
activeIcon: 'fluent-emoji:radioactive',
icon: 'bi:radioactive',
title: $t('page.demos.activeIcon.title'),
},
name: 'ActiveIconDemos',
path: '/demos/active-icon',
children: [
{
name: 'ActiveIconDemo',
component: () => import('#/views/demos/active-icon/index.vue'),
path: '/demos/active-icon/children',
meta: {
activeIcon: 'fluent-emoji:radioactive',
icon: 'bi:radioactive',
title: $t('page.demos.activeIcon.children'),
},
},
],
},
// 外部链接
{
meta: {
icon: 'ic:round-settings-input-composite',
title: $t('page.demos.outside.title'),
},
name: 'Outside',
path: 'outside',
redirect: '/demos/outside/iframe',
name: 'OutsideDemos',
path: '/demos/outside',
children: [
{
name: 'iframe',
path: 'iframe',
name: 'IframeDemos',
path: '/demos/outside/iframe',
meta: {
icon: 'mdi:newspaper-variant-outline',
title: $t('page.demos.outside.embedded'),
},
redirect: '/demos/outside/iframe/vue-document',
children: [
{
name: 'VueDocument',
path: 'vue-document',
name: 'VueDocumentDemo',
path: '/demos/outside/iframe/vue-document',
component: IFrameView,
meta: {
icon: 'logos:vue',
@ -329,8 +359,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'Tailwindcss',
path: 'tailwindcss',
name: 'TailwindcssDemo',
path: '/demos/outside/iframe/tailwindcss',
component: IFrameView,
meta: {
icon: 'devicon:tailwindcss',
@ -342,17 +372,16 @@ const routes: RouteRecordRaw[] = [
],
},
{
name: 'ExternalLink',
path: 'external-link',
name: 'ExternalLinkDemos',
path: '/demos/outside/external-link',
meta: {
icon: 'mdi:newspaper-variant-multiple-outline',
title: $t('page.demos.outside.externalLink'),
},
redirect: '/demos/outside/external-link/vite',
children: [
{
name: 'Vite',
path: 'vite',
name: 'ViteDemo',
path: '/demos/outside/external-link/vite',
component: IFrameView,
meta: {
icon: 'logos:vitejs',
@ -361,8 +390,8 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'VueUse',
path: 'vue-use',
name: 'VueUseDemo',
path: '/demos/outside/external-link/vue-use',
component: IFrameView,
meta: {
icon: 'logos:vueuse',
@ -380,13 +409,12 @@ const routes: RouteRecordRaw[] = [
icon: 'ic:round-menu',
title: $t('page.demos.nested.title'),
},
name: 'Nested',
path: 'nested',
redirect: '/demos/nested/menu1',
name: 'NestedDemos',
path: '/demos/nested',
children: [
{
name: 'Menu1',
path: 'menu1',
name: 'Menu1Demo',
path: '/demos/nested/menu1',
component: () => import('#/views/demos/nested/menu-1.vue'),
meta: {
icon: 'ic:round-menu',
@ -395,18 +423,17 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'Menu2',
path: 'menu2',
name: 'Menu2Demo',
path: '/demos/nested/menu2',
meta: {
icon: 'ic:round-menu',
keepAlive: true,
title: $t('page.demos.nested.menu2'),
},
redirect: '/demos/nested/menu2/menu2-1',
children: [
{
name: 'Menu21',
path: 'menu2-1',
name: 'Menu21Demo',
path: '/demos/nested/menu2/menu2-1',
component: () => import('#/views/demos/nested/menu-2-1.vue'),
meta: {
icon: 'ic:round-menu',
@ -417,16 +444,15 @@ const routes: RouteRecordRaw[] = [
],
},
{
name: 'Menu3',
path: 'menu3',
name: 'Menu3Demo',
path: '/demos/nested/menu3',
meta: {
icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3'),
},
redirect: '/demos/nested/menu3/menu3-1',
children: [
{
name: 'Menu31',
name: 'Menu31Demo',
path: 'menu3-1',
component: () => import('#/views/demos/nested/menu-3-1.vue'),
meta: {
@ -436,17 +462,16 @@ const routes: RouteRecordRaw[] = [
},
},
{
name: 'Menu32',
name: 'Menu32Demo',
path: 'menu3-2',
meta: {
icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3_2'),
},
redirect: '/demos/nested/menu3/menu3-2/menu3-2-1',
children: [
{
name: 'Menu321',
path: 'menu3-2-1',
name: 'Menu321Demo',
path: '/demos/nested/menu3/menu3-2/menu3-2-1',
component: () =>
import('#/views/demos/nested/menu-3-2-1.vue'),
meta: {

View File

@ -17,11 +17,10 @@ const routes: RouteRecordRaw[] = [
},
name: 'VbenProject',
path: '/vben-admin',
redirect: '/vben-admin/about',
children: [
{
name: 'VbenAbout',
path: 'about',
path: '/vben-admin/about',
component: () => import('#/views/_core/vben/about/index.vue'),
meta: {
badgeType: 'dot',
@ -32,7 +31,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'VbenDocument',
path: 'document',
path: '/vben-admin/document',
component: IFrameView,
meta: {
icon: 'lucide:book-open-text',
@ -43,7 +42,7 @@ const routes: RouteRecordRaw[] = [
},
{
name: 'VbenGithub',
path: 'github',
path: '/vben-admin/github',
component: IFrameView,
meta: {
icon: 'mdi:github',

View File

@ -11,7 +11,7 @@ import { resetAllStores, useCoreAccessStore } from '@vben/stores';
import { notification } from 'ant-design-vue';
import { defineStore } from 'pinia';
import { getAccessCodes, getUserInfo, login } from '#/apis';
import { getAccessCodes, getUserInfo, login } from '#/api';
import { $t } from '#/locales';
export const useAccessStore = defineStore('access', () => {

View File

@ -1,5 +1,7 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Fallback403Demo' });
</script>
<template>

View File

@ -1,5 +1,7 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Fallback500Demo' });
</script>
<template>

View File

@ -1,5 +1,7 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Fallback404Demo' });
</script>
<template>

View File

@ -1,5 +1,7 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'FallbackOfflineDemo' });
</script>
<template>

View File

@ -3,8 +3,6 @@ import { onMounted, ref } from 'vue';
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
defineOptions({ name: 'AnalyticsTrends' });
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@ -3,8 +3,6 @@ import { onMounted, ref } from 'vue';
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
defineOptions({ name: 'AnalyticsVisitsData' });
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@ -3,8 +3,6 @@ import { onMounted, ref } from 'vue';
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
defineOptions({ name: 'AnalyticsVisitsSales' });
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@ -3,8 +3,6 @@ import { onMounted, ref } from 'vue';
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
defineOptions({ name: 'AnalyticsVisitsSource' });
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@ -3,8 +3,6 @@ import { onMounted, ref } from 'vue';
import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
defineOptions({ name: 'AnalyticsVisits' });
const chartRef = ref<EchartsUIType>();
const { renderEcharts } = useEcharts(chartRef);

View File

@ -20,8 +20,6 @@ import AnalyticsVisitsData from './analytics-visits-data.vue';
import AnalyticsVisitsSales from './analytics-visits-sales.vue';
import AnalyticsVisitsSource from './analytics-visits-source.vue';
defineOptions({ name: 'Analytics' });
const overviewItems: AnalysisOverviewItem[] = [
{
icon: SvgCardIcon,

View File

@ -22,8 +22,6 @@ import { useAccessStore } from '#/store';
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
defineOptions({ name: 'Workspace' });
const accessStore = useAccessStore();
const projectItems: WorkbenchProjectItem[] = [

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'AccessAdminVisible' });
</script>
<template>

View File

@ -9,8 +9,6 @@ import { Button } from 'ant-design-vue';
import { resetAllStores, useAccessStore } from '#/store';
defineOptions({ name: 'AccessButtonControl' });
const accounts: Record<string, LoginAndRegisterParams> = {
admin: {
password: '123456',

View File

@ -9,8 +9,6 @@ import { Button } from 'ant-design-vue';
import { resetAllStores, useAccessStore } from '#/store';
defineOptions({ name: 'Access' });
const accounts: Record<string, LoginAndRegisterParams> = {
admin: {
password: '123456',
@ -51,7 +49,9 @@ async function handleToggleAccessMode() {
resetAllStores();
await accessStore.authLogin(accounts.super, async () => {
router.go(0);
setTimeout(() => {
router.go(0);
}, 150);
});
}
</script>

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'AccessMenuVisible403' });
</script>
<template>

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'AccessSuperVisible' });
</script>
<template>

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'AccessUserVisible' });
</script>
<template>

View File

@ -0,0 +1,11 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
</script>
<template>
<Fallback
description="用于菜单激活显示不同的图标"
status="coming-soon"
title="激活图标示例"
/>
</template>

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Menu321' });
</script>
<template>

View File

@ -5,8 +5,6 @@ import { Fallback } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
defineOptions({ name: 'BreadcrumbLateralDetail' });
const router = useRouter();
</script>

View File

@ -5,12 +5,10 @@ import { Fallback } from '@vben/common-ui';
import { Button } from 'ant-design-vue';
defineOptions({ name: 'BreadcrumbLateral' });
const router = useRouter();
function details() {
router.push({ name: 'BreadcrumbLateralDetail' });
router.push({ name: 'BreadcrumbLateralDetailDemo' });
}
</script>

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'BreadcrumbLevelDetail' });
</script>
<template>

View File

@ -1,7 +1,5 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'HideMenuChildren' });
</script>
<template>

View File

@ -0,0 +1,61 @@
<script lang="ts" setup>
import {
MdiGithub,
MdiGoogle,
MdiKeyboardEsc,
MdiQqchat,
MdiWechat,
SvgAvatar1Icon,
SvgAvatar2Icon,
SvgAvatar3Icon,
SvgAvatar4Icon,
SvgBellIcon,
SvgCakeIcon,
SvgCardIcon,
SvgDownloadIcon,
} from '@vben/icons';
</script>
<template>
<div class="p-5">
<div class="card-box p-5">
<h1 class="text-xl font-semibold">图标</h1>
<div class="text-foreground/80 mt-2">
图标可在
<a
class="text-primary"
href="https://icon-sets.iconify.design/"
target="_blank"
>
Iconify
</a>
中查找支持多种图标库 Material Design, Font Awesome, Jam Icons
</div>
</div>
<div class="card-box mt-5 p-5">
<div class="mb-3 text-lg font-semibold">Iconify</div>
<div class="flex items-center gap-5">
<MdiGithub class="size-8" />
<MdiGoogle class="size-8 text-red-500" />
<MdiQqchat class="size-8 text-green-500" />
<MdiWechat class="size-8" />
<MdiKeyboardEsc class="size-8" />
</div>
</div>
<div class="card-box mt-5 p-5">
<div class="mb-3 text-lg font-semibold">Svg Icons</div>
<div class="flex items-center gap-5">
<SvgAvatar1Icon class="size-8" />
<SvgAvatar2Icon class="size-8 text-red-500" />
<SvgAvatar3Icon class="size-8 text-green-500" />
<SvgAvatar4Icon class="size-8" />
<SvgCakeIcon class="size-8" />
<SvgBellIcon class="size-8" />
<SvgCardIcon class="size-8" />
<SvgDownloadIcon class="size-8" />
</div>
</div>
</div>
</template>

View File

@ -5,9 +5,7 @@ import { preferences, updatePreferences } from '@vben/preferences';
import { Button } from 'ant-design-vue';
import { getMockStatus } from '#/apis';
defineOptions({ name: 'LoginExpired' });
import { getMockStatus } from '#/api';
async function handleClick(type: LoginExpiredModeType) {
const loginExpiredMode = preferences.app.loginExpiredMode;

View File

@ -6,8 +6,6 @@ import { useTabs } from '@vben/hooks';
import { Input as AInput, Button } from 'ant-design-vue';
defineOptions({ name: 'FeatureTabsDemo' });
const router = useRouter();
const newTabTitle = ref('');

View File

@ -4,8 +4,6 @@ import { useRoute } from 'vue-router';
import { useTabs } from '@vben/hooks';
defineOptions({ name: 'FeatureTabDetailDemo' });
const route = useRoute();
const { setTabTitle } = useTabs();

View File

@ -0,0 +1,66 @@
<script lang="ts" setup>
import { useWatermark } from '@vben/hooks';
import { Button } from 'ant-design-vue';
const { destroyWatermark, updateWatermark } = useWatermark();
async function createWaterMark() {
await updateWatermark({
advancedStyle: {
colorStops: [
{
color: 'red',
offset: 0,
},
{
color: 'blue',
offset: 1,
},
],
type: 'linear',
},
content: 'hello my watermark',
globalAlpha: 0.5,
gridLayoutOptions: {
cols: 2,
gap: [20, 20],
matrix: [
[1, 0],
[0, 1],
],
rows: 2,
},
height: 200,
layout: 'grid',
rotate: 22,
width: 200,
});
}
</script>
<template>
<div class="p-5">
<div class="card-box p-5">
<h1 class="text-xl font-semibold">水印</h1>
<div class="text-foreground/80 mt-2">
水印使用了
<a
class="text-primary"
href="https://zhensherlock.github.io/watermark-js-plus/"
target="_blank"
>
watermark-js-plus
</a>
开源插件详细配置可见插件配置
</div>
</div>
<div class="card-box mt-5 p-5">
<div class="mb-3 flex gap-3 text-lg font-semibold">
<Button type="primary" @click="createWaterMark()"></Button>
<Button danger @click="destroyWatermark"></Button>
</div>
</div>
</div>
</template>

View File

@ -1,9 +1,10 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Menu1' });
</script>
<template>
<Fallback status="coming-soon" />
<div>
<Fallback status="coming-soon" />
<input />
</div>
</template>

View File

@ -1,9 +1,10 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Menu21' });
</script>
<template>
<Fallback status="coming-soon" />
<div>
<Fallback status="coming-soon" />
<input />
</div>
</template>

View File

@ -1,9 +1,10 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Menu31' });
</script>
<template>
<Fallback status="coming-soon" />
<div>
<Fallback status="coming-soon" />
<input />
</div>
</template>

View File

@ -1,9 +1,10 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Menu321' });
</script>
<template>
<Fallback status="coming-soon" />
<div>
<Fallback status="coming-soon" />
<input />
</div>
</template>

View File

@ -3,6 +3,7 @@
"extends": "@vben/tsconfig/node.json",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"noEmit": false
},
"include": ["vite.config.mts"]

View File

@ -6,7 +6,7 @@ import {
} from '@vben/vite-config';
export default defineConfig(async () => {
const { appTitle, port, ...envConfig } = await loadAndConvertEnv();
const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv();
return {
application: {
...envConfig,
@ -16,13 +16,14 @@ export default defineConfig(async () => {
pwaOptions: getDefaultPwaOptions(appTitle),
},
vite: {
base,
server: {
port,
proxy: {
'/api': {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
// 代理目标地址 - backend-mock 项目
// mock代理目标地址
target: 'http://localhost:5320/api',
ws: true,
},

View File

@ -28,25 +28,25 @@
},
"dependencies": {
"eslint-config-turbo": "^2.0.9",
"eslint-plugin-command": "^0.2.3"
"eslint-plugin-command": "^0.2.3",
"eslint-plugin-import-x": "^3.1.0"
},
"devDependencies": {
"@eslint/js": "^9.7.0",
"@eslint/js": "^9.8.0",
"@types/eslint": "^9.6.0",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"eslint": "^8.57.0",
"eslint": "^9.8.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-i": "^2.29.1",
"eslint-plugin-jsdoc": "^48.8.3",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-n": "^17.9.0",
"eslint-plugin-n": "^17.10.1",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-perfectionist": "^3.0.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-regexp": "^2.6.0",
"eslint-plugin-unicorn": "^54.0.0",
"eslint-plugin-unicorn": "^55.0.0",
"eslint-plugin-unused-imports": "^4.0.1",
"eslint-plugin-vitest": "^0.5.4",
"eslint-plugin-vue": "^9.27.0",

View File

@ -1,14 +1,12 @@
import type { Linter } from 'eslint';
export async function importPluginConfig(): Promise<Linter.FlatConfig[]> {
const [pluginImport] = await Promise.all([
// @ts-expect-error - no types
import('eslint-plugin-i'),
] as const);
import * as pluginImport from 'eslint-plugin-import-x';
export async function importPluginConfig(): Promise<Linter.Config[]> {
return [
{
plugins: {
// @ts-expect-error - This is a dynamic import
import: pluginImport,
},
rules: {

View File

@ -33,7 +33,7 @@
"stylelint-scss": "^6.4.1"
},
"devDependencies": {
"postcss": "^8.4.39",
"postcss": "^8.4.40",
"postcss-html": "^1.7.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.3.3",

View File

@ -37,6 +37,6 @@
"pkg-types": "^1.1.3",
"prettier": "^3.3.3",
"rimraf": "^6.0.1",
"zx": "^7.2.3"
"zx": "^8.1.4"
}
}

View File

@ -46,17 +46,17 @@
"tailwindcss": "^3.4.3"
},
"dependencies": {
"@iconify/json": "^2.2.230",
"@iconify/json": "^2.2.231",
"@iconify/tailwind": "^1.1.1",
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
"@tailwindcss/typography": "^0.5.13",
"autoprefixer": "^10.4.19",
"cssnano": "^7.0.4",
"postcss": "^8.4.39",
"postcss": "^8.4.40",
"postcss-antd-fixes": "^0.2.0",
"postcss-import": "^16.1.0",
"postcss-preset-env": "^9.6.0",
"tailwindcss": "^3.4.6",
"tailwindcss": "^3.4.7",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {

View File

@ -20,6 +20,6 @@
],
"dependencies": {
"@vben/types": "workspace:*",
"vite": "^5.3.4"
"vite": "^5.3.5"
}
}

View File

@ -34,20 +34,20 @@
"nitropack": "^2.9.7",
"resolve.exports": "^2.0.2",
"vite-plugin-lib-inject-css": "^2.1.1",
"vite-plugin-pwa": "^0.20.0",
"vite-plugin-vue-devtools": "^7.3.6"
"vite-plugin-pwa": "^0.20.1",
"vite-plugin-vue-devtools": "^7.3.7"
},
"devDependencies": {
"@types/html-minifier-terser": "^7.0.2",
"@vben/node-utils": "workspace:*",
"@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue": "^5.1.1",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"dayjs": "^1.11.12",
"dotenv": "^16.4.5",
"rollup": "^4.19.0",
"rollup": "^4.19.1",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.8",
"vite": "^5.3.4",
"vite": "^5.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-dts": "4.0.0-beta.1",
"vite-plugin-html": "^3.2.2"

View File

@ -94,7 +94,7 @@ function createCssOptions(injectGlobalScss = true) {
const relativePath = relative(root, filepath);
// apps下的包注入全局样式
if (relativePath.startsWith('apps/')) {
return `@import "@vben/styles/global";\n${content}`;
return `@import (reference) "@vben/styles/global";\n${content}`;
}
return content;
},

View File

@ -7,16 +7,16 @@ const isDevelopment = process.env.NODE_ENV === 'development';
const getDefaultPwaOptions = (name: string): Partial<PwaPluginOptions> => ({
manifest: {
description:
'Vue Vben Admin is a modern admin dashboard template based on Vue 3. ',
'Vben Admin is a modern admin dashboard template based on Vue 3. ',
icons: [
{
sizes: '192x192',
src: 'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/pwa-icon-192.png',
src: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/pwa-icon-192.png',
type: 'image/png',
},
{
sizes: '512x512',
src: 'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/pwa-icon-512.png',
src: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/pwa-icon-512.png',
type: 'image/png',
},
],

View File

@ -1,7 +1,7 @@
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { fs } from '@vben/node-utils';
import { fs, readPackageJSON } from '@vben/node-utils';
import { type PluginOption } from 'vite';
@ -15,8 +15,9 @@ async function viteInjectAppLoadingPlugin(
loadingTemplate = 'loading.html',
): Promise<PluginOption | undefined> {
const loadingHtml = await getLoadingRawByHtmlTemplate(loadingTemplate);
const { version } = await readPackageJSON(process.cwd());
const envRaw = isBuild ? 'prod' : 'dev';
const cacheName = `'${env.VITE_APP_NAMESPACE}-${envRaw}-preferences-theme'`;
const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`;
// 获取缓存的主题
// 保证黑暗主题下刷新页面时loading也是黑暗主题

View File

@ -74,6 +74,7 @@ async function viteMetadataPlugin(
license,
version,
}),
'import.meta.env.VITE_APP_VERSION': JSON.stringify(version),
},
};
},

View File

@ -30,7 +30,7 @@ async function viteLicensePlugin(
handler: (_options: NormalizedOutputOptions, bundle: OutputBundle) => {
const date = dateUtil().format('YYYY-MM-DD ');
const copyrightText = `/*!
* Vue Vben Admin
* Vben Admin
* Version: ${version}
* Author: vben
* Copyright (C) 2024 Vben

View File

@ -56,24 +56,40 @@ async function loadAndConvertEnv(
match = 'VITE_',
confFiles = getConfFiles(),
): Promise<
{ appTitle: string; port: number } & Partial<ApplicationPluginOptions>
{
appTitle: string;
base: string;
port: number;
} & Partial<ApplicationPluginOptions>
> {
const envConfig = await loadEnv(match, confFiles);
const visualizer = envConfig.visualizer || '';
const pwa = envConfig.pwa || '';
const compress = envConfig.VITE_COMPRESS || '';
const {
VITE_BASE,
VITE_COMPRESS,
VITE_DEVTOOLS,
VITE_GLOB_APP_TITLE,
VITE_INJECT_APP_LOADING,
VITE_NITRO_MOCK,
VITE_PORT,
VITE_PWA,
VITE_VISUALIZER,
} = envConfig;
const compress = VITE_COMPRESS || '';
const compressTypes = compress
.split(',')
.filter((item) => item === 'brotli' || item === 'gzip');
return {
appTitle: envConfig?.VITE_GLOB_APP_TITLE ?? 'Vben Admin',
appTitle: VITE_GLOB_APP_TITLE ?? 'Vben Admin',
base: VITE_BASE || '/',
compress: !!compress,
compressTypes: compressTypes as ('brotli' | 'gzip')[],
nitroMock: !!envConfig.VITE_NITRO_MOCK,
port: Number(envConfig.VITE_PORT) || 5173,
pwa: !!pwa,
visualizer: !!visualizer,
devtools: VITE_DEVTOOLS === 'true',
injectAppLoading: VITE_INJECT_APP_LOADING === 'true',
nitroMock: VITE_NITRO_MOCK === 'true',
port: Number(VITE_PORT) || 5173,
pwa: VITE_PWA === 'true',
visualizer: VITE_VISUALIZER === 'true',
};
}

View File

@ -7,7 +7,8 @@
"turbo",
"vben",
"vue vben admin",
"vben admin pro",
"vue vben admin pro",
"vben admin",
"vben pro",
"vue",
"vue admin",
@ -26,6 +27,8 @@
"scripts": {
"bootstrap": "pnpm install",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
"preview": "turbo preview",
"build:analyze": "turbo build:analyze",
"build:docker": "./build-local-docker-image.sh",
"changeset": "pnpm exec changeset",
"check": "pnpm run check:dep && pnpm run check:circular && pnpm run check:type && pnpm run check:cspell",
@ -35,9 +38,8 @@
"check:type": "turbo run typecheck",
"clean": "vsh clean",
"commit": "czg",
"dev": "cross-env turbo run dev",
"dev:ui": "cross-env TURBO_UI=1 turbo run dev",
"docs:dev": "pnpm -F @vben/website run docs:dev",
"dev": "cross-env TURBO_UI=1 turbo run dev",
"dev:docs": "pnpm -F @vben/website run docs:dev",
"format": "vsh lint --format",
"lint": "vsh lint",
"postinstall": "turbo run stub",
@ -45,7 +47,7 @@
"prepare": "is-ci || husky",
"publint": "vsh publint",
"reinstall": "pnpm clean --del-lock && pnpm bootstrap",
"test": "vitest",
"test:unit": "vitest",
"update:deps": " pnpm update --latest --recursive",
"version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile"
},
@ -54,7 +56,7 @@
"@changesets/cli": "^2.27.7",
"@ls-lint/ls-lint": "^2.2.3",
"@types/jsdom": "^21.1.7",
"@types/node": "^20.14.11",
"@types/node": "^20.14.12",
"@vben/commitlint-config": "workspace:*",
"@vben/eslint-config": "workspace:*",
"@vben/lint-staged-config": "workspace:*",
@ -67,16 +69,16 @@
"@vue/test-utils": "^2.4.6",
"cross-env": "^7.0.3",
"cspell": "^8.12.1",
"husky": "^9.1.1",
"husky": "^9.1.3",
"is-ci": "^3.0.1",
"jsdom": "^24.1.1",
"rimraf": "^6.0.1",
"turbo": "^2.0.9",
"typescript": "^5.5.4",
"unbuild": "^2.0.0",
"vite": "^5.3.4",
"vite": "^5.3.5",
"vitest": "^2.0.4",
"vue-tsc": "^2.0.28"
"vue-tsc": "^2.0.29"
},
"engines": {
"node": ">=20",
@ -87,9 +89,7 @@
"overrides": {
"@ctrl/tinycolor": "^4.1.0",
"clsx": "^2.1.1",
"eslint": "^8.57.0",
"vue": "^3.4.33",
"zx": "^7.2.3"
"vue": "^3.4.34"
},
"neverBuiltDependencies": [
"canvas",
@ -98,7 +98,6 @@
],
"updateConfig": {
"ignoreDependencies": [
"eslint",
"zx"
]
}

View File

@ -40,7 +40,7 @@
"@vueuse/core": "^10.11.0",
"radix-vue": "^1.9.2",
"sortablejs": "^1.15.2",
"vue": "^3.4.33"
"vue": "^3.4.34"
},
"devDependencies": {
"@types/sortablejs": "^1.15.8"

View File

@ -31,6 +31,6 @@
"@vben-core/toolkit": "workspace:*",
"@vben-core/typings": "workspace:*",
"@vueuse/core": "^10.11.0",
"vue": "^3.4.33"
"vue": "^3.4.34"
}
}

View File

@ -9,14 +9,15 @@ const defaultPreferences: Preferences = {
compact: false,
contentCompact: 'wide',
defaultAvatar:
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/avatar-v1.webp',
'https://unpkg.com/@vbenjs/static-source@0.1.5/source/avatar-v1.webp',
dynamicTitle: true,
enablePreferences: true,
isMobile: false,
layout: 'sidebar-nav',
locale: 'zh-CN',
loginExpiredMode: 'page',
loginExpiredMode: 'modal',
name: 'Vben Admin',
watermark: false,
},
breadcrumb: {
enable: true,
@ -44,8 +45,7 @@ const defaultPreferences: Preferences = {
},
logo: {
enable: true,
source:
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/logo-v1.webp',
source: 'https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp',
},
navigation: {
accordion: true,
@ -75,6 +75,9 @@ const defaultPreferences: Preferences = {
keepAlive: true,
persist: true,
showIcon: true,
showMaximize: true,
showMore: true,
showRefresh: true,
styleType: 'chrome',
},
theme: {
@ -94,7 +97,6 @@ const defaultPreferences: Preferences = {
progress: true,
},
widget: {
aiAssistant: true,
fullscreen: true,
globalSearch: true,
languageToggle: true,

View File

@ -1,12 +1,4 @@
import type {
BuiltinThemeType,
SupportedLanguagesType,
} from '@vben-core/typings';
interface Language {
key: SupportedLanguagesType;
text: string;
}
import type { BuiltinThemeType } from '@vben-core/typings';
interface BuiltinThemePreset {
color: string;
@ -15,25 +7,11 @@ interface BuiltinThemePreset {
type: BuiltinThemeType;
}
/**
* Supported languages
*/
const SUPPORT_LANGUAGES: Language[] = [
{
key: 'zh-CN',
text: '简体中文',
},
{
key: 'en-US',
text: 'English',
},
];
const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
{
color: 'hsl(231 98% 65%)',
type: 'default',
},
// {
// color: 'hsl(231 98% 65%)',
// type: 'default',
// },
{
color: 'hsl(245 82% 67%)',
type: 'violet',
@ -102,6 +80,6 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
export const COLOR_PRESETS = [...BUILT_IN_THEME_PRESETS].slice(0, 7);
export { BUILT_IN_THEME_PRESETS, SUPPORT_LANGUAGES };
export { BUILT_IN_THEME_PRESETS };
export type { BuiltinThemePreset };

View File

@ -24,17 +24,6 @@ describe('preferences', () => {
preferenceManager = new PreferenceManager();
});
it('initPreferences should initialize preferences with overrides and namespace', async () => {
const overrides = { theme: { colorPrimary: 'hsl(231 98% 65%)' } };
const namespace = 'testNamespace';
await preferenceManager.initPreferences({ namespace, overrides });
expect(preferenceManager.getPreferences().theme.colorPrimary).toBe(
overrides.theme.colorPrimary,
);
});
it('loads default preferences if no saved preferences found', () => {
const preferences = preferenceManager.getPreferences();
expect(preferences).toEqual(defaultPreferences);

View File

@ -41,7 +41,7 @@ class PreferenceManager {
this.savePreferences = useDebounceFn(
(preference: Preferences) => this._savePreferences(preference),
100,
150,
);
}

View File

@ -10,11 +10,12 @@ import type {
LoginExpiredModeType,
NavigationStyleType,
PageTransitionType,
SupportedLanguagesType,
TabsStyleType,
ThemeModeType,
} from '@vben-core/typings';
type SupportedLanguagesType = 'en-US' | 'zh-CN';
interface AppPreferences {
/** 权限模式 */
accessMode: AccessModeType;
@ -44,6 +45,10 @@ interface AppPreferences {
loginExpiredMode: LoginExpiredModeType;
/** 应用名 */
name: string;
/**
* @zh_CN
*/
watermark: boolean;
}
interface BreadcrumbPreferences {
@ -149,6 +154,12 @@ interface TabbarPreferences {
persist: boolean;
/** 是否开启多标签页图标 */
showIcon: boolean;
/** 显示最大化按钮 */
showMaximize: boolean;
/** 显示更多按钮 */
showMore: boolean;
/** 显示刷新按钮 */
showRefresh: boolean;
/** 标签页风格 */
styleType: TabsStyleType;
}
@ -184,8 +195,6 @@ interface TransitionPreferences {
}
interface WidgetPreferences {
/** 是否开启vben助手部件 */
aiAssistant: boolean;
/** 是否启用全屏部件 */
fullscreen: boolean;
/** 是否启用全局搜索部件 */
@ -249,6 +258,7 @@ export type {
PreferencesKeys,
ShortcutKeyPreferences,
SidebarPreferences,
SupportedLanguagesType,
TabbarPreferences,
ThemePreferences,
TransitionPreferences,

View File

@ -5,7 +5,7 @@ import {
generatorColorVariables,
} from '@vben-core/toolkit';
import { BUILT_IN_THEME_PRESETS } from './constants';
import { BUILT_IN_THEME_PRESETS, type BuiltinThemePreset } from './constants';
/**
* CSS CSS
@ -37,9 +37,13 @@ function updateCSSVariables(preferences: Preferences) {
}
// 获取当前的内置主题
const currentBuiltType = BUILT_IN_THEME_PRESETS.find(
(item) => item.type === builtinType,
);
const currentBuiltType = [
{
color: preferences.theme.colorPrimary,
type: 'default',
} as BuiltinThemePreset,
...BUILT_IN_THEME_PRESETS,
].find((item) => item.type === builtinType);
let builtinTypeColorPrimary: string | undefined = '';

View File

@ -12,7 +12,7 @@ const VBEN_DOC_URL = 'https://doc.vben.pro';
* @zh_CN Vben Logo
*/
const VBEN_LOGO_URL =
'https://cdn.jsdelivr.net/npm/@vbenjs/static-source@0.1.3/source/logo-v1.webp';
'https://unpkg.com/@vbenjs/static-source@0.1.5/source/logo-v1.webp';
/**
* @zh_CN Vben Admin

View File

@ -85,17 +85,21 @@
/* 只有非mac下才进行调整mac下使用默认滚动条 */
html:not([data-platform='macOs']) {
*::-webkit-scrollbar {
::-webkit-scrollbar {
@apply h-[1px] w-[10px];
}
*::-webkit-scrollbar-thumb {
::-webkit-scrollbar-thumb {
@apply bg-border rounded-sm border-none;
}
*::-webkit-scrollbar-track {
::-webkit-scrollbar-track {
@apply rounded-sm border-none bg-transparent shadow-none;
}
::-webkit-scrollbar-button {
@apply hidden;
}
}
}

View File

@ -35,7 +35,7 @@
},
"dependencies": {
"@iconify/vue": "^4.1.2",
"lucide-vue-next": "^0.414.0",
"vue": "^3.4.33"
"lucide-vue-next": "^0.416.0",
"vue": "^3.4.34"
}
}

View File

@ -36,7 +36,7 @@
},
"dependencies": {
"@ctrl/tinycolor": "^4.1.0",
"@vue/shared": "^3.4.33",
"@vue/shared": "^3.4.34",
"clsx": "^2.1.1",
"defu": "^6.1.4",
"lodash.clonedeep": "^4.5.0",

View File

@ -38,7 +38,7 @@
}
},
"dependencies": {
"vue": "^3.4.33",
"vue": "^3.4.34",
"vue-router": "^4.4.0"
}
}

Some files were not shown because too many files have changed in this diff Show More