This commit is contained in:
dap
2024-08-12 07:38:55 +08:00
226 changed files with 3687 additions and 1111 deletions

View File

@@ -1,5 +1,42 @@
# Contributing Guide # Vben Admin Contributing Guide
1. Make sure you put things in the right category! Hi! We're really excited that you are interested in contributing to Vben Admin. Before submitting your contribution, please make sure to take a moment and read through the following guidelines:
2. Always add your items to the end of a list. To be fair, the order is first-come-first-serve.
3. If you think something belongs in the wrong category, or think there needs to be a new category, feel free to edit things too. - [Pull Request Guidelines](#pull-request-guidelines)
## Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of the level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
## Pull Request Guidelines
- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.
- If adding a new feature:
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- If fixing bug:
- Provide a detailed description of the bug in the PR. Live demo preferred.
- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging.
## Development Setup
You will need [pnpm](https://pnpm.io/)
After cloning the repo, run:
```bash
# install the dependencies of the project
$ pnpm install
# start the project
$ pnpm run dev
```

View File

@@ -17,15 +17,17 @@ categories:
- title: "🐞 Bug Fixes" - title: "🐞 Bug Fixes"
labels: labels:
- "bug" - "bug"
- title: "📈 Performance"
labels:
- "perf"
- title: 📝 Documentation - title: 📝 Documentation
labels: labels:
- "documentation" - "documentation"
- title: 👻 Maintenance - title: 👻 Maintenance
labels: labels:
- "perf"
- "chore" - "chore"
- "dependencies" - "dependencies"
collapse-after: 5 # collapse-after: 12
- title: 🚦 Tests - title: 🚦 Tests
labels: labels:
- "tests" - "tests"
@@ -40,11 +42,10 @@ version-resolver:
minor: minor:
labels: labels:
- "minor" - "minor"
# - "feature" - "feature"
patch: patch:
labels: labels:
- "patch" - "patch"
- "feature"
- "bug" - "bug"
- "maintenance" - "maintenance"
- "docs" - "docs"

View File

@@ -6,8 +6,47 @@ on:
- main - main
jobs: jobs:
deploy-push-ftp: deploy-push-playground-ftp:
name: Deploy Push Ftp name: Deploy Push Playground Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Sed Config Base
shell: bash
run: |
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./playground/.env.production
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./playground/.env.production
cat ./playground/.env.production
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Build
run: pnpm run build
- name: Sync Playground files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_PLAYGROUND_FTP_ACCOUNT }}
password: ${{ secrets.WEB_PLAYGROUND_FTP_PWSSWORD }}
local-dir: ./playground/dist/
- name: Sync Docs files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}
password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
local-dir: ./docs/.vitepress/dist/
deploy-push-antd-ftp:
name: Deploy Push Antd Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]') if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -22,9 +61,65 @@ jobs:
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-antd/.env.production sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-antd/.env.production
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-antd/.env.production sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-antd/.env.production
cat ./apps/web-antd/.env.production cat ./apps/web-antd/.env.production
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Build
run: pnpm run build
- name: Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }}
password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}
local-dir: ./apps/web-antd/dist/
deploy-push-ele-ftp:
name: Deploy Push Element Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Sed Config Base
shell: bash
run: |
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-ele/.env.production sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-ele/.env.production
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-ele/.env.production sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-ele/.env.production
cat ./apps/web-ele/.env.production cat ./apps/web-ele/.env.production
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Build
run: pnpm run build
- name: Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }}
password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
local-dir: ./apps/web-ele/dist/
deploy-push-naive-ftp:
name: Deploy Push Naive Ftp
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Sed Config Base
shell: bash
run: |
sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-naive/.env.production sed -i "s#VITE_COMPRESS\s*=.*#VITE_COMPRESS = gzip#g" ./apps/web-naive/.env.production
sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-naive/.env.production sed -i "s#VITE_PWA\s*=.*#VITE_PWA = true#g" ./apps/web-naive/.env.production
cat ./apps/web-naive/.env.production cat ./apps/web-naive/.env.production
@@ -35,34 +130,10 @@ jobs:
- name: Build - name: Build
run: pnpm run build run: pnpm run build
- name: Sync Web Antd files - name: Sync files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }}
password: ${{ secrets.WEB_ANTD_FTP_PASSWORD }}
local-dir: ./apps/web-antd/dist/
- name: Sync Web Naive files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5 uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with: with:
server: ${{ secrets.PRO_FTP_HOST }} server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }} username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }}
password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }} password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }}
local-dir: ./apps/web-naive/dist/ local-dir: ./apps/web-naive/dist/
- name: Sync Web Ele files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }}
password: ${{ secrets.WEB_ELE_FTP_PASSWORD }}
local-dir: ./apps/web-ele/dist/
- name: Sync Docs files
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.PRO_FTP_HOST }}
username: ${{ secrets.WEBSITE_FTP_ACCOUNT }}
password: ${{ secrets.WEBSITE_FTP_PASSWORD }}
local-dir: ./docs/.vitepress/dist/

View File

@@ -10,10 +10,6 @@
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
// 支持 dotenv 文件语法 // 支持 dotenv 文件语法
"mikestead.dotenv", "mikestead.dotenv",
// 获取每个 CSS 属性的初始值。
"dzhavat.css-initial-value",
// 使 VSCode 中的 TypeScript 错误更漂亮、更易于理解
"yoavbls.pretty-ts-errors",
// 源代码的拼写检查器 // 源代码的拼写检查器
"streetsidesoftware.code-spell-checker", "streetsidesoftware.code-spell-checker",
// Tailwind CSS 的官方 VS Code 插件 // Tailwind CSS 的官方 VS Code 插件

15
.vscode/launch.json vendored
View File

@@ -4,18 +4,27 @@
"configurations": [ "configurations": [
{ {
"type": "chrome", "type": "chrome",
"name": "vben admin antd dev", "name": "vben admin playground dev",
"request": "launch", "request": "launch",
"url": "http://localhost:5555", "url": "http://localhost:5555",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}/playground/src"
},
{
"type": "chrome",
"name": "vben admin antd dev",
"request": "launch",
"url": "http://localhost:5666",
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-antd/src" "webRoot": "${workspaceFolder}/apps/web-antd/src"
}, },
{ {
"type": "chrome", "type": "chrome",
"name": "vben admin ele dev", "name": "vben admin ele dev",
"request": "launch", "request": "launch",
"url": "http://localhost:5666", "url": "http://localhost:5777",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-ele/src" "webRoot": "${workspaceFolder}/apps/web-ele/src"
@@ -24,7 +33,7 @@
"type": "chrome", "type": "chrome",
"name": "vben admin naive dev", "name": "vben admin naive dev",
"request": "launch", "request": "launch",
"url": "http://localhost:5777", "url": "http://localhost:5888",
"env": { "NODE_ENV": "development" }, "env": { "NODE_ENV": "development" },
"sourceMaps": true, "sourceMaps": true,
"webRoot": "${workspaceFolder}/apps/web-naive/src" "webRoot": "${workspaceFolder}/apps/web-naive/src"

View File

@@ -167,6 +167,7 @@
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"packages/locales/src/langs", "packages/locales/src/langs",
"playground/src/langs",
"apps/*/src/locales/langs" "apps/*/src/locales/langs"
], ],
"i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"], "i18n-ally.enabledParsers": ["json", "ts", "js", "yaml"],
@@ -191,5 +192,6 @@
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"commentTranslate.multiLineMerge": true, "commentTranslate.multiLineMerge": true,
"vue.server.hybridMode": true, "vue.server.hybridMode": true,
"typescript.tsdk": "node_modules/typescript/lib" "typescript.tsdk": "node_modules/typescript/lib",
"vitest.disableWorkspaceWarning": true
} }

View File

@@ -21,7 +21,7 @@ RUN echo "Builder Success 🎉"
FROM nginx:stable-alpine as production FROM nginx:stable-alpine as production
RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf
COPY --from=builder /app/apps/web-antd/dist /usr/share/nginx/html COPY --from=builder /app/playground/dist /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /etc/nginx/nginx.conf

View File

@@ -134,7 +134,8 @@ pnpm build
## 貢献者 ## 貢献者
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> <a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" /> <img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a> </a>
## Discord ## Discord

View File

@@ -133,7 +133,8 @@ If you think this project is helpful to you, you can help the author buy a cup o
## Contributor ## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> <a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" /> <img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a> </a>
## Discord ## Discord

View File

@@ -126,6 +126,13 @@ pnpm build
<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a> <a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
## Contributor
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false" />
</a>
## Discord ## Discord
- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) - [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions)

View File

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

View File

@@ -1,6 +1,6 @@
# 端口号 # 端口号
VITE_PORT=5555 VITE_PORT=5666
# base路径
VITE_BASE=/ VITE_BASE=/
# 是否开启 Nitro Mock服务true 为开启false 为关闭 # 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=true VITE_NITRO_MOCK=true

View File

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

View File

@@ -58,7 +58,11 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
locale = await import('dayjs/locale/en'); locale = await import('dayjs/locale/en');
} }
} }
dayjs.locale(locale); if (locale) {
dayjs.locale(locale);
} else {
console.error(`Failed to load dayjs locale for ${lang}`);
}
} }
/** /**

View File

@@ -2,66 +2,7 @@
"page": { "page": {
"demos": { "demos": {
"title": "Demos", "title": "Demos",
"access": { "antd": "Ant Design Vue"
"frontendPermissions": "Frontend Permissions",
"backendPermissions": "Backend Permissions",
"pageAccess": "Page Access",
"buttonControl": "Button Control",
"menuVisible403": "Menu Visible(403)",
"superVisible": "Visible to Super",
"adminVisible": "Visible to Admin",
"userVisible": "Visible to User"
},
"nested": {
"title": "Nested Menu",
"menu1": "Menu 1",
"menu2": "Menu 2",
"menu2_1": "Menu 2-1",
"menu3": "Menu 3",
"menu3_1": "Menu 3-1",
"menu3_2": "Menu 3-2",
"menu3_2_1": "Menu 3-2-1"
},
"outside": {
"title": "External Pages",
"embedded": "Embedded",
"externalLink": "External Link"
},
"badge": {
"title": "Menu Badge",
"dot": "Dot Badge",
"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"
},
"breadcrumb": {
"navigation": "Breadcrumb Navigation",
"lateral": "Lateral Mode",
"lateralDetail": "Lateral Mode Detail",
"level": "Level Mode",
"levelDetail": "Level Mode Detail"
}
},
"examples": {
"title": "Examples",
"ellipsis": {
"title": "EllipsisText"
}
} }
} }
} }

View File

@@ -2,66 +2,7 @@
"page": { "page": {
"demos": { "demos": {
"title": "演示", "title": "演示",
"access": { "antd": "Ant Design Vue"
"frontendPermissions": "前端权限",
"backendPermissions": "后端权限",
"pageAccess": "页面访问",
"buttonControl": "按钮控制",
"menuVisible403": "菜单可见(403)",
"superVisible": "Super 可见",
"adminVisible": "Admin 可见",
"userVisible": "User 可见"
},
"nested": {
"title": "嵌套菜单",
"menu1": "菜单 1",
"menu2": "菜单 2",
"menu2_1": "菜单 2-1",
"menu3": "菜单 3",
"menu3_1": "菜单 3-1",
"menu3_2": "菜单 3-2",
"menu3_2_1": "菜单 3-2-1"
},
"outside": {
"title": "外部页面",
"embedded": "内嵌",
"externalLink": "外链"
},
"badge": {
"title": "菜单徽标",
"dot": "点徽标",
"text": "文本徽标",
"color": "徽标颜色"
},
"activeIcon": {
"title": "菜单激活图标",
"children": "子级激活图标"
},
"fallback": {
"title": "缺省页"
},
"features": {
"title": "功能",
"hideChildrenInMenu": "隐藏子菜单",
"loginExpired": "登录过期",
"icons": "图标",
"watermark": "水印",
"tabs": "标签页",
"tabDetail": "标签详情页"
},
"breadcrumb": {
"navigation": "面包屑导航",
"lateral": "平级模式",
"level": "层级模式",
"levelDetail": "层级模式详情",
"lateralDetail": "平级模式详情"
}
},
"examples": {
"title": "示例",
"ellipsis": {
"title": "文本省略"
}
} }
} }
} }

View File

@@ -115,10 +115,10 @@ function setupAccessGuard(router: Router) {
// 保存菜单信息和路由信息 // 保存菜单信息和路由信息
accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes); accessStore.setAccessRoutes(accessibleRoutes);
const redirectPath = (from.query.redirect ?? to.path) as string; const redirectPath = (from.query.redirect ?? to.fullPath) as string;
return { return {
path: decodeURIComponent(redirectPath), ...router.resolve(decodeURIComponent(redirectPath)),
replace: true, replace: true,
}; };
}); });

View File

@@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout, IFrameView } from '#/layouts'; import { BasicLayout } from '#/layouts';
import { $t } from '#/locales'; import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
@@ -15,477 +15,13 @@ const routes: RouteRecordRaw[] = [
name: 'Demos', name: 'Demos',
path: '/demos', path: '/demos',
children: [ children: [
// 权限控制
{ {
meta: { meta: {
icon: 'mdi:shield-key-outline', title: $t('page.demos.antd'),
title: $t('page.demos.access.frontendPermissions'),
}, },
name: 'AccessDemos', name: 'AntDesignDemos',
path: '/demos/access', path: '/demos/ant-design',
children: [ component: () => import('#/views/demos/antd/index.vue'),
{
name: 'AccessPageControlDemo',
path: '/demos/access/page-control',
component: () => import('#/views/demos/access/index.vue'),
meta: {
icon: 'mdi:page-previous-outline',
title: $t('page.demos.access.pageAccess'),
},
},
{
name: 'AccessButtonControlDemo',
path: '/demos/access/button-control',
component: () => import('#/views/demos/access/button-control.vue'),
meta: {
icon: 'mdi:button-cursor',
title: $t('page.demos.access.buttonControl'),
},
},
{
name: 'AccessMenuVisible403Demo',
path: '/demos/access/menu-visible-403',
component: () =>
import('#/views/demos/access/menu-visible-403.vue'),
meta: {
authority: ['no-body'],
icon: 'mdi:button-cursor',
menuVisibleWithForbidden: true,
title: $t('page.demos.access.menuVisible403'),
},
},
{
name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible',
component: () => import('#/views/demos/access/super-visible.vue'),
meta: {
authority: ['super'],
icon: 'mdi:button-cursor',
title: $t('page.demos.access.superVisible'),
},
},
{
name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible',
component: () => import('#/views/demos/access/admin-visible.vue'),
meta: {
authority: ['admin'],
icon: 'mdi:button-cursor',
title: $t('page.demos.access.adminVisible'),
},
},
{
name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible',
component: () => import('#/views/demos/access/user-visible.vue'),
meta: {
authority: ['user'],
icon: 'mdi:button-cursor',
title: $t('page.demos.access.userVisible'),
},
},
],
},
// 功能
{
meta: {
icon: 'mdi:feature-highlight',
title: $t('page.demos.features.title'),
},
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: '/demos/features/tabs',
component: () => import('#/views/demos/features/tabs/index.vue'),
meta: {
icon: 'lucide:app-window',
title: $t('page.demos.features.tabs'),
},
},
{
name: 'FeatureTabDetailDemo',
path: '/demos/features/tabs/detail/:id',
component: () =>
import('#/views/demos/features/tabs/tab-detail.vue'),
meta: {
activePath: '/demos/features/tabs',
hideInMenu: true,
maxNumOfOpenTab: 3,
title: $t('page.demos.features.tabDetail'),
},
},
{
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: $t('page.demos.features.hideChildrenInMenu'),
},
children: [
{
name: 'HideChildrenInMenuChildrenDemo',
path: '/demos/features/hide-menu-children/children',
component: () =>
import(
'#/views/demos/features/hide-menu-children/children.vue'
),
meta: { title: 'HideChildrenInMenuChildrenDemo' },
},
],
},
],
},
// 面包屑导航
{
name: 'BreadcrumbDemos',
path: '/demos/breadcrumb',
meta: {
icon: 'lucide:navigation',
title: $t('page.demos.breadcrumb.navigation'),
},
children: [
{
name: 'BreadcrumbLateralDemo',
path: '/demos/breadcrumb/lateral',
component: () => import('#/views/demos/breadcrumb/lateral.vue'),
meta: {
icon: 'lucide:navigation',
title: $t('page.demos.breadcrumb.lateral'),
},
},
{
name: 'BreadcrumbLateralDetailDemo',
path: '/demos/breadcrumb/lateral-detail',
component: () =>
import('#/views/demos/breadcrumb/lateral-detail.vue'),
meta: {
activePath: '/demos/breadcrumb/lateral',
hideInMenu: true,
title: $t('page.demos.breadcrumb.lateralDetail'),
},
},
{
name: 'BreadcrumbLevelDemo',
path: '/demos/breadcrumb/level',
meta: {
icon: 'lucide:navigation',
title: $t('page.demos.breadcrumb.level'),
},
children: [
{
name: 'BreadcrumbLevelDetailDemo',
path: '/demos/breadcrumb/level/detail',
component: () =>
import('#/views/demos/breadcrumb/level-detail.vue'),
meta: {
title: $t('page.demos.breadcrumb.levelDetail'),
},
},
],
},
],
},
// 缺省页
{
meta: {
icon: 'mdi:lightbulb-error-outline',
title: $t('page.demos.fallback.title'),
},
name: 'FallbackDemos',
path: '/demos/fallback',
children: [
{
name: 'Fallback403Demo',
path: '/demos/fallback/403',
component: () => import('#/views/_core/fallback/forbidden.vue'),
meta: {
icon: 'mdi:do-not-disturb-alt',
title: '403',
},
},
{
name: 'Fallback404Demo',
path: '/demos/fallback/404',
component: () => import('#/views/_core/fallback/not-found.vue'),
meta: {
icon: 'mdi:table-off',
title: '404',
},
},
{
name: 'Fallback500Demo',
path: '/demos/fallback/500',
component: () =>
import('#/views/_core/fallback/internal-error.vue'),
meta: {
icon: 'mdi:server-network-off',
title: '500',
},
},
{
name: 'FallbackOfflineDemo',
path: '/demos/fallback/offline',
component: () => import('#/views/_core/fallback/offline.vue'),
meta: {
icon: 'mdi:offline',
title: $t('fallback.offline'),
},
},
],
},
// 菜单徽标
{
meta: {
badgeType: 'dot',
badgeVariants: 'destructive',
icon: 'lucide:circle-dot',
title: $t('page.demos.badge.title'),
},
name: 'BadgeDemos',
path: '/demos/badge',
children: [
{
name: 'BadgeDotDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: '/demos/badge/dot',
meta: {
badgeType: 'dot',
icon: 'lucide:square-dot',
title: $t('page.demos.badge.dot'),
},
},
{
name: 'BadgeTextDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: '/demos/badge/text',
meta: {
badge: '10',
icon: 'lucide:square-dot',
title: $t('page.demos.badge.text'),
},
},
{
name: 'BadgeColorDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: '/demos/badge/color',
meta: {
badge: 'Hot',
badgeVariants: 'destructive',
icon: 'lucide:square-dot',
title: $t('page.demos.badge.color'),
},
},
],
},
// 菜单激活图标
{
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: 'OutsideDemos',
path: '/demos/outside',
children: [
{
name: 'IframeDemos',
path: '/demos/outside/iframe',
meta: {
icon: 'mdi:newspaper-variant-outline',
title: $t('page.demos.outside.embedded'),
},
children: [
{
name: 'VueDocumentDemo',
path: '/demos/outside/iframe/vue-document',
component: IFrameView,
meta: {
icon: 'logos:vue',
iframeSrc: 'https://cn.vuejs.org/',
keepAlive: true,
title: 'Vue',
},
},
{
name: 'TailwindcssDemo',
path: '/demos/outside/iframe/tailwindcss',
component: IFrameView,
meta: {
icon: 'devicon:tailwindcss',
iframeSrc: 'https://tailwindcss.com/',
// keepAlive: true,
title: 'Tailwindcss',
},
},
],
},
{
name: 'ExternalLinkDemos',
path: '/demos/outside/external-link',
meta: {
icon: 'mdi:newspaper-variant-multiple-outline',
title: $t('page.demos.outside.externalLink'),
},
children: [
{
name: 'ViteDemo',
path: '/demos/outside/external-link/vite',
component: IFrameView,
meta: {
icon: 'logos:vitejs',
link: 'https://vitejs.dev/',
title: 'Vite',
},
},
{
name: 'VueUseDemo',
path: '/demos/outside/external-link/vue-use',
component: IFrameView,
meta: {
icon: 'logos:vueuse',
link: 'https://vueuse.org',
title: 'VueUse',
},
},
],
},
],
},
// 嵌套菜单
{
meta: {
icon: 'ic:round-menu',
title: $t('page.demos.nested.title'),
},
name: 'NestedDemos',
path: '/demos/nested',
children: [
{
name: 'Menu1Demo',
path: '/demos/nested/menu1',
component: () => import('#/views/demos/nested/menu-1.vue'),
meta: {
icon: 'ic:round-menu',
keepAlive: true,
title: $t('page.demos.nested.menu1'),
},
},
{
name: 'Menu2Demo',
path: '/demos/nested/menu2',
meta: {
icon: 'ic:round-menu',
keepAlive: true,
title: $t('page.demos.nested.menu2'),
},
children: [
{
name: 'Menu21Demo',
path: '/demos/nested/menu2/menu2-1',
component: () => import('#/views/demos/nested/menu-2-1.vue'),
meta: {
icon: 'ic:round-menu',
keepAlive: true,
title: $t('page.demos.nested.menu2_1'),
},
},
],
},
{
name: 'Menu3Demo',
path: '/demos/nested/menu3',
meta: {
icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3'),
},
children: [
{
name: 'Menu31Demo',
path: 'menu3-1',
component: () => import('#/views/demos/nested/menu-3-1.vue'),
meta: {
icon: 'ic:round-menu',
keepAlive: true,
title: $t('page.demos.nested.menu3_1'),
},
},
{
name: 'Menu32Demo',
path: 'menu3-2',
meta: {
icon: 'ic:round-menu',
title: $t('page.demos.nested.menu3_2'),
},
children: [
{
name: 'Menu321Demo',
path: '/demos/nested/menu3/menu3-2/menu3-2-1',
component: () =>
import('#/views/demos/nested/menu-3-2-1.vue'),
meta: {
icon: 'ic:round-menu',
keepAlive: true,
title: $t('page.demos.nested.menu3_2_1'),
},
},
],
},
],
},
],
}, },
], ],
}, },

View File

@@ -38,8 +38,7 @@ const routes: RouteRecordRaw[] = [
component: IFrameView, component: IFrameView,
meta: { meta: {
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
iframeSrc: VBEN_DOC_URL, link: VBEN_DOC_URL,
keepAlive: true,
title: $t('page.vben.document'), title: $t('page.vben.document'),
}, },
}, },

View File

@@ -13,7 +13,7 @@ onMounted(() => {
containLabel: true, containLabel: true,
left: '1%', left: '1%',
right: '1%', right: '1%',
top: '2 %', top: '2 %',
}, },
series: [ series: [
{ {

View File

@@ -13,7 +13,7 @@ onMounted(() => {
containLabel: true, containLabel: true,
left: '1%', left: '1%',
right: '1%', right: '1%',
top: '2 %', top: '2 %',
}, },
series: [ series: [
{ {

View File

@@ -0,0 +1,66 @@
<script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { Button, Card, message, notification, Space } from 'ant-design-vue';
type NotificationType = 'error' | 'info' | 'success' | 'warning';
function info() {
message.info('How many roads must a man walk down');
}
function error() {
message.error({
content: 'Once upon a time you dressed so fine',
duration: 2500,
});
}
function warning() {
message.warning('How many roads must a man walk down');
}
function success() {
message.success('Cause you walked hand in hand With another man in my place');
}
function notify(type: NotificationType) {
notification[type]({
duration: 2500,
message: '说点啥呢',
type,
});
}
</script>
<template>
<Page
description="支持多语言,主题功能集成切换等"
title="Ant Design Vue组件使用演示"
>
<Card title="按钮">
<Space>
<Button>Default</Button>
<Button type="primary"> Primary </Button>
<Button> Info </Button>
<Button danger> Error </Button>
</Space>
</Card>
<Card class="mb-5" title="Message">
<Space>
<Button @click="info"> 信息 </Button>
<Button danger @click="error"> 错误 </Button>
<Button @click="warning"> 警告 </Button>
<Button @click="success"> 成功 </Button>
</Space>
</Card>
<Card class="mb-5" title="Notification">
<Space>
<Button @click="notify('info')"> 信息 </Button>
<Button danger @click="notify('error')"> 错误 </Button>
<Button @click="notify('warning')"> 警告 </Button>
<Button @click="notify('success')"> 成功 </Button>
</Space>
</Card>
</Page>
</template>

View File

@@ -1,2 +0,0 @@
export const longText: string =
'Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈可以作为项目的启动模版以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈可以作为项目的启动模版以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈可以作为项目的启动模版以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术并将其应用在项目中。Vben Admin 是一个基于 Vue3.0、Vite、 TypeScript 的后台解决方案目标是为开发中大型项目提供开箱即用的解决方案。包括二次封装组件、utils、hooks、动态菜单、权限校验、多主题配置、按钮级别权限控制等功能。项目会使用前端较新的技术栈可以作为项目的启动模版以帮助你快速搭建企业级中后台产品原型。也可以作为一个示例用于学习 vue3、vite、ts 等主流技术。该项目会持续跟进最新技术,并将其应用在项目中。';

View File

@@ -1,42 +0,0 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { EllipsisText } from '@vben/common-ui';
import { Collapse, CollapsePanel } from 'ant-design-vue';
import { longText } from './data';
const text = ref(longText);
const activeKey = ref(['1', '2', '3', '4']);
</script>
<template>
<div class="card-box p-5">
<h1 class="mb-5 text-xl font-semibold">文本省略示例</h1>
<div>
<Collapse v-model:activeKey="activeKey">
<CollapsePanel key="1" header="Ellipsis 基本使用">
<EllipsisText :max-width="240">{{ text }}</EllipsisText>
</CollapsePanel>
<CollapsePanel key="2" header="Ellipsis 多行省略">
<EllipsisText :line="2">{{ text }}</EllipsisText>
</CollapsePanel>
<CollapsePanel key="3" header="Ellipsis 点击展开">
<EllipsisText :line="3" expand>{{ text }}</EllipsisText>
</CollapsePanel>
<CollapsePanel key="4" header="Ellipsis 定制 Tooltip 内容">
<EllipsisText :max-width="240">
住在我心里孤独的 孤独的海怪 痛苦之王 开始厌倦 深海的光 停滞的海浪
<template #tooltip>
<div style="text-align: center">
秦皇岛<br />住在我心里孤独的<br />孤独的海怪 痛苦之王<br />开始厌倦
深海的光 停滞的海浪
</div>
</template>
</EllipsisText>
</CollapsePanel>
</Collapse>
</div>
</div>
</template>

View File

@@ -1,5 +1,5 @@
# 端口号 # 端口号
VITE_PORT=5666 VITE_PORT=5777
VITE_BASE=/ VITE_BASE=/

View File

@@ -59,7 +59,7 @@ function createRequestClient(baseURL: string) {
if (status >= 200 && status < 400 && code === 0) { if (status >= 200 && status < 400 && code === 0) {
return data; return data;
} }
throw new Error(msg); throw new Error(`Error ${status}: ${msg}`);
}); });
return client; return client;
} }

View File

@@ -58,7 +58,11 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
locale = await import('dayjs/locale/en'); locale = await import('dayjs/locale/en');
} }
} }
dayjs.locale(locale); if (locale) {
dayjs.locale(locale);
} else {
console.error(`Failed to load dayjs locale for ${lang}`);
}
} }
/** /**

View File

@@ -115,10 +115,10 @@ function setupAccessGuard(router: Router) {
// 保存菜单信息和路由信息 // 保存菜单信息和路由信息
accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes); accessStore.setAccessRoutes(accessibleRoutes);
const redirectPath = (from.query.redirect ?? to.path) as string; const redirectPath = (from.query.redirect ?? to.fullPath) as string;
return { return {
path: decodeURIComponent(redirectPath), ...router.resolve(decodeURIComponent(redirectPath)),
replace: true, replace: true,
}; };
}); });

View File

@@ -17,7 +17,6 @@ const routes: RouteRecordRaw[] = [
children: [ children: [
{ {
meta: { meta: {
icon: 'mdi:shield-key-outline',
title: $t('page.demos.element-plus'), title: $t('page.demos.element-plus'),
}, },
name: 'NaiveDemos', name: 'NaiveDemos',

View File

@@ -1,11 +1,11 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { import {
VBEN_ANT_PREVIEW_URL,
VBEN_DOC_URL, VBEN_DOC_URL,
VBEN_GITHUB_URL, VBEN_GITHUB_URL,
VBEN_LOGO_URL, VBEN_LOGO_URL,
VBEN_NAIVE_PREVIEW_URL, VBEN_NAIVE_PREVIEW_URL,
VBEN_PREVIEW_URL,
} from '@vben/constants'; } from '@vben/constants';
import { BasicLayout, IFrameView } from '#/layouts'; import { BasicLayout, IFrameView } from '#/layouts';
@@ -38,8 +38,7 @@ const routes: RouteRecordRaw[] = [
component: IFrameView, component: IFrameView,
meta: { meta: {
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
iframeSrc: VBEN_DOC_URL, link: VBEN_DOC_URL,
keepAlive: true,
title: $t('page.vben.document'), title: $t('page.vben.document'),
}, },
}, },
@@ -69,7 +68,7 @@ const routes: RouteRecordRaw[] = [
component: IFrameView, component: IFrameView,
meta: { meta: {
badgeType: 'dot', badgeType: 'dot',
link: VBEN_PREVIEW_URL, link: VBEN_ANT_PREVIEW_URL,
title: $t('page.vben.antdv'), title: $t('page.vben.antdv'),
}, },
}, },

View File

@@ -13,7 +13,7 @@ onMounted(() => {
containLabel: true, containLabel: true,
left: '1%', left: '1%',
right: '1%', right: '1%',
top: '2 %', top: '2 %',
}, },
series: [ series: [
{ {

View File

@@ -13,7 +13,7 @@ onMounted(() => {
containLabel: true, containLabel: true,
left: '1%', left: '1%',
right: '1%', right: '1%',
top: '2 %', top: '2 %',
}, },
series: [ series: [
{ {

View File

@@ -1,4 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Page } from '@vben/common-ui';
import { import {
ElButton, ElButton,
ElCard, ElCard,
@@ -39,58 +41,38 @@ function notify(type: NotificationType) {
</script> </script>
<template> <template>
<div class="p-5"> <Page
<div class="card-box p-5"> description="支持多语言,主题功能集成切换等"
<h1 class="text-xl font-semibold">Element Plus组件使用演示</h1> title="Element Plus组件使用演示"
<div class="text-foreground/80 mt-2">支持多语言主题功能集成切换等</div> >
</div> <ElCard class="mb-5">
<template #header> 按钮 </template>
<div class="card-box mt-5 p-5"> <ElSpace>
<div class="mb-3"> <ElButton>Default</ElButton>
<span class="text-lg font-semibold">按钮</span> <ElButton type="primary"> Primary </ElButton>
</div> <ElButton type="info"> Info </ElButton>
<div> <ElButton type="success"> Success </ElButton>
<ElSpace> <ElButton type="warning"> Warning </ElButton>
<ElButton>Default</ElButton> <ElButton type="danger"> Error </ElButton>
<ElButton type="primary"> Primary </ElButton> </ElSpace>
<ElButton type="info"> Info </ElButton> </ElCard>
<ElButton type="success"> Success </ElButton> <ElCard class="mb-5">
<ElButton type="warning"> Warning </ElButton> <template #header> Message </template>
<ElButton type="danger"> Error </ElButton> <ElSpace>
</ElSpace>
</div>
</div>
<div class="card-box mt-5 p-5">
<div class="mb-3">
<span class="text-lg font-semibold">卡片</span>
</div>
<div>
<ElCard title="卡片"> 卡片内容 </ElCard>
</div>
</div>
<div class="card-box mt-5 p-5">
<div class="mb-3">
<span class="text-lg font-semibold">信息 Message </span>
</div>
<div class="flex gap-3">
<ElButton type="info" @click="info"> 信息 </ElButton> <ElButton type="info" @click="info"> 信息 </ElButton>
<ElButton type="danger" @click="error"> 错误 </ElButton> <ElButton type="danger" @click="error"> 错误 </ElButton>
<ElButton type="warning" @click="warning"> 警告 </ElButton> <ElButton type="warning" @click="warning"> 警告 </ElButton>
<ElButton type="success" @click="success"> 成功 </ElButton> <ElButton type="success" @click="success"> 成功 </ElButton>
</div> </ElSpace>
</div> </ElCard>
<ElCard class="mb-5">
<div class="card-box mt-5 p-5"> <template #header> Notification </template>
<div class="mb-3"> <ElSpace>
<span class="text-lg font-semibold">通知 Notification </span>
</div>
<div class="flex gap-3">
<ElButton type="info" @click="notify('info')"> 信息 </ElButton> <ElButton type="info" @click="notify('info')"> 信息 </ElButton>
<ElButton type="danger" @click="notify('error')"> 错误 </ElButton> <ElButton type="danger" @click="notify('error')"> 错误 </ElButton>
<ElButton type="warning" @click="notify('warning')"> 警告 </ElButton> <ElButton type="warning" @click="notify('warning')"> 警告 </ElButton>
<ElButton type="success" @click="notify('success')"> 成功 </ElButton> <ElButton type="success" @click="notify('success')"> 成功 </ElButton>
</div> </ElSpace>
</div> </ElCard>
</div> </Page>
</template> </template>

View File

@@ -1,5 +1,5 @@
# 端口号 # 端口号
VITE_PORT=5777 VITE_PORT=5888
VITE_BASE=/ VITE_BASE=/

View File

@@ -58,7 +58,7 @@ function createRequestClient(baseURL: string) {
if (status >= 200 && status < 400 && code === 0) { if (status >= 200 && status < 400 && code === 0) {
return data; return data;
} }
throw new Error(msg); throw new Error(`Error ${status}: ${msg}`);
}); });
return client; return client;
} }

View File

@@ -115,10 +115,10 @@ function setupAccessGuard(router: Router) {
// 保存菜单信息和路由信息 // 保存菜单信息和路由信息
accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes); accessStore.setAccessRoutes(accessibleRoutes);
const redirectPath = (from.query.redirect ?? to.path) as string; const redirectPath = (from.query.redirect ?? to.fullPath) as string;
return { return {
path: decodeURIComponent(redirectPath), ...router.resolve(decodeURIComponent(redirectPath)),
replace: true, replace: true,
}; };
}); });

View File

@@ -17,7 +17,6 @@ const routes: RouteRecordRaw[] = [
children: [ children: [
{ {
meta: { meta: {
icon: 'mdi:shield-key-outline',
title: $t('page.demos.naive'), title: $t('page.demos.naive'),
}, },
name: 'NaiveDemos', name: 'NaiveDemos',

View File

@@ -1,11 +1,11 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { import {
VBEN_ANT_PREVIEW_URL,
VBEN_DOC_URL, VBEN_DOC_URL,
VBEN_ELE_PREVIEW_URL, VBEN_ELE_PREVIEW_URL,
VBEN_GITHUB_URL, VBEN_GITHUB_URL,
VBEN_LOGO_URL, VBEN_LOGO_URL,
VBEN_PREVIEW_URL,
} from '@vben/constants'; } from '@vben/constants';
import { BasicLayout, IFrameView } from '#/layouts'; import { BasicLayout, IFrameView } from '#/layouts';
@@ -38,8 +38,7 @@ const routes: RouteRecordRaw[] = [
component: IFrameView, component: IFrameView,
meta: { meta: {
icon: 'lucide:book-open-text', icon: 'lucide:book-open-text',
iframeSrc: VBEN_DOC_URL, link: VBEN_DOC_URL,
keepAlive: true,
title: $t('page.vben.document'), title: $t('page.vben.document'),
}, },
}, },
@@ -59,7 +58,7 @@ const routes: RouteRecordRaw[] = [
component: IFrameView, component: IFrameView,
meta: { meta: {
badgeType: 'dot', badgeType: 'dot',
link: VBEN_PREVIEW_URL, link: VBEN_ANT_PREVIEW_URL,
title: $t('page.vben.antdv'), title: $t('page.vben.antdv'),
}, },
}, },

View File

@@ -13,7 +13,7 @@ onMounted(() => {
containLabel: true, containLabel: true,
left: '1%', left: '1%',
right: '1%', right: '1%',
top: '2 %', top: '2 %',
}, },
series: [ series: [
{ {

View File

@@ -13,7 +13,7 @@ onMounted(() => {
containLabel: true, containLabel: true,
left: '1%', left: '1%',
right: '1%', right: '1%',
top: '2 %', top: '2 %',
}, },
series: [ series: [
{ {

View File

@@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { NotificationType } from 'naive-ui'; import { Page } from '@vben/common-ui';
import { type NotificationType } from 'naive-ui';
import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui'; import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';
const notification = useNotification(); const notification = useNotification();
@@ -33,59 +34,35 @@ function notify(type: NotificationType) {
</script> </script>
<template> <template>
<div class="p-5"> <Page description="支持多语言,主题功能集成切换等" title="naive组件使用演示">
<div class="card-box p-5"> <NCard class="mb-5" title="按钮">
<h1 class="text-xl font-semibold">naive组件使用演示</h1> <NSpace>
<div class="text-foreground/80 mt-2">支持多语言主题功能集成切换等</div> <NButton>Default</NButton>
</div> <NButton type="tertiary"> Tertiary </NButton>
<NButton type="primary"> Primary </NButton>
<NButton type="info"> Info </NButton>
<NButton type="success"> Success </NButton>
<NButton type="warning"> Warning </NButton>
<NButton type="error"> Error </NButton>
</NSpace>
</NCard>
<div class="card-box mt-5 p-5"> <NCard class="mb-5" title="Message">
<div class="mb-3"> <NSpace>
<span class="text-lg font-semibold">按钮</span>
</div>
<div>
<NSpace>
<NButton>Default</NButton>
<NButton type="tertiary"> Tertiary </NButton>
<NButton type="primary"> Primary </NButton>
<NButton type="info"> Info </NButton>
<NButton type="success"> Success </NButton>
<NButton type="warning"> Warning </NButton>
<NButton type="error"> Error </NButton>
</NSpace>
</div>
</div>
<div class="card-box mt-5 p-5">
<div class="mb-3">
<span class="text-lg font-semibold">卡片</span>
</div>
<div>
<NCard title="卡片"> 卡片内容 </NCard>
</div>
</div>
<div class="card-box mt-5 p-5">
<div class="mb-3">
<span class="text-lg font-semibold">信息 Message </span>
</div>
<div class="flex gap-3">
<NButton type="error" @click="error"> 错误 </NButton> <NButton type="error" @click="error"> 错误 </NButton>
<NButton type="warning" @click="warning"> 警告 </NButton> <NButton type="warning" @click="warning"> 警告 </NButton>
<NButton type="success" @click="success"> 成功 </NButton> <NButton type="success" @click="success"> 成功 </NButton>
<NButton type="primary" @click="loading"> 加载中 </NButton> <NButton type="primary" @click="loading"> 加载中 </NButton>
</div> </NSpace>
</div> </NCard>
<div class="card-box mt-5 p-5"> <NCard class="mb-5" title="Notification">
<div class="mb-3"> <NSpace>
<span class="text-lg font-semibold">通知 Notification </span>
</div>
<div class="flex gap-3">
<NButton type="error" @click="notify('error')"> 错误 </NButton> <NButton type="error" @click="notify('error')"> 错误 </NButton>
<NButton type="warning" @click="notify('warning')"> 警告 </NButton> <NButton type="warning" @click="notify('warning')"> 警告 </NButton>
<NButton type="success" @click="notify('success')"> 成功 </NButton> <NButton type="success" @click="notify('success')"> 成功 </NButton>
<NButton type="primary" @click="notify('info')"> 加载中 </NButton> <NButton type="primary" @click="notify('info')"> 加载中 </NButton>
</div> </NSpace>
</div> </NCard>
</div> </Page>
</template> </template>

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { Page } from '@vben/common-ui';
import { NDataTable } from 'naive-ui'; import { NDataTable } from 'naive-ui';
const columns = ref([ const columns = ref([
@@ -25,7 +27,12 @@ const data = [
</script> </script>
<template> <template>
<NDataTable :columns="columns" :data="data" /> <Page
description="表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。"
title="NDataTable"
>
<NDataTable :columns="columns" :data="data" />
</Page>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@@ -162,7 +162,11 @@ function nav(): DefaultTheme.NavItem[] {
items: [ items: [
{ {
link: 'https://www.vben.pro', link: 'https://www.vben.pro',
text: 'Ant Design Vue 版本(默认)', text: '演示版本',
},
{
link: 'https://ant.vben.pro',
text: 'Ant Design Vue 版本',
}, },
{ {
link: 'https://naive.vben.pro', link: 'https://naive.vben.pro',
@@ -250,6 +254,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
text: '为什么选择我们?', text: '为什么选择我们?',
}, },
{ link: 'introduction/quick-start', text: '快速开始' }, { link: 'introduction/quick-start', text: '快速开始' },
{ link: 'introduction/thin', text: '精简版本' },
], ],
}, },
{ {
@@ -284,6 +289,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] {
items: [ items: [
{ link: 'project/standard', text: '规范' }, { link: 'project/standard', text: '规范' },
{ link: 'project/cli', text: 'CLI' }, { link: 'project/cli', text: 'CLI' },
{ link: 'project/dir', text: '目录说明' },
{ link: 'project/test', text: '单元测试' }, { link: 'project/test', text: '单元测试' },
{ link: 'project/tailwindcss', text: 'Tailwind CSS' }, { link: 'project/tailwindcss', text: 'Tailwind CSS' },
{ link: 'project/changeset', text: 'Changeset' }, { link: 'project/changeset', text: 'Changeset' },

View File

@@ -4,7 +4,10 @@
<div class="vp-doc vben-contributors"> <div class="vp-doc vben-contributors">
<p>Contributors</p> <p>Contributors</p>
<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> <a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" /> <img
alt="Contributors"
src="https://opencollective.com/vbenjs/contributors.svg?button=false"
/>
</a> </a>
</div> </div>
</template> </template>

View File

@@ -78,6 +78,8 @@ npm 脚本是项目常见的配置,用于执行一些常见的任务,比如
"dev:docs": "pnpm -F @vben/docs run dev", "dev:docs": "pnpm -F @vben/docs run dev",
// 启动web-ele应用 // 启动web-ele应用
"dev:ele": "pnpm -F @vben/web-ele run dev", "dev:ele": "pnpm -F @vben/web-ele run dev",
// 启动演示应用
"dev:play": "pnpm -F @vben/playground run dev",
// 启动web-naive应用 // 启动web-naive应用
"dev:naive": "pnpm -F @vben/web-naive run dev", "dev:naive": "pnpm -F @vben/web-naive run dev",
// 格式化代码 // 格式化代码

View File

@@ -221,7 +221,7 @@ function createRequestClient(baseURL: string) {
if (status >= 200 && status < 400 && code === 0) { if (status >= 200 && status < 400 && code === 0) {
return data; return data;
} }
throw new Error(msg); throw new Error(`Error ${status}: ${msg}`);
}); });
return client; return client;
} }

View File

@@ -198,7 +198,11 @@ async function loadDayjsLocale(lang: SupportedLanguagesType) {
locale = await import('dayjs/locale/en'); locale = await import('dayjs/locale/en');
} }
} }
dayjs.locale(locale); if (locale) {
dayjs.locale(locale);
} else {
console.error(`Failed to load dayjs locale for ${lang}`);
}
} }
``` ```

View File

@@ -72,11 +72,24 @@ pnpm install
### 运行项目 ### 运行项目
执行以下命令即可运行项目: 执行以下命运行项目:
```bash ```bash
# 启动项目 # 启动项目
pnpm dev pnpm dev
``` ```
此时,你会看到类似如下的输出,选择你需要运行的项目:
```bash
◆ Select the app you need to run [dev]:
│ ● @vben/web-antd
│ ○ @vben/web-ele
│ ○ @vben/web-naive
│ ○ @vben/docs
│ ○ @vben/playground
```
现在,你可以在浏览器访问 `http://localhost:5555` 查看项目。 现在,你可以在浏览器访问 `http://localhost:5555` 查看项目。

View File

@@ -0,0 +1,62 @@
# 精简版本
`5.0` 版本开始,我们不再提供精简的仓库或者分支。我们的目标是提供一个更加一致的开发体验,同时减少维护成本。在这里,我们将如何介绍自己的项目,如何去精简以及移除不需要的功能。
## 应用精简
首先,确认你需要的 `UI` 组件库版本,然后删除对应的应用,比如你选择使用 `Ant Design Vue`,那么你可以删除其他应用, 只需要删除下面两个文件夹即可:
```bash
apps/web-ele
apps/web-native
```
::: tip
如果项目没有内置你需要的 `UI` 组件库应用,你可以直接全部删除其他应用。然后自行新建应用即可。
:::
## 演示代码精简
如果你不需要演示代码,你可以直接删除的`playground`文件夹。
## 文档精简
如果你不需要文档,你可以直接删除`docs`文件夹。
## Mock 服务精简
如果你不需要`Mock`服务,你可以直接删除`apps/backend-mock`文件夹。同时在你的应用下`.env.development`文件中删除`VITE_NITRO_MOCK`变量。
```bash
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=false
```
## 安装依赖
到这里,你已经完成了精简操作,接下来你可以安装依赖,并启动你的项目:
```bash
# 根目录下执行
pnpm install
```
## 命令调整
在精简后,你可能需要根据你的项目调整命令,在根目录下的`package.json`文件中,你可以调整`scripts`字段,移除你不需要的命令。
```json
{
"scripts": {
"dev:antd": "pnpm -F @vben/web-antd run dev",
"dev:docs": "pnpm -F @vben/docs run dev",
"dev:ele": "pnpm -F @vben/web-ele run dev",
"dev:play": "pnpm -F @vben/playground run dev",
"dev:naive": "pnpm -F @vben/web-naive run dev"
}
}
```

View File

@@ -0,0 +1,68 @@
# 目录说明
目录使用 Monorepo 管理,项目结构如下:
```bash
.
├── Dockerfile # Docker 镜像构建文件
├── README.md # 项目说明文档
├── apps # 项目应用目录
│   ├── backend-mock # 后端模拟服务应用
│   ├── web-antd # 基于 Ant Design Vue 的前端应用
│   ├── web-ele # 基于 Element Plus 的前端应用
│   └── web-naive # 基于 Naive UI 的前端应用
├── build-local-docker-image.sh # 本地构建 Docker 镜像脚本
├── cspell.json # CSpell 配置文件
├── docs # 项目文档目录
├── eslint.config.mjs # ESLint 配置文件
├── internal # 内部工具目录
│   ├── lint-configs # 代码检查配置
│   │   ├── commitlint-config # Commitlint 配置
│   │   ├── eslint-config # ESLint 配置
│   │   ├── prettier-config # Prettier 配置
│   │   └── stylelint-config # Stylelint 配置
│   ├── node-utils # Node.js 工具
│   ├── tailwind-config # Tailwind 配置
│   ├── tsconfig # 通用 tsconfig 配置
│   └── vite-config # 通用Vite 配置
├── package.json # 项目依赖配置
├── packages # 项目包目录
│   ├── @core # 核心包
│   │   ├── base # 基础包
│   │   │   ├── design # 设计相关
│   │   │   ├── icons # 图标
│   │   │   ├── shared # 共享
│   │   │   └── typings # 类型定义
│   │   ├── composables # 组合式 API
│   │   ├── preferences # 偏好设置
│   │   └── ui-kit # UI 组件集合
│   │   ├── layout-ui # 布局 UI
│   │   ├── menu-ui # 菜单 UI
│   │   ├── shadcn-ui # shadcn UI
│   │   └── tabs-ui # 标签页 UI
│   ├── constants # 常量
│   ├── effects # 副作用相关包
│   │   ├── access # 访问控制
│   │   ├── chart-ui # 图表 UI
│   │   ├── common-ui # 通用 UI
│   │   ├── hooks # 组合式 API
│   │   ├── layouts # 布局
│   │   └── request # 请求
│   ├── icons # 图标
│   ├── locales # 国际化
│   ├── preferences # 偏好设置
│   ├── stores # 状态管理
│   ├── styles # 样式
│   ├── types # 类型定义
│   └── utils # 工具
├── playground # 演示目录
├── pnpm-lock.yaml # pnpm 锁定文件
├── pnpm-workspace.yaml # pnpm 工作区配置文件
├── scripts # 脚本目录
│   ├── turbo-run # Turbo 运行脚本
│   └── vsh # VSH 脚本
├── stylelint.config.mjs # Stylelint 配置文件
├── turbo.json # Turbo 配置文件
├── vben-admin.code-workspace # VS Code 工作区配置文件
└── vitest.config.ts # Vite 配置文件
```

View File

@@ -13,7 +13,6 @@
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"allowImportingTsExtensions": true,
"strict": true, "strict": true,
"strictNullChecks": true, "strictNullChecks": true,

View File

@@ -41,6 +41,7 @@
"dev:antd": "pnpm -F @vben/web-antd run dev", "dev:antd": "pnpm -F @vben/web-antd run dev",
"dev:docs": "pnpm -F @vben/docs run dev", "dev:docs": "pnpm -F @vben/docs run dev",
"dev:ele": "pnpm -F @vben/web-ele run dev", "dev:ele": "pnpm -F @vben/web-ele run dev",
"dev:play": "pnpm -F @vben/playground run dev",
"dev:naive": "pnpm -F @vben/web-naive run dev", "dev:naive": "pnpm -F @vben/web-naive run dev",
"format": "vsh lint --format", "format": "vsh lint --format",
"lint": "vsh lint", "lint": "vsh lint",
@@ -69,6 +70,8 @@
"@vben/turbo-run": "workspace:*", "@vben/turbo-run": "workspace:*",
"@vben/vite-config": "workspace:*", "@vben/vite-config": "workspace:*",
"@vben/vsh": "workspace:*", "@vben/vsh": "workspace:*",
"@vitejs/plugin-vue": "^5.1.2",
"@vitejs/plugin-vue-jsx": "^4.0.0",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
@@ -84,6 +87,7 @@
"unbuild": "^2.0.0", "unbuild": "^2.0.0",
"vite": "^5.4.0", "vite": "^5.4.0",
"vitest": "^2.0.5", "vitest": "^2.0.5",
"vue": "^3.4.37",
"vue-tsc": "^2.0.29" "vue-tsc": "^2.0.29"
}, },
"engines": { "engines": {

View File

@@ -35,7 +35,9 @@
min-height: 100vh; min-height: 100vh;
/* overflow: overlay; */ /* overflow: overlay; */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
a, a,

View File

@@ -8,7 +8,7 @@
/* 主体区域背景色 */ /* 主体区域背景色 */
--background-deep: 210 11.11% 96.47%; --background-deep: 210 11.11% 96.47%;
--foreground: 210 6% 21%; --foreground: 222 84% 5%;
/* Background color for <Card /> */ /* Background color for <Card /> */
--card: 0 0% 100%; --card: 0 0% 100%;

View File

@@ -22,3 +22,5 @@ export const VBEN_PREVIEW_URL = 'https://www.vben.pro';
export const VBEN_ELE_PREVIEW_URL = 'https://ele.vben.pro'; export const VBEN_ELE_PREVIEW_URL = 'https://ele.vben.pro';
export const VBEN_NAIVE_PREVIEW_URL = 'https://naive.vben.pro'; export const VBEN_NAIVE_PREVIEW_URL = 'https://naive.vben.pro';
export const VBEN_ANT_PREVIEW_URL = 'https://ant.vben.pro';

View File

@@ -8,7 +8,7 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialog as AlertDialogRoot, AlertDialog as AlertDialogRoot,
AlertDialogTitle, AlertDialogTitle,
} from '@vben-core/shadcn-ui/components/ui/alert-dialog'; } from '../ui/alert-dialog';
interface Props { interface Props {
cancelText?: string; cancelText?: string;

View File

@@ -5,20 +5,15 @@ import type {
AvatarRootProps, AvatarRootProps,
} from 'radix-vue'; } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar';
Avatar,
AvatarFallback,
AvatarImage,
} from '@vben-core/shadcn-ui/components/ui/avatar';
interface Props extends AvatarRootProps, AvatarFallbackProps, AvatarImageProps { interface Props extends AvatarRootProps, AvatarFallbackProps, AvatarImageProps {
alt?: string; alt?: string;
class?: HTMLAttributes['class']; class?: any;
dot?: boolean; dot?: boolean;
dotClass?: HTMLAttributes['class']; dotClass?: any;
} }
defineOptions({ defineOptions({

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { IBreadcrumb } from './interface'; import type { IBreadcrumb } from './types';
import { VbenIcon } from '../icon'; import { VbenIcon } from '../icon';

View File

@@ -1,7 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { IBreadcrumb } from './interface'; import type { IBreadcrumb } from './types';
import { ChevronDown } from '@vben-core/icons'; import { ChevronDown } from '@vben-core/icons';
import { VbenIcon } from '../icon';
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,
@@ -9,15 +11,13 @@ import {
BreadcrumbList, BreadcrumbList,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
} from '@vben-core/shadcn-ui/components/ui/breadcrumb'; } from '../ui/breadcrumb';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@vben-core/shadcn-ui/components/ui/dropdown-menu'; } from '../ui/dropdown-menu';
import { VbenIcon } from '../icon';
interface Props { interface Props {
breadcrumbs: IBreadcrumb[]; breadcrumbs: IBreadcrumb[];

View File

@@ -1,4 +1,4 @@
export { default as VbenBreadcrumb } from './breadcrumb.vue'; export { default as VbenBreadcrumb } from './breadcrumb.vue';
export { default as VbenBackgroundBreadcrumb } from './breadcrumb-background.vue'; export { default as VbenBackgroundBreadcrumb } from './breadcrumb-background.vue';
export type * from './interface'; export type * from './types';

View File

@@ -1,18 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { LoaderCircle } from '@vben-core/icons'; import { LoaderCircle } from '@vben-core/icons';
import {
type ButtonVariants,
buttonVariants,
} from '@vben-core/shadcn-ui/components/ui/button';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
import { Primitive, type PrimitiveProps } from 'radix-vue'; import { Primitive, type PrimitiveProps } from 'radix-vue';
import { type ButtonVariants, buttonVariants } from '../ui/button';
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
class?: HTMLAttributes['class']; class?: any;
disabled?: boolean; disabled?: boolean;
loading?: boolean; loading?: boolean;
size?: ButtonVariants['size']; size?: ButtonVariants['size'];

View File

@@ -1,17 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ButtonVariants } from '@vben-core/shadcn-ui/components/ui/button'; import type { ButtonVariants } from '../ui/button';
import { computed, type HTMLAttributes, useSlots } from 'vue'; import { computed, useSlots } from 'vue';
import { VbenTooltip } from '@vben-core/shadcn-ui/components/tooltip';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
import { type PrimitiveProps } from 'radix-vue'; import { type PrimitiveProps } from 'radix-vue';
import { VbenTooltip } from '../tooltip';
import VbenButton from './button.vue'; import VbenButton from './button.vue';
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
class?: HTMLAttributes['class']; class?: any;
disabled?: boolean; disabled?: boolean;
onClick?: () => void; onClick?: () => void;
tooltip?: string; tooltip?: string;

View File

@@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'; import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue';
import { Checkbox } from '@vben-core/shadcn-ui/components/ui/checkbox';
import { useForwardPropsEmits } from 'radix-vue'; import { useForwardPropsEmits } from 'radix-vue';
import { Checkbox } from '../ui/checkbox';
const props = defineProps< const props = defineProps<
{ {
name: string; name: string;

View File

@@ -7,9 +7,10 @@ import type {
import type { IContextMenuItem } from './interface'; import type { IContextMenuItem } from './interface';
import type { HTMLAttributes } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { useForwardPropsEmits } from 'radix-vue';
import { import {
ContextMenu, ContextMenu,
ContextMenuContent, ContextMenuContent,
@@ -17,17 +18,15 @@ import {
ContextMenuSeparator, ContextMenuSeparator,
ContextMenuShortcut, ContextMenuShortcut,
ContextMenuTrigger, ContextMenuTrigger,
} from '@vben-core/shadcn-ui/components/ui/context-menu'; } from '../ui/context-menu';
import { useForwardPropsEmits } from 'radix-vue';
const props = defineProps< const props = defineProps<
{ {
class?: HTMLAttributes['class']; class?: any;
contentClass?: HTMLAttributes['class']; contentClass?: any;
contentProps?: ContextMenuContentProps; contentProps?: ContextMenuContentProps;
handlerData?: Record<string, any>; handlerData?: Record<string, any>;
itemClass?: HTMLAttributes['class']; itemClass?: any;
menus: (data: any) => IContextMenuItem[]; menus: (data: any) => IContextMenuItem[];
} & ContextMenuRootProps } & ContextMenuRootProps
>(); >();

View File

@@ -11,7 +11,7 @@ import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@vben-core/shadcn-ui/components/ui/dropdown-menu'; } from '../ui/dropdown-menu';
interface Props extends DropdownMenuProps {} interface Props extends DropdownMenuProps {}

View File

@@ -7,7 +7,7 @@ import {
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@vben-core/shadcn-ui/components/ui/dropdown-menu'; } from '../ui/dropdown-menu';
interface Props extends DropdownMenuProps {} interface Props extends DropdownMenuProps {}

View File

@@ -5,24 +5,23 @@ import type {
HoverCardRootProps, HoverCardRootProps,
} from 'radix-vue'; } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { useForwardPropsEmits } from 'radix-vue';
import { import {
HoverCard, HoverCard,
HoverCardContent, HoverCardContent,
HoverCardTrigger, HoverCardTrigger,
} from '@vben-core/shadcn-ui/components/ui/hover-card'; } from '../ui/hover-card';
import { useForwardPropsEmits } from 'radix-vue'; interface Props extends HoverCardRootProps {
class?: any;
contentClass?: any;
contentProps?: HoverCardContentProps;
}
const props = defineProps< const props = defineProps<Props>();
{
class?: HTMLAttributes['class'];
contentClass?: HTMLAttributes['class'];
contentProps?: HoverCardContentProps;
} & HoverCardRootProps
>();
const emits = defineEmits<HoverCardRootEmits>(); const emits = defineEmits<HoverCardRootEmits>();

View File

@@ -2,13 +2,10 @@
import { ref, useSlots } from 'vue'; import { ref, useSlots } from 'vue';
import { Eye, EyeOff } from '@vben-core/icons'; import { Eye, EyeOff } from '@vben-core/icons';
import {
type InputProps,
VbenInput,
} from '@vben-core/shadcn-ui/components/input';
import { useForwardProps } from 'radix-vue'; import { useForwardProps } from 'radix-vue';
import { type InputProps, VbenInput } from '../input';
import PasswordStrength from './password-strength.vue'; import PasswordStrength from './password-strength.vue';
interface Props extends InputProps {} interface Props extends InputProps {}

View File

@@ -1,2 +1,2 @@
export { default as VbenInput } from './input.vue'; export { default as VbenInput } from './input.vue';
export type * from './interface'; export type * from './types';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { InputProps } from './interface'; import type { InputProps } from './types';
import { computed } from 'vue'; import { computed } from 'vue';

View File

@@ -1,7 +1,5 @@
import type { HTMLAttributes } from 'vue';
interface InputProps { interface InputProps {
class?: HTMLAttributes['class']; class?: any;
/** /**
* *
*/ */

View File

@@ -1,12 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
import { Primitive, type PrimitiveProps } from 'radix-vue'; import { Primitive, type PrimitiveProps } from 'radix-vue';
interface Props extends PrimitiveProps { interface Props extends PrimitiveProps {
class?: HTMLAttributes['class']; class?: any;
href: string; href: string;
} }

View File

@@ -1,3 +1,3 @@
export { default as VbenPinInput } from './input.vue'; export { default as VbenPinInput } from './input.vue';
export type * from './interface'; export type * from './types';

View File

@@ -1,14 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PinInputProps } from './interface'; import type { PinInputProps } from './types';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { VbenButton } from '@vben-core/shadcn-ui/components/button'; import { VbenButton } from '../button';
import { import { PinInput, PinInputGroup, PinInputInput } from '../ui/pin-input';
PinInput,
PinInputGroup,
PinInputInput,
} from '@vben-core/shadcn-ui/components/ui/pin-input';
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,

View File

@@ -1,5 +1,3 @@
import type { HTMLAttributes } from 'vue';
interface PinInputProps { interface PinInputProps {
/** /**
* loading * loading
@@ -9,7 +7,7 @@ interface PinInputProps {
* *
*/ */
btnText?: string; btnText?: string;
class?: HTMLAttributes['class']; class?: any;
/** /**
* *
*/ */

View File

@@ -5,27 +5,23 @@ import type {
PopoverRootProps, PopoverRootProps,
} from 'radix-vue'; } from 'radix-vue';
import type { HTMLAttributes } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
import { useForwardPropsEmits } from 'radix-vue';
import { import {
PopoverContent, PopoverContent,
Popover as PopoverRoot, Popover as PopoverRoot,
PopoverTrigger, PopoverTrigger,
} from '@vben-core/shadcn-ui/components/ui/popover'; } from '../ui/popover';
import { useForwardPropsEmits } from 'radix-vue'; interface Props extends PopoverRootProps {
class?: any;
contentClass?: any;
contentProps?: PopoverContentProps;
}
const props = withDefaults( const props = withDefaults(defineProps<Props>(), {});
defineProps<
{
class?: HTMLAttributes['class'];
contentClass?: HTMLAttributes['class'];
contentProps?: PopoverContentProps;
} & PopoverRootProps
>(),
{},
);
const emits = defineEmits<PopoverRootEmits>(); const emits = defineEmits<PopoverRootEmits>();

View File

@@ -1,17 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { HTMLAttributes } from 'vue';
import { ref } from 'vue'; import { ref } from 'vue';
import {
ScrollArea,
ScrollBar,
} from '@vben-core/shadcn-ui/components/ui/scroll-area';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
import { ScrollArea, ScrollBar } from '../ui/scroll-area';
interface Props { interface Props {
class?: HTMLAttributes['class']; class?: any;
horizontal?: boolean; horizontal?: boolean;
scrollBarClass?: HTMLAttributes['class']; scrollBarClass?: any;
shadow?: boolean; shadow?: boolean;
shadowBorder?: boolean; shadowBorder?: boolean;
} }

View File

@@ -1,3 +1,3 @@
export type * from './interface';
export { default as VbenSegmented } from './segmented.vue'; export { default as VbenSegmented } from './segmented.vue';
export type * from './types';

View File

@@ -1,16 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SegmentedItem } from './interface'; import type { SegmentedItem } from './types';
import { computed } from 'vue'; import { computed } from 'vue';
import {
Tabs,
TabsContent,
TabsList,
} from '@vben-core/shadcn-ui/components/ui/tabs';
import { TabsTrigger } from 'radix-vue'; import { TabsTrigger } from 'radix-vue';
import { Tabs, TabsContent, TabsList } from '../ui/tabs';
import TabsIndicator from './tabs-indicator.vue'; import TabsIndicator from './tabs-indicator.vue';
interface Props { interface Props {

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, type HTMLAttributes } from 'vue'; import { computed } from 'vue';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
@@ -9,9 +9,7 @@ import {
useForwardProps, useForwardProps,
} from 'radix-vue'; } from 'radix-vue';
const props = defineProps< const props = defineProps<{ class?: any } & TabsIndicatorProps>();
{ class?: HTMLAttributes['class'] } & TabsIndicatorProps
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { class: _, ...delegated } = props; const { class: _, ...delegated } = props;

View File

@@ -1,11 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, useSlots } from 'vue'; import { computed, useSlots } from 'vue';
import { import { X } from 'lucide-vue-next';
VbenButton,
VbenIconButton, import { VbenButton, VbenIconButton } from '../button';
} from '@vben-core/shadcn-ui/components/button'; import { VbenScrollbar } from '../scrollbar';
import { VbenScrollbar } from '@vben-core/shadcn-ui/components/scrollbar';
import { import {
Sheet, Sheet,
SheetClose, SheetClose,
@@ -15,9 +14,7 @@ import {
SheetHeader, SheetHeader,
SheetTitle, SheetTitle,
SheetTrigger, SheetTrigger,
} from '@vben-core/shadcn-ui/components/ui/sheet'; } from '../ui/sheet';
import { X } from 'lucide-vue-next';
interface Props { interface Props {
cancelText?: string; cancelText?: string;

View File

@@ -1,18 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import type { TooltipContentProps } from 'radix-vue'; import type { TooltipContentProps } from 'radix-vue';
import type { HTMLAttributes } from 'vue'; import type { StyleValue } from 'vue';
import { import {
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from '@vben-core/shadcn-ui/components/ui/tooltip'; } from '../ui/tooltip';
interface Props { interface Props {
contentClass?: HTMLAttributes['class']; contentClass?: any;
contentStyle?: HTMLAttributes['style']; contentStyle?: StyleValue;
delayDuration?: number; delayDuration?: number;
side: TooltipContentProps['side']; side: TooltipContentProps['side'];
} }

View File

@@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, type HTMLAttributes } from 'vue'; import { computed, type HTMLAttributes } from 'vue';
import { buttonVariants } from '@vben-core/shadcn-ui/components/ui/button';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue'; import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue';
import { buttonVariants } from '../button';
const props = defineProps< const props = defineProps<
{ class?: HTMLAttributes['class'] } & AlertDialogActionProps { class?: HTMLAttributes['class'] } & AlertDialogActionProps
>(); >();

View File

@@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, type HTMLAttributes } from 'vue'; import { computed, type HTMLAttributes } from 'vue';
import { buttonVariants } from '@vben-core/shadcn-ui/components/ui/button';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue'; import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue';
import { buttonVariants } from '../button';
const props = defineProps< const props = defineProps<
{ class?: HTMLAttributes['class'] } & AlertDialogCancelProps { class?: HTMLAttributes['class'] } & AlertDialogCancelProps
>(); >();

View File

@@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import type { toggleVariants } from '@vben-core/shadcn-ui/components/ui/toggle';
import type { VariantProps } from 'class-variance-authority'; import type { VariantProps } from 'class-variance-authority';
import type { toggleVariants } from '../toggle';
import { computed, type HTMLAttributes, provide } from 'vue'; import { computed, type HTMLAttributes, provide } from 'vue';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';

View File

@@ -3,7 +3,6 @@ import type { VariantProps } from 'class-variance-authority';
import { computed, type HTMLAttributes, inject } from 'vue'; import { computed, type HTMLAttributes, inject } from 'vue';
import { toggleVariants } from '@vben-core/shadcn-ui/components/ui/toggle';
import { cn } from '@vben-core/shared'; import { cn } from '@vben-core/shared';
import { import {
@@ -12,6 +11,8 @@ import {
useForwardProps, useForwardProps,
} from 'radix-vue'; } from 'radix-vue';
import { toggleVariants } from '../toggle';
type ToggleGroupVariants = VariantProps<typeof toggleVariants>; type ToggleGroupVariants = VariantProps<typeof toggleVariants>;
const props = defineProps< const props = defineProps<

View File

@@ -1,6 +1,6 @@
## Effects 目录 ## Effects 目录
`effects` 目录专门用于存放与副作用相关的代码和逻辑。如果你的包具有以下特点,建议将其放置在 `effects` 目录下: `effects` 目录专门用于存放与轻微耦合相关的代码和逻辑。如果你的包具有以下特点,建议将其放置在 `effects` 目录下:
- **状态管理**:使用状态管理框架 `pinia`并包含处理副作用如异步操作、API 调用)的部分。 - **状态管理**:使用状态管理框架 `pinia`并包含处理副作用如异步操作、API 调用)的部分。
- **用户偏好设置**:使用 `@vben-core/preferences` 处理用户偏好设置,涉及本地存储或浏览器缓存逻辑(如使用 `localStorage`)。 - **用户偏好设置**:使用 `@vben-core/preferences` 处理用户偏好设置,涉及本地存储或浏览器缓存逻辑(如使用 `localStorage`)。

View File

@@ -0,0 +1,39 @@
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { EllipsisText } from '..';
describe('ellipsis-text.vue', () => {
it('renders the correct content and truncates text', async () => {
const wrapper = mount(EllipsisText, {
props: {
line: 1,
title: 'Test Title',
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
expect(wrapper.text()).toContain('This is a very long text');
// 检查 ellipsis 是否应用了正确的 class
const ellipsis = wrapper.find('.truncate');
expect(ellipsis.exists()).toBe(true);
});
it('expands text on click if expand is true', async () => {
const wrapper = mount(EllipsisText, {
props: {
expand: true,
line: 1,
},
slots: {
default: 'This is a very long text that should be truncated.',
},
});
const ellipsis = wrapper.find('.truncate');
await ellipsis.trigger('click');
expect(wrapper.emitted('expandChange')).toBeTruthy();
});
});

View File

@@ -23,7 +23,7 @@ interface Props {
* 提示框位置 * 提示框位置
* @default 'top' * @default 'top'
*/ */
placement: 'bottom' | 'left' | 'right' | 'top'; placement?: 'bottom' | 'left' | 'right' | 'top';
/** /**
* 是否启用文本提示框 * 是否启用文本提示框
* @default true * @default true
@@ -49,8 +49,9 @@ interface Props {
* 提示框内容区域样式 * 提示框内容区域样式
* @default { textAlign: 'justify' } * @default { textAlign: 'justify' }
*/ */
tooltipOverlayStyle?: CSSProperties; // tooltipOverlayStyle?: CSSProperties;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
expand: false, expand: false,
line: 1, line: 1,
@@ -99,6 +100,14 @@ function onExpand() {
emit('expandChange', !isExpanded); emit('expandChange', !isExpanded);
} }
function handleExpand() {
if (props.expand) {
onExpand();
} else {
return false;
}
}
</script> </script>
<template> <template>
<VbenTooltip <VbenTooltip
@@ -110,7 +119,6 @@ function onExpand() {
backgroundColor: tooltipBackgroundColor, backgroundColor: tooltipBackgroundColor,
}" }"
:disabled="!showTooltip" :disabled="!showTooltip"
:overlay-style="tooltipOverlayStyle"
:side="placement" :side="placement"
> >
<slot name="tooltip"> <slot name="tooltip">
@@ -127,7 +135,7 @@ function onExpand() {
}" }"
:style="`-webkit-line-clamp: ${line}; max-width: ${textMaxWidth};`" :style="`-webkit-line-clamp: ${line}; max-width: ${textMaxWidth};`"
class="cursor-text overflow-hidden" class="cursor-text overflow-hidden"
@click="expand ? onExpand() : () => false" @click="handleExpand"
v-bind="$attrs" v-bind="$attrs"
> >
<slot></slot> <slot></slot>

View File

@@ -0,0 +1,2 @@
export * from './ellipsis-text';
export * from './page';

View File

@@ -0,0 +1,74 @@
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { Page } from '..';
describe('page.vue', () => {
it('renders title when passed', () => {
const wrapper = mount(Page, {
props: {
title: 'Test Title',
},
});
expect(wrapper.text()).toContain('Test Title');
});
it('renders description when passed', () => {
const wrapper = mount(Page, {
props: {
description: 'Test Description',
},
});
expect(wrapper.text()).toContain('Test Description');
});
it('renders default slot content', () => {
const wrapper = mount(Page, {
slots: {
default: '<p>Default Slot Content</p>',
},
});
expect(wrapper.html()).toContain('<p>Default Slot Content</p>');
});
it('renders footer slot when showFooter is true', () => {
const wrapper = mount(Page, {
props: {
showFooter: true,
},
slots: {
footer: '<p>Footer Slot Content</p>',
},
});
expect(wrapper.html()).toContain('<p>Footer Slot Content</p>');
});
it('applies the custom contentClass', () => {
const wrapper = mount(Page, {
props: {
contentClass: 'custom-class',
},
});
const contentDiv = wrapper.find('.m-4');
expect(contentDiv.classes()).toContain('custom-class');
});
it('does not render description slot if description prop is provided', () => {
const wrapper = mount(Page, {
props: {
description: 'Test Description',
},
slots: {
description: '<p>Description Slot Content</p>',
},
});
expect(wrapper.text()).toContain('Test Description');
expect(wrapper.html()).not.toContain('Description Slot Content');
});
});

View File

@@ -0,0 +1 @@
export { default as Page } from './page.vue';

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
interface Props {
title?: string;
description?: string;
contentClass?: string;
showFooter?: boolean;
}
defineOptions({
name: 'Page',
});
const props = withDefaults(defineProps<Props>(), {
contentClass: '',
description: '',
showFooter: false,
title: '',
});
</script>
<template>
<div class="relative h-full">
<div
v-if="description || $slots.description || title"
class="bg-card px-6 py-4"
>
<div class="mb-2 flex justify-between text-xl font-bold leading-10">
{{ title }}
</div>
<template v-if="description">{{ description }}</template>
<slot v-else name="description"></slot>
</div>
<div :class="contentClass" class="m-4">
<slot></slot>
</div>
<div
v-if="props.showFooter"
class="bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4"
>
<slot name="footer"></slot>
</div>
</div>
</template>

View File

@@ -1,6 +1,3 @@
export * from './about'; export * from './components';
export * from './authentication'; export * from './ui';
export * from './dashboard';
export * from './ellipsis-text';
export * from './fallback';
export { useToast } from '@vben-core/shadcn-ui'; export { useToast } from '@vben-core/shadcn-ui';

View File

@@ -10,6 +10,8 @@ import {
} from '@vben/constants'; } from '@vben/constants';
import { VbenLink, VbenRenderContent } from '@vben-core/shadcn-ui'; import { VbenLink, VbenRenderContent } from '@vben-core/shadcn-ui';
import { Page } from '../../components';
interface Props extends AboutProps {} interface Props extends AboutProps {}
defineOptions({ defineOptions({
@@ -119,18 +121,18 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
</script> </script>
<template> <template>
<div class="m-5"> <Page :title="title">
<template #description>
<p class="text-foreground mt-3 text-sm leading-6">
<VbenLink :href="VBEN_GITHUB_URL">
{{ name }}
</VbenLink>
{{ description }}
</p>
</template>
<div class="card-box p-5"> <div class="card-box p-5">
<div> <div>
<h3 class="text-foreground text-2xl font-semibold leading-7"> <h5 class="text-foreground text-lg">基本信息</h5>
{{ title }}
</h3>
<p class="text-foreground/80 mt-3 text-sm leading-6">
<VbenLink :href="VBEN_GITHUB_URL">
{{ name }}
</VbenLink>
{{ description }}
</p>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<dl class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4"> <dl class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
@@ -139,7 +141,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
<dt class="text-foreground text-sm font-medium leading-6"> <dt class="text-foreground text-sm font-medium leading-6">
{{ item.title }} {{ item.title }}
</dt> </dt>
<dd class="text-foreground/80 mt-1 text-sm leading-6 sm:mt-2"> <dd class="text-foreground mt-1 text-sm leading-6 sm:mt-2">
<VbenRenderContent :content="item.content" /> <VbenRenderContent :content="item.content" />
</dd> </dd>
</div> </div>
@@ -159,7 +161,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
<dt class="text-foreground text-sm"> <dt class="text-foreground text-sm">
{{ item.title }} {{ item.title }}
</dt> </dt>
<dd class="text-foreground/60 mt-1 text-sm sm:mt-2"> <dd class="text-foreground/80 mt-1 text-sm sm:mt-2">
<VbenRenderContent :content="item.content" /> <VbenRenderContent :content="item.content" />
</dd> </dd>
</div> </div>
@@ -178,7 +180,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
<dt class="text-foreground text-sm"> <dt class="text-foreground text-sm">
{{ item.title }} {{ item.title }}
</dt> </dt>
<dd class="text-foreground/60 mt-1 text-sm sm:mt-2"> <dd class="text-foreground/80 mt-1 text-sm sm:mt-2">
<VbenRenderContent :content="item.content" /> <VbenRenderContent :content="item.content" />
</dd> </dd>
</div> </div>
@@ -186,5 +188,5 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
</dl> </dl>
</div> </div>
</div> </div>
</div> </Page>
</template> </template>

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