mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 00:26:20 +08:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3697f6bc5a | ||
![]() |
978edb1e02 | ||
![]() |
b417ac2469 | ||
![]() |
524b9badf2 | ||
![]() |
86ed732ca8 | ||
![]() |
56e66193fc | ||
![]() |
b1636405fc | ||
![]() |
ad89ea7a75 | ||
![]() |
7b3c9d56be | ||
![]() |
1cff0b9753 | ||
![]() |
31d5f03b45 | ||
![]() |
2b65e935c1 | ||
![]() |
58f2b17bde | ||
![]() |
46a9fac38e | ||
![]() |
41612f7723 | ||
![]() |
83ecae7c4e | ||
![]() |
3332b20fd0 | ||
![]() |
2d0153a841 | ||
![]() |
95a4a85c3b | ||
![]() |
3f2dcb8281 | ||
![]() |
67f3d63066 | ||
![]() |
277e98c42c | ||
![]() |
1063b2268e | ||
![]() |
8404c12129 | ||
![]() |
a7d322019e | ||
![]() |
f23821ff46 | ||
![]() |
2b0aedbaba | ||
![]() |
071cc0dcec | ||
![]() |
7db7d6ec5f | ||
![]() |
b3e3e05990 | ||
![]() |
cc678a2b51 | ||
![]() |
7d2bcf476f | ||
![]() |
cfbe379ee4 | ||
![]() |
388e5b5cb3 | ||
![]() |
c1dfbc1ebf |
16
.github/labeler.yml
vendored
16
.github/labeler.yml
vendored
@@ -1,16 +0,0 @@
|
||||
# Add 'feature' label to any PR where the head branch name starts with `feature` or has a `feature` section in the name
|
||||
feature:
|
||||
- head-branch: ["^feat", "feat"]
|
||||
|
||||
bug:
|
||||
- head-branch: ["^fix", "fix"]
|
||||
|
||||
chore:
|
||||
- head-branch: ["^chore", "chore"]
|
||||
|
||||
perf:
|
||||
- head-branch: ["^perf", "perf"]
|
||||
|
||||
documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ["**/*.md", "docs/**"]
|
22
.github/workflows/labeler.yml
vendored
22
.github/workflows/labeler.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: PR Labeler
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor != 'dependabot[bot]' && !contains(github.event.head_commit.message, '[skip ci]')
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Label PR based on title or file changes
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
configuration-path: .github/labeler.yml
|
28
.ls-lint.yml
28
.ls-lint.yml
@@ -1,28 +0,0 @@
|
||||
ls:
|
||||
.js: kebab-case | pointcase
|
||||
.vue: kebab-case | pointcase
|
||||
.ts: kebab-case | pointcase
|
||||
.tsx: kebab-case | pointcase
|
||||
.jsx: kebab-case | pointcase
|
||||
.css: kebab-case | pointcase
|
||||
.d.ts: kebab-case | pointcase
|
||||
# shadcn 自动生成文件为 PascalCase 格式
|
||||
packages/@core/ui-kit/shadcn-ui/src/components/ui:
|
||||
.vue: PascalCase
|
||||
|
||||
ignore:
|
||||
- "**/*.png"
|
||||
- "**/*.jpg"
|
||||
- "**/*.jpeg"
|
||||
- "**/*.jpeg"
|
||||
- "**/*.gif"
|
||||
- "**/_util.ts"
|
||||
- "**/deps/**"
|
||||
- "**/dist/**"
|
||||
- "**/node_modules/**"
|
||||
- "**/.turbo/**"
|
||||
- .git
|
||||
- .vscode
|
||||
- .idea
|
||||
- node_modules
|
||||
- .cache
|
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -9,7 +9,7 @@
|
||||
"url": "http://localhost:5555",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/playground/src"
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
@@ -18,7 +18,7 @@
|
||||
"url": "http://localhost:5666",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-antd/src"
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
@@ -27,7 +27,7 @@
|
||||
"url": "http://localhost:5777",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-ele/src"
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
@@ -36,7 +36,7 @@
|
||||
"url": "http://localhost:5888",
|
||||
"env": { "NODE_ENV": "development" },
|
||||
"sourceMaps": true,
|
||||
"webRoot": "${workspaceFolder}/apps/web-naive/src"
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -213,7 +213,7 @@
|
||||
"README.md": "README*,CHANGELOG*,LICENSE,CNAME",
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json",
|
||||
"Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*,nginx.conf",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,.ls-lint*,cspell.json",
|
||||
"eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json",
|
||||
"tailwind.config.mjs": "postcss.*"
|
||||
},
|
||||
"commentTranslate.hover.enabled": false,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -44,7 +44,7 @@
|
||||
"ant-design-vue": "^4.2.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.5.4",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
114
apps/web-antd/src/adapter/form.ts
Normal file
114
apps/web-antd/src/adapter/form.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
AutoComplete,
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
// 业务表单组件适配
|
||||
|
||||
export type FormComponentType =
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
Mentions,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
TimePicker,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
},
|
||||
config: {
|
||||
baseModelPropName: 'value',
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
Switch: 'checked',
|
||||
Upload: 'fileList',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
required: (value, _params, ctx) => {
|
||||
if ((!value && value !== 0) || value.length === 0) {
|
||||
return $t('formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type { VbenFormProps };
|
1
apps/web-antd/src/adapter/index.ts
Normal file
1
apps/web-antd/src/adapter/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './form';
|
23
apps/web-antd/src/layouts/auth.vue
Normal file
23
apps/web-antd/src/layouts/auth.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthPageLayout } from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const appName = computed(() => preferences.app.name);
|
||||
const logo = computed(() => preferences.logo.source);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthPageLayout
|
||||
:app-name="appName"
|
||||
:logo="logo"
|
||||
:page-description="$t('authentication.pageDesc')"
|
||||
:page-title="$t('authentication.pageTitle')"
|
||||
>
|
||||
<!-- 自定义工具栏 -->
|
||||
<!-- <template #toolbar></template> -->
|
||||
</AuthPageLayout>
|
||||
</template>
|
@@ -13,11 +13,12 @@ import {
|
||||
UserDropdown,
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
import { useAuthStore } from '#/store';
|
||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||
|
||||
const notifications = ref<NotificationItem[]>([
|
||||
{
|
||||
@@ -87,8 +88,6 @@ const menus = computed(() => [
|
||||
},
|
||||
]);
|
||||
|
||||
const { loginLoading } = storeToRefs(authStore);
|
||||
|
||||
const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
@@ -130,11 +129,9 @@ function handleMakeAll() {
|
||||
<AuthenticationLoginExpiredModal
|
||||
v-model:open="accessStore.loginExpired"
|
||||
:avatar
|
||||
:loading="loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
>
|
||||
<LoginForm />
|
||||
</AuthenticationLoginExpiredModal>
|
||||
</template>
|
||||
<template #lock-screen>
|
||||
<LockScreen :avatar @to-login="handleLogout" />
|
||||
|
@@ -1,8 +1,6 @@
|
||||
const BasicLayout = () => import('./basic.vue');
|
||||
const AuthPageLayout = () => import('./auth.vue');
|
||||
|
||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||
|
||||
const AuthPageLayout = () =>
|
||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
||||
|
||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||
|
@@ -1,15 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginCodeParams } from '@vben/common-ui';
|
||||
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.mobile'),
|
||||
},
|
||||
fieldName: 'phoneNumber',
|
||||
label: $t('authentication.mobile'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.mobileTip') })
|
||||
.refine((v) => /^\d{11}$/.test(v), {
|
||||
message: $t('authentication.mobileErrortip'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenPinInput',
|
||||
componentProps: {
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
? $t('authentication.sendText', [countdown])
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
placeholder: $t('authentication.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('authentication.code'),
|
||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
||||
},
|
||||
];
|
||||
});
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
@@ -23,8 +57,8 @@ async function handleLogin(values: LoginCodeParams) {
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,13 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'ForgetPassword' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: 'example@example.com',
|
||||
},
|
||||
fieldName: 'email',
|
||||
label: $t('authentication.email'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.emailTip') })
|
||||
.email($t('authentication.emailValidErrorTip')),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: string) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('reset email:', value);
|
||||
@@ -16,8 +35,8 @@ function handleSubmit(value: string) {
|
||||
|
||||
<template>
|
||||
<AuthenticationForgetPassword
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,18 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import { AuthenticationLogin } from '@vben/common-ui';
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { BasicOption } from '@vben/types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
||||
{
|
||||
label: '超级管理员',
|
||||
value: 'vben',
|
||||
},
|
||||
{
|
||||
label: '管理员',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: '用户',
|
||||
value: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
options: MOCK_USER_OPTIONS,
|
||||
placeholder: $t('authentication.selectAccount'),
|
||||
},
|
||||
fieldName: 'selectAccount',
|
||||
label: $t('authentication.selectAccount'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.selectAccount') })
|
||||
.optional()
|
||||
.default('vben'),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
dependencies: {
|
||||
trigger(values, form) {
|
||||
if (values.selectAccount) {
|
||||
const findUser = MOCK_USER_OPTIONS.find(
|
||||
(item) => item.value === values.selectAccount,
|
||||
);
|
||||
if (findUser) {
|
||||
form.setValues({
|
||||
password: '123456',
|
||||
username: findUser.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
triggerFields: ['selectAccount'],
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="authStore.loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,15 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, h, ref } from 'vue';
|
||||
|
||||
import { AuthenticationRegister } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'Register' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
passwordStrength: true,
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
renderComponentContent() {
|
||||
return {
|
||||
strengthText: () => $t('authentication.passwordStrength'),
|
||||
};
|
||||
},
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.confirmPassword'),
|
||||
},
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.refine((value) => value === password, {
|
||||
message: $t('authentication.confirmPasswordTip'),
|
||||
});
|
||||
},
|
||||
triggerFields: ['password'],
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('authentication.confirmPassword'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenCheckbox',
|
||||
fieldName: 'agreePolicy',
|
||||
renderComponentContent: () => ({
|
||||
default: () =>
|
||||
h('span', [
|
||||
$t('authentication.agree'),
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
class:
|
||||
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
|
||||
href: '',
|
||||
},
|
||||
[
|
||||
$t('authentication.privacyPolicy'),
|
||||
'&',
|
||||
$t('authentication.terms'),
|
||||
],
|
||||
),
|
||||
]),
|
||||
}),
|
||||
rules: z.boolean().refine((value) => !!value, {
|
||||
message: $t('authentication.agreeTip'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: LoginAndRegisterParams) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('register submit:', value);
|
||||
@@ -18,8 +94,8 @@ function handleSubmit(value: LoginAndRegisterParams) {
|
||||
|
||||
<template>
|
||||
<AuthenticationRegister
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -55,12 +55,27 @@ onMounted(() => {
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
// xAxis: {
|
||||
// axisTick: {
|
||||
// show: false,
|
||||
// },
|
||||
// boundaryGap: false,
|
||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
// type: 'category',
|
||||
// },
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 1,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
@@ -69,7 +84,10 @@ onMounted(() => {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
|
@@ -214,7 +214,11 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||
<WorkbenchQuickNav
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
<AnalyticsVisitsSource />
|
||||
|
@@ -37,7 +37,7 @@ function notify(type: NotificationType) {
|
||||
description="支持多语言,主题功能集成切换等"
|
||||
title="Ant Design Vue组件使用演示"
|
||||
>
|
||||
<Card title="按钮">
|
||||
<Card class="mb-5" title="按钮">
|
||||
<Space>
|
||||
<Button>Default</Button>
|
||||
<Button type="primary"> Primary </Button>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-ele",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -42,9 +42,9 @@
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"element-plus": "^2.8.1",
|
||||
"element-plus": "^2.8.2",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.5.4",
|
||||
"vue-router": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
89
apps/web-ele/src/adapter/form.ts
Normal file
89
apps/web-ele/src/adapter/form.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
ElButton,
|
||||
ElCheckbox,
|
||||
ElCheckboxGroup,
|
||||
ElDivider,
|
||||
ElInput,
|
||||
ElInputNumber,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
ElSpace,
|
||||
ElSwitch,
|
||||
ElTimePicker,
|
||||
ElTreeSelect,
|
||||
ElUpload,
|
||||
} from 'element-plus';
|
||||
// 业务表单组件适配
|
||||
|
||||
export type FormComponentType =
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
Checkbox: ElCheckbox,
|
||||
CheckboxGroup: ElCheckboxGroup,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Divider: ElDivider,
|
||||
Input: ElInput,
|
||||
InputNumber: ElInputNumber,
|
||||
RadioGroup: ElRadioGroup,
|
||||
Select: ElSelect,
|
||||
Space: ElSpace,
|
||||
Switch: ElSwitch,
|
||||
TimePicker: ElTimePicker,
|
||||
TreeSelect: ElTreeSelect,
|
||||
Upload: ElUpload,
|
||||
},
|
||||
config: {
|
||||
modelPropNameMap: {
|
||||
Upload: 'fileList',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
required: (value, _params, ctx) => {
|
||||
if ((!value && value !== 0) || value.length === 0) {
|
||||
return $t('formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type { VbenFormProps };
|
1
apps/web-ele/src/adapter/index.ts
Normal file
1
apps/web-ele/src/adapter/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './form';
|
23
apps/web-ele/src/layouts/auth.vue
Normal file
23
apps/web-ele/src/layouts/auth.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthPageLayout } from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const appName = computed(() => preferences.app.name);
|
||||
const logo = computed(() => preferences.logo.source);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthPageLayout
|
||||
:app-name="appName"
|
||||
:logo="logo"
|
||||
:page-description="$t('authentication.pageDesc')"
|
||||
:page-title="$t('authentication.pageTitle')"
|
||||
>
|
||||
<!-- 自定义工具栏 -->
|
||||
<!-- <template #toolbar></template> -->
|
||||
</AuthPageLayout>
|
||||
</template>
|
@@ -13,11 +13,12 @@ import {
|
||||
UserDropdown,
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
import { useAuthStore } from '#/store';
|
||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||
|
||||
const notifications = ref<NotificationItem[]>([
|
||||
{
|
||||
@@ -87,8 +88,6 @@ const menus = computed(() => [
|
||||
},
|
||||
]);
|
||||
|
||||
const { loginLoading } = storeToRefs(authStore);
|
||||
|
||||
const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
@@ -130,11 +129,9 @@ function handleMakeAll() {
|
||||
<AuthenticationLoginExpiredModal
|
||||
v-model:open="accessStore.loginExpired"
|
||||
:avatar
|
||||
:loading="loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
>
|
||||
<LoginForm />
|
||||
</AuthenticationLoginExpiredModal>
|
||||
</template>
|
||||
<template #lock-screen>
|
||||
<LockScreen :avatar @to-login="handleLogout" />
|
||||
|
@@ -1,8 +1,6 @@
|
||||
const BasicLayout = () => import('./basic.vue');
|
||||
const AuthPageLayout = () => import('./auth.vue');
|
||||
|
||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||
|
||||
const AuthPageLayout = () =>
|
||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
||||
|
||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||
|
@@ -1,15 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginCodeParams } from '@vben/common-ui';
|
||||
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.mobile'),
|
||||
},
|
||||
fieldName: 'phoneNumber',
|
||||
label: $t('authentication.mobile'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.mobileTip') })
|
||||
.refine((v) => /^\d{11}$/.test(v), {
|
||||
message: $t('authentication.mobileErrortip'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenPinInput',
|
||||
componentProps: {
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
? $t('authentication.sendText', [countdown])
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
placeholder: $t('authentication.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('authentication.code'),
|
||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
||||
},
|
||||
];
|
||||
});
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
@@ -23,8 +57,8 @@ async function handleLogin(values: LoginCodeParams) {
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,13 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'ForgetPassword' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: 'example@example.com',
|
||||
},
|
||||
fieldName: 'email',
|
||||
label: $t('authentication.email'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.emailTip') })
|
||||
.email($t('authentication.emailValidErrorTip')),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: string) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('reset email:', value);
|
||||
@@ -16,8 +35,8 @@ function handleSubmit(value: string) {
|
||||
|
||||
<template>
|
||||
<AuthenticationForgetPassword
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,18 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import { AuthenticationLogin } from '@vben/common-ui';
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { BasicOption } from '@vben/types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
||||
{
|
||||
label: '超级管理员',
|
||||
value: 'vben',
|
||||
},
|
||||
{
|
||||
label: '管理员',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: '用户',
|
||||
value: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
options: MOCK_USER_OPTIONS,
|
||||
placeholder: $t('authentication.selectAccount'),
|
||||
},
|
||||
fieldName: 'selectAccount',
|
||||
label: $t('authentication.selectAccount'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.selectAccount') })
|
||||
.optional()
|
||||
.default('vben'),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
dependencies: {
|
||||
trigger(values, form) {
|
||||
if (values.selectAccount) {
|
||||
const findUser = MOCK_USER_OPTIONS.find(
|
||||
(item) => item.value === values.selectAccount,
|
||||
);
|
||||
if (findUser) {
|
||||
form.setValues({
|
||||
password: '123456',
|
||||
username: findUser.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
triggerFields: ['selectAccount'],
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="authStore.loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,15 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, h, ref } from 'vue';
|
||||
|
||||
import { AuthenticationRegister } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'Register' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
passwordStrength: true,
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
renderComponentContent() {
|
||||
return {
|
||||
strengthText: () => $t('authentication.passwordStrength'),
|
||||
};
|
||||
},
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.confirmPassword'),
|
||||
},
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.refine((value) => value === password, {
|
||||
message: $t('authentication.confirmPasswordTip'),
|
||||
});
|
||||
},
|
||||
triggerFields: ['password'],
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('authentication.confirmPassword'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenCheckbox',
|
||||
fieldName: 'agreePolicy',
|
||||
renderComponentContent: () => ({
|
||||
default: () =>
|
||||
h('span', [
|
||||
$t('authentication.agree'),
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
class:
|
||||
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
|
||||
href: '',
|
||||
},
|
||||
[
|
||||
$t('authentication.privacyPolicy'),
|
||||
'&',
|
||||
$t('authentication.terms'),
|
||||
],
|
||||
),
|
||||
]),
|
||||
}),
|
||||
rules: z.boolean().refine((value) => !!value, {
|
||||
message: $t('authentication.agreeTip'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: LoginAndRegisterParams) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('register submit:', value);
|
||||
@@ -18,8 +94,8 @@ function handleSubmit(value: LoginAndRegisterParams) {
|
||||
|
||||
<template>
|
||||
<AuthenticationRegister
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -55,12 +55,27 @@ onMounted(() => {
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
// xAxis: {
|
||||
// axisTick: {
|
||||
// show: false,
|
||||
// },
|
||||
// boundaryGap: false,
|
||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
// type: 'category',
|
||||
// },
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 1,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
@@ -69,7 +84,10 @@ onMounted(() => {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
|
@@ -214,7 +214,11 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||
<WorkbenchQuickNav
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
<AnalyticsVisitsSource />
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/web-naive",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -43,7 +43,7 @@
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"naive-ui": "^2.39.0",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.5.4",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
98
apps/web-naive/src/adapter/form.ts
Normal file
98
apps/web-naive/src/adapter/form.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import type {
|
||||
BaseFormComponentType,
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NCheckboxGroup,
|
||||
NDatePicker,
|
||||
NDivider,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NRadioGroup,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSwitch,
|
||||
NTimePicker,
|
||||
NTreeSelect,
|
||||
NUpload,
|
||||
} from 'naive-ui';
|
||||
// 业务表单组件适配
|
||||
|
||||
export type FormComponentType =
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'Divider'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'RadioGroup'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
// 初始化表单组件,并注册到form组件内部
|
||||
setupVbenForm<FormComponentType>({
|
||||
components: {
|
||||
Checkbox: NCheckbox,
|
||||
CheckboxGroup: NCheckboxGroup,
|
||||
DatePicker: NDatePicker,
|
||||
// 自定义默认的重置按钮
|
||||
DefaultResetActionButton: (props, { attrs, slots }) => {
|
||||
return h(NButton, { ...props, attrs, text: false, type: 'info' }, slots);
|
||||
},
|
||||
// 自定义默认的提交按钮
|
||||
DefaultSubmitActionButton: (props, { attrs, slots }) => {
|
||||
return h(
|
||||
NButton,
|
||||
{ ...props, attrs, text: false, type: 'primary' },
|
||||
slots,
|
||||
);
|
||||
},
|
||||
Divider: NDivider,
|
||||
Input: NInput,
|
||||
InputNumber: NInputNumber,
|
||||
RadioGroup: NRadioGroup,
|
||||
Select: NSelect,
|
||||
Space: NSpace,
|
||||
Switch: NSwitch,
|
||||
TimePicker: NTimePicker,
|
||||
TreeSelect: NTreeSelect,
|
||||
Upload: NUpload,
|
||||
},
|
||||
config: {
|
||||
baseModelPropName: 'value',
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
Upload: 'fileList',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
required: (value, _params, ctx) => {
|
||||
if ((!value && value !== 0) || value.length === 0) {
|
||||
return $t('formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<FormComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<FormComponentType>;
|
||||
export type { VbenFormProps };
|
1
apps/web-naive/src/adapter/index.ts
Normal file
1
apps/web-naive/src/adapter/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './form';
|
23
apps/web-naive/src/layouts/auth.vue
Normal file
23
apps/web-naive/src/layouts/auth.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthPageLayout } from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const appName = computed(() => preferences.app.name);
|
||||
const logo = computed(() => preferences.logo.source);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthPageLayout
|
||||
:app-name="appName"
|
||||
:logo="logo"
|
||||
:page-description="$t('authentication.pageDesc')"
|
||||
:page-title="$t('authentication.pageTitle')"
|
||||
>
|
||||
<!-- 自定义工具栏 -->
|
||||
<!-- <template #toolbar></template> -->
|
||||
</AuthPageLayout>
|
||||
</template>
|
@@ -13,11 +13,12 @@ import {
|
||||
UserDropdown,
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { openWindow } from '@vben/utils';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
import { useAuthStore } from '#/store';
|
||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||
|
||||
const notifications = ref<NotificationItem[]>([
|
||||
{
|
||||
@@ -87,8 +88,6 @@ const menus = computed(() => [
|
||||
},
|
||||
]);
|
||||
|
||||
const { loginLoading } = storeToRefs(authStore);
|
||||
|
||||
const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
@@ -130,11 +129,9 @@ function handleMakeAll() {
|
||||
<AuthenticationLoginExpiredModal
|
||||
v-model:open="accessStore.loginExpired"
|
||||
:avatar
|
||||
:loading="loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
>
|
||||
<LoginForm />
|
||||
</AuthenticationLoginExpiredModal>
|
||||
</template>
|
||||
<template #lock-screen>
|
||||
<LockScreen :avatar @to-login="handleLogout" />
|
||||
|
@@ -1,8 +1,6 @@
|
||||
const BasicLayout = () => import('./basic.vue');
|
||||
const AuthPageLayout = () => import('./auth.vue');
|
||||
|
||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||
|
||||
const AuthPageLayout = () =>
|
||||
import('@vben/layouts').then((m) => m.AuthPageLayout);
|
||||
|
||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||
|
@@ -1,15 +1,49 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginCodeParams } from '@vben/common-ui';
|
||||
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.mobile'),
|
||||
},
|
||||
fieldName: 'phoneNumber',
|
||||
label: $t('authentication.mobile'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.mobileTip') })
|
||||
.refine((v) => /^\d{11}$/.test(v), {
|
||||
message: $t('authentication.mobileErrortip'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenPinInput',
|
||||
componentProps: {
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
? $t('authentication.sendText', [countdown])
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
placeholder: $t('authentication.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('authentication.code'),
|
||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
||||
},
|
||||
];
|
||||
});
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
@@ -23,8 +57,8 @@ async function handleLogin(values: LoginCodeParams) {
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,13 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { AuthenticationForgetPassword } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'ForgetPassword' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: 'example@example.com',
|
||||
},
|
||||
fieldName: 'email',
|
||||
label: $t('authentication.email'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.emailTip') })
|
||||
.email($t('authentication.emailValidErrorTip')),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: string) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('reset email:', value);
|
||||
@@ -16,8 +35,8 @@ function handleSubmit(value: string) {
|
||||
|
||||
<template>
|
||||
<AuthenticationForgetPassword
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,18 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import { AuthenticationLogin } from '@vben/common-ui';
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { BasicOption } from '@vben/types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthenticationLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const MOCK_USER_OPTIONS: BasicOption[] = [
|
||||
{
|
||||
label: '超级管理员',
|
||||
value: 'vben',
|
||||
},
|
||||
{
|
||||
label: '管理员',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: '用户',
|
||||
value: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
options: MOCK_USER_OPTIONS,
|
||||
placeholder: $t('authentication.selectAccount'),
|
||||
},
|
||||
fieldName: 'selectAccount',
|
||||
label: $t('authentication.selectAccount'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.selectAccount') })
|
||||
.optional()
|
||||
.default('vben'),
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
dependencies: {
|
||||
trigger(values, form) {
|
||||
if (values.selectAccount) {
|
||||
const findUser = MOCK_USER_OPTIONS.find(
|
||||
(item) => item.value === values.selectAccount,
|
||||
);
|
||||
if (findUser) {
|
||||
form.setValues({
|
||||
password: '123456',
|
||||
username: findUser.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
triggerFields: ['selectAccount'],
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="authStore.loginLoading"
|
||||
password-placeholder="123456"
|
||||
username-placeholder="vben"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -1,15 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LoginAndRegisterParams } from '@vben/common-ui';
|
||||
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, h, ref } from 'vue';
|
||||
|
||||
import { AuthenticationRegister } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'Register' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
passwordStrength: true,
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
renderComponentContent() {
|
||||
return {
|
||||
strengthText: () => $t('authentication.passwordStrength'),
|
||||
};
|
||||
},
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.confirmPassword'),
|
||||
},
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.refine((value) => value === password, {
|
||||
message: $t('authentication.confirmPasswordTip'),
|
||||
});
|
||||
},
|
||||
triggerFields: ['password'],
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('authentication.confirmPassword'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenCheckbox',
|
||||
fieldName: 'agreePolicy',
|
||||
renderComponentContent: () => ({
|
||||
default: () =>
|
||||
h('span', [
|
||||
$t('authentication.agree'),
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
class:
|
||||
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
|
||||
href: '',
|
||||
},
|
||||
[
|
||||
$t('authentication.privacyPolicy'),
|
||||
'&',
|
||||
$t('authentication.terms'),
|
||||
],
|
||||
),
|
||||
]),
|
||||
}),
|
||||
rules: z.boolean().refine((value) => !!value, {
|
||||
message: $t('authentication.agreeTip'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: LoginAndRegisterParams) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('register submit:', value);
|
||||
@@ -18,8 +94,8 @@ function handleSubmit(value: LoginAndRegisterParams) {
|
||||
|
||||
<template>
|
||||
<AuthenticationRegister
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
:login-path="LOGIN_PATH"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -55,12 +55,27 @@ onMounted(() => {
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
// xAxis: {
|
||||
// axisTick: {
|
||||
// show: false,
|
||||
// },
|
||||
// boundaryGap: false,
|
||||
// data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
// type: 'category',
|
||||
// },
|
||||
xAxis: {
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: false,
|
||||
data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'solid',
|
||||
width: 1,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
type: 'category',
|
||||
},
|
||||
yAxis: [
|
||||
@@ -69,7 +84,10 @@ onMounted(() => {
|
||||
show: false,
|
||||
},
|
||||
max: 80_000,
|
||||
|
||||
splitArea: {
|
||||
show: true,
|
||||
},
|
||||
splitNumber: 4,
|
||||
type: 'value',
|
||||
},
|
||||
],
|
||||
|
@@ -214,7 +214,11 @@ const trendItems: WorkbenchTrendItem[] = [
|
||||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
|
||||
</div>
|
||||
<div class="w-full lg:w-2/5">
|
||||
<WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
|
||||
<WorkbenchQuickNav
|
||||
:items="quickNavItems"
|
||||
class="mt-5 lg:mt-0"
|
||||
title="快捷导航"
|
||||
/>
|
||||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
|
||||
<AnalysisChartCard class="mt-5" title="访问来源">
|
||||
<AnalyticsVisitsSource />
|
||||
|
@@ -160,6 +160,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
||||
link: 'common-ui/vben-drawer',
|
||||
text: 'Vben Drawer 抽屉',
|
||||
},
|
||||
{
|
||||
link: 'common-ui/vben-form',
|
||||
text: 'Vben Form 表单',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/docs",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vitepress build",
|
||||
@@ -11,15 +11,15 @@
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/styles": "workspace:*",
|
||||
"lucide-vue-next": "^0.436.0",
|
||||
"lucide-vue-next": "^0.439.0",
|
||||
"medium-zoom": "^1.1.0",
|
||||
"radix-vue": "^1.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nolebase/vitepress-plugin-git-changelog": "^2.4.0",
|
||||
"@nolebase/vitepress-plugin-git-changelog": "^2.5.0",
|
||||
"@vben/vite-config": "workspace:*",
|
||||
"@vite-pwa/vitepress": "^0.5.0",
|
||||
"@vite-pwa/vitepress": "^0.5.3",
|
||||
"vitepress": "^1.3.4",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.5.4"
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,14 @@
|
||||
|
||||
社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
|
||||
|
||||
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐,主要提供问题解答,分享经验等。
|
||||
- QQ群:[1群(满)](https://qm.qq.com/q/YacMHPYAMu)、[2群(满)](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),主要使用者的交流群。
|
||||
- [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
|
||||
- QQ群:[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),[老群](https://qm.qq.com/q/MEmHoCLbG0),主要使用者的交流群。
|
||||
- [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
|
||||
|
||||
::: tip
|
||||
|
||||
免费QQ群人数上限200,将会不定期清理。推荐加入QQ频道进行交流
|
||||
|
||||
## 微信群
|
||||
|
||||
::: tip
|
||||
|
@@ -74,6 +74,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
|
||||
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
|
||||
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
|
||||
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
|
||||
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
|
||||
| class | modal的class,宽度通过这个配置 | `string` | - |
|
||||
| contentClass | modal内容区域的class | `string` | - |
|
||||
| footerClass | modal底部区域的class | `string` | - |
|
||||
|
11
docs/src/components/common-ui/vben-form.md
Normal file
11
docs/src/components/common-ui/vben-form.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Vben Form 表单
|
||||
|
||||
框架提供的表单组件,可适配 `Element Plus`、`Ant Design Vue`、`Naive`UI 框架。
|
||||
|
||||
# 使用
|
||||
|
||||
TODO
|
@@ -84,6 +84,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
| closeOnPressEscape | esc 关闭弹窗 | `boolean` | `true` |
|
||||
| confirmText | 确认按钮文本 | `string\|slot` | `确认` |
|
||||
| cancelText | 取消按钮文本 | `string\|slot` | `取消` |
|
||||
| showCancelButton | 显示取消按钮 | `boolean` | `true` |
|
||||
| showConfirmButton | 显示确认按钮文本 | `boolean` | `true` |
|
||||
| class | modal的class,宽度通过这个配置 | `string` | - |
|
||||
| contentClass | modal内容区域的class | `string` | - |
|
||||
| footerClass | modal底部区域的class | `string` | - |
|
||||
|
@@ -240,7 +240,7 @@ const defaultPreferences: Preferences = {
|
||||
theme: {
|
||||
builtinType: 'default',
|
||||
colorDestructive: 'hsl(348 100% 61%)',
|
||||
colorPrimary: 'hsl(231 98% 65%)',
|
||||
colorPrimary: 'hsl(212 100% 45%)',
|
||||
colorSuccess: 'hsl(144 57% 58%)',
|
||||
colorWarning: 'hsl(42 84% 61%)',
|
||||
mode: 'dark',
|
||||
|
@@ -35,7 +35,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
loginExpiredMode: 'model',
|
||||
loginExpiredMode: 'modal',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
@@ -18,7 +18,7 @@ You just need to configure the `props` parameter of `AuthPageLayout` in `src/rou
|
||||
pageTitle: "开箱即用的大型中后台管理系统",
|
||||
pageDescription: "工程化、高性能、跨组件库的前端模版",
|
||||
toolbar: true,
|
||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
||||
toolbarList: ['color', 'language', 'layout', 'theme'],
|
||||
}
|
||||
// ...
|
||||
},
|
||||
@@ -61,11 +61,6 @@ If you want to adjust the content of the login form, you can configure the `Auth
|
||||
*/
|
||||
loading?: boolean;
|
||||
|
||||
/**
|
||||
* @en Password placeholder
|
||||
*/
|
||||
passwordPlaceholder?: string;
|
||||
|
||||
/**
|
||||
* @en QR code login path
|
||||
*/
|
||||
@@ -114,11 +109,6 @@ If you want to adjust the content of the login form, you can configure the `Auth
|
||||
* @en Login box title
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* @en Username placeholder
|
||||
*/
|
||||
usernamePlaceholder?: string;
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -36,7 +36,7 @@ You can check the list below to understand all the available variables.
|
||||
--background: 0 0% 100%;
|
||||
|
||||
/* Main area background color */
|
||||
--background-deep: 210 11.11% 96.47%;
|
||||
--background-deep: 216 20.11% 95.47%;
|
||||
--foreground: 210 6% 21%;
|
||||
|
||||
/* Background color for <Card /> */
|
||||
@@ -53,7 +53,7 @@ You can check the list below to understand all the available variables.
|
||||
|
||||
/* Theme Colors */
|
||||
|
||||
--primary: 231 98% 65%;
|
||||
--primary: 212 100% 45%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||
@@ -111,7 +111,7 @@ You can check the list below to understand all the available variables.
|
||||
|
||||
/* menu */
|
||||
--sidebar: 0 0% 100%;
|
||||
--sidebar-deep: 210 11.11% 96.47%;
|
||||
--sidebar-deep: 216 20.11% 95.47%;
|
||||
--menu: var(--sidebar);
|
||||
|
||||
/* header */
|
||||
@@ -264,7 +264,7 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
// Error color
|
||||
colorDestructive: 'hsl(348 100% 61%)',
|
||||
// Primary color
|
||||
colorPrimary: 'hsl(231 98% 65%)',
|
||||
colorPrimary: 'hsl(212 100% 45%)',
|
||||
// Success color
|
||||
colorSuccess: 'hsl(144 57% 58%)',
|
||||
// Warning color
|
||||
@@ -330,7 +330,7 @@ type BuiltinThemeType =
|
||||
--background: 0 0% 100%;
|
||||
|
||||
/* Main area background color */
|
||||
--background-deep: 210 11.11% 96.47%;
|
||||
--background-deep: 216 20.11% 95.47%;
|
||||
--foreground: 222 84% 5%;
|
||||
|
||||
/* Background color for <Card /> */
|
||||
@@ -351,7 +351,7 @@ type BuiltinThemeType =
|
||||
|
||||
/* Theme Colors */
|
||||
|
||||
--primary: 231 98% 65%;
|
||||
--primary: 212 100% 45%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||
|
@@ -262,7 +262,7 @@ const defaultPreferences: Preferences = {
|
||||
theme: {
|
||||
builtinType: 'default',
|
||||
colorDestructive: 'hsl(348 100% 61%)',
|
||||
colorPrimary: 'hsl(231 98% 65%)',
|
||||
colorPrimary: 'hsl(212 100% 45%)',
|
||||
colorSuccess: 'hsl(144 57% 58%)',
|
||||
colorWarning: 'hsl(42 84% 61%)',
|
||||
mode: 'dark',
|
||||
|
@@ -35,7 +35,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
loginExpiredMode: 'model',
|
||||
loginExpiredMode: 'modal',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
@@ -8,28 +8,21 @@
|
||||
|
||||

|
||||
|
||||
只需要在应用下的 `src/router/routes/core.ts` 内,配置`AuthPageLayout`的 `props`参数即可:
|
||||
只需要在应用下的 `src/layouts/auth.vue` 内,配置`AuthPageLayout`的 `props`参数即可:
|
||||
|
||||
```ts {4-8}
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
props: {
|
||||
sloganImage: "xxx/xxx.png",
|
||||
pageTitle: "开箱即用的大型中后台管理系统",
|
||||
pageDescription: "工程化、高性能、跨组件库的前端模版",
|
||||
toolbar: true,
|
||||
toolbarList: () => ['color', 'language', 'layout', 'theme'],
|
||||
}
|
||||
// ...
|
||||
},
|
||||
```vue {2-7}
|
||||
<AuthPageLayout
|
||||
:copyright="true"
|
||||
:toolbar="true"
|
||||
:toolbarList="['color', 'language', 'layout', 'theme']"
|
||||
:app-name="appName"
|
||||
:logo="logo"
|
||||
:page-description="$t('authentication.pageDesc')"
|
||||
:page-title="$t('authentication.pageTitle')"
|
||||
>
|
||||
</AuthPageLayout>
|
||||
```
|
||||
|
||||
::: tip
|
||||
|
||||
如果这些配置不能满足你的需求,你可以自行实现登录页面。直接实现自己的 `AuthPageLayout`即可。
|
||||
|
||||
:::
|
||||
|
||||
## 登录表单调整
|
||||
|
||||
如果你想调整登录表单的相关内容,你可以在应用下的 `src/views/_core/authentication/login.vue` 内,配置`AuthenticationLogin` 组件参数即可:
|
||||
@@ -61,11 +54,6 @@
|
||||
*/
|
||||
loading?: boolean;
|
||||
|
||||
/**
|
||||
* @zh_CN 密码占位符
|
||||
*/
|
||||
passwordPlaceholder?: string;
|
||||
|
||||
/**
|
||||
* @zh_CN 二维码登录路径
|
||||
*/
|
||||
@@ -115,10 +103,6 @@
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* @zh_CN 用户名占位符
|
||||
*/
|
||||
usernamePlaceholder?: string;
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -36,7 +36,7 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
|
||||
--background: 0 0% 100%;
|
||||
|
||||
/* 主体区域背景色 */
|
||||
--background-deep: 210 11.11% 96.47%;
|
||||
--background-deep: 216 20.11% 95.47%;
|
||||
--foreground: 210 6% 21%;
|
||||
|
||||
/* Background color for <Card /> */
|
||||
@@ -53,7 +53,7 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
|
||||
|
||||
/* 主题颜色 */
|
||||
|
||||
--primary: 231 98% 65%;
|
||||
--primary: 212 100% 45%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||
@@ -111,7 +111,7 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
|
||||
|
||||
/* menu */
|
||||
--sidebar: 0 0% 100%;
|
||||
--sidebar-deep: 210 11.11% 96.47%;
|
||||
--sidebar-deep: 216 20.11% 95.47%;
|
||||
--menu: var(--sidebar);
|
||||
|
||||
/* header */
|
||||
@@ -264,7 +264,7 @@ export const overridesPreferences = defineOverridesPreferences({
|
||||
// 错误色
|
||||
colorDestructive: 'hsl(348 100% 61%)',
|
||||
// 主题色
|
||||
colorPrimary: 'hsl(231 98% 65%)',
|
||||
colorPrimary: 'hsl(212 100% 45%)',
|
||||
// 成功色
|
||||
colorSuccess: 'hsl(144 57% 58%)',
|
||||
// 警告色
|
||||
@@ -330,7 +330,7 @@ type BuiltinThemeType =
|
||||
--background: 0 0% 100%;
|
||||
|
||||
/* 主体区域背景色 */
|
||||
--background-deep: 210 11.11% 96.47%;
|
||||
--background-deep: 216 20.11% 95.47%;
|
||||
--foreground: 222 84% 5%;
|
||||
|
||||
/* Background color for <Card /> */
|
||||
@@ -351,7 +351,7 @@ type BuiltinThemeType =
|
||||
|
||||
/* 主题颜色 */
|
||||
|
||||
--primary: 231 98% 65%;
|
||||
--primary: 212 100% 45%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||
|
@@ -10,7 +10,7 @@ outline: deep
|
||||
|
||||
在启动项目前,你需要确保你的环境满足以下要求:
|
||||
|
||||
- [Node.js](https://nodejs.org/en) 20 及以上版本,推荐使用 [fnm](https://github.com/Schniz/fnm) 或者 [nvm](https://github.com/nvm-sh/nvm) 进行版本管理。
|
||||
- [Node.js](https://nodejs.org/en) 20.15.0 及以上版本,推荐使用 [fnm](https://github.com/Schniz/fnm) 或者 [nvm](https://github.com/nvm-sh/nvm) 进行版本管理。
|
||||
- [Git](https://git-scm.com/) 任意版本。
|
||||
|
||||
验证你的环境是否满足以上要求,你可以通过以下命令查看版本:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/commitlint-config",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
|
@@ -27,29 +27,29 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-config-turbo": "^2.1.0",
|
||||
"eslint-plugin-command": "^0.2.3",
|
||||
"eslint-plugin-import-x": "^4.1.0"
|
||||
"eslint-config-turbo": "^2.1.1",
|
||||
"eslint-plugin-command": "^0.2.4",
|
||||
"eslint-plugin-import-x": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@eslint/js": "^9.10.0",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"eslint": "^9.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
||||
"@typescript-eslint/parser": "^8.5.0",
|
||||
"eslint": "^9.10.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-jsdoc": "^50.2.2",
|
||||
"eslint-plugin-jsonc": "^2.16.0",
|
||||
"eslint-plugin-n": "^17.10.2",
|
||||
"eslint-plugin-no-only-tests": "^3.3.0",
|
||||
"eslint-plugin-perfectionist": "^3.3.0",
|
||||
"eslint-plugin-perfectionist": "^3.5.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-regexp": "^2.6.0",
|
||||
"eslint-plugin-unicorn": "^55.0.0",
|
||||
"eslint-plugin-unused-imports": "^4.1.3",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"globals": "^15.9.0",
|
||||
"jsonc-eslint-parser": "^2.4.0",
|
||||
"vue-eslint-parser": "^9.4.3"
|
||||
|
@@ -15,12 +15,14 @@ export async function unicorn(): Promise<Linter.Config[]> {
|
||||
rules: {
|
||||
...pluginUnicorn.configs.recommended.rules,
|
||||
|
||||
'unicorn/better-regex': 'off',
|
||||
'unicorn/consistent-destructuring': 'off',
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
'unicorn/filename-case': 'off',
|
||||
'unicorn/import-style': 'off',
|
||||
'unicorn/no-array-for-each': 'off',
|
||||
'unicorn/no-null': 'off',
|
||||
'unicorn/no-useless-undefined': 'off',
|
||||
'unicorn/prefer-at': 'off',
|
||||
'unicorn/prefer-dom-node-text-content': 'off',
|
||||
'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }],
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/stylelint-config",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
@@ -28,7 +28,7 @@
|
||||
"stylelint-scss": "^6.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss": "^8.4.41",
|
||||
"postcss": "^8.4.45",
|
||||
"postcss-html": "^1.7.0",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^3.3.3",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/node-utils",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@changesets/git": "^3.0.0",
|
||||
"@changesets/git": "^3.0.1",
|
||||
"@manypkg/get-packages": "^2.2.2",
|
||||
"chalk": "^5.3.0",
|
||||
"consola": "^3.2.3",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tailwind-config",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
@@ -46,16 +46,16 @@
|
||||
"tailwindcss": "^3.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/json": "^2.2.242",
|
||||
"@iconify/json": "^2.2.246",
|
||||
"@iconify/tailwind": "^1.1.3",
|
||||
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cssnano": "^7.0.5",
|
||||
"postcss": "^8.4.41",
|
||||
"cssnano": "^7.0.6",
|
||||
"postcss": "^8.4.45",
|
||||
"postcss-antd-fixes": "^0.2.0",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-preset-env": "^10.0.2",
|
||||
"postcss-preset-env": "^10.0.3",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/tsconfig",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
@@ -20,6 +20,6 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@vben/types": "workspace:*",
|
||||
"vite": "^5.4.2"
|
||||
"vite": "^5.4.3"
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben/vite-config",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@jspm/generator": "^2.1.3",
|
||||
"@jspm/generator": "^2.3.0",
|
||||
"archiver": "^7.0.1",
|
||||
"cheerio": "1.0.0",
|
||||
"get-port": "^7.1.0",
|
||||
@@ -36,23 +36,23 @@
|
||||
"nitropack": "^2.9.7",
|
||||
"resolve.exports": "^2.0.2",
|
||||
"vite-plugin-lib-inject-css": "^2.1.1",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-vue-devtools": "^7.3.9"
|
||||
"vite-plugin-pwa": "^0.20.5",
|
||||
"vite-plugin-vue-devtools": "^7.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/html-minifier-terser": "^7.0.2",
|
||||
"@vben/node-utils": "workspace:*",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"@vitejs/plugin-vue": "^5.1.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.5",
|
||||
"rollup": "^4.21.1",
|
||||
"rollup": "^4.21.2",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.77.8",
|
||||
"vite": "^5.4.2",
|
||||
"sass": "^1.78.0",
|
||||
"vite": "^5.4.3",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-dts": "4.0.3",
|
||||
"vite-plugin-dts": "4.2.1",
|
||||
"vite-plugin-html": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
@@ -81,7 +81,11 @@ function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) {
|
||||
port,
|
||||
warmup: {
|
||||
// 预热文件
|
||||
clientFiles: ['./index.html', './src/{views,layouts,router,store}/*'],
|
||||
clientFiles: [
|
||||
'./index.html',
|
||||
'./bootstrap.ts',
|
||||
'./src/{views,layouts,router,store,api}/*',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -3,6 +3,7 @@ import type { PluginOption } from 'vite';
|
||||
import type { ArchiverPluginOptions } from '../typing';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import fsp from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import archiver from 'archiver';
|
||||
@@ -18,7 +19,14 @@ export const viteArchiverPlugin = (
|
||||
|
||||
setTimeout(async () => {
|
||||
const folderToZip = 'dist';
|
||||
const zipOutputPath = join(process.cwd(), outputDir, `${name}.zip`);
|
||||
|
||||
const zipOutputDir = join(process.cwd(), outputDir);
|
||||
const zipOutputPath = join(zipOutputDir, `${name}.zip`);
|
||||
try {
|
||||
await fsp.mkdir(zipOutputDir, { recursive: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
await zipFolder(folderToZip, zipOutputPath);
|
||||
|
25
package.json
25
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vben-admin-pro",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"monorepo",
|
||||
@@ -62,10 +62,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.5.0",
|
||||
"@changesets/cli": "^2.27.7",
|
||||
"@ls-lint/ls-lint": "^2.2.3",
|
||||
"@changesets/cli": "^2.27.8",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^22.5.1",
|
||||
"@types/node": "^22.5.4",
|
||||
"@vben/commitlint-config": "workspace:*",
|
||||
"@vben/eslint-config": "workspace:*",
|
||||
"@vben/prettier-config": "workspace:*",
|
||||
@@ -75,7 +74,7 @@
|
||||
"@vben/turbo-run": "workspace:*",
|
||||
"@vben/vite-config": "workspace:*",
|
||||
"@vben/vsh": "workspace:*",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"@vitejs/plugin-vue": "^5.1.3",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
@@ -84,22 +83,22 @@
|
||||
"husky": "^9.1.5",
|
||||
"is-ci": "^3.0.1",
|
||||
"jsdom": "^25.0.0",
|
||||
"lint-staged": "^15.2.9",
|
||||
"lint-staged": "^15.2.10",
|
||||
"rimraf": "^6.0.1",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"turbo": "^2.1.0",
|
||||
"typescript": "^5.5.4",
|
||||
"turbo": "^2.1.1",
|
||||
"typescript": "^5.6.2",
|
||||
"unbuild": "^2.0.0",
|
||||
"vite": "^5.4.2",
|
||||
"vite": "^5.4.3",
|
||||
"vitest": "^2.0.5",
|
||||
"vue": "^3.4.38",
|
||||
"vue-tsc": "^2.0.29"
|
||||
"vue": "^3.5.4",
|
||||
"vue-tsc": "^2.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
"pnpm": ">=9"
|
||||
},
|
||||
"packageManager": "pnpm@9.9.0",
|
||||
"packageManager": "pnpm@9.10.0",
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
@@ -110,7 +109,7 @@
|
||||
"@ctrl/tinycolor": "4.1.0",
|
||||
"clsx": "2.1.1",
|
||||
"pinia": "2.2.2",
|
||||
"vue": "3.4.38"
|
||||
"vue": "3.5.3"
|
||||
},
|
||||
"neverBuiltDependencies": [
|
||||
"canvas",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/design",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
|
@@ -23,12 +23,15 @@
|
||||
scroll-behavior: smooth;
|
||||
text-rendering: optimizelegibility;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
/* -webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale; */
|
||||
}
|
||||
|
||||
#app,
|
||||
body,
|
||||
html {
|
||||
@apply !pointer-events-auto size-full overscroll-none;
|
||||
@apply size-full overscroll-none;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -136,7 +139,7 @@
|
||||
}
|
||||
|
||||
.card-box {
|
||||
@apply bg-card text-card-foreground border-border rounded-xl border shadow;
|
||||
@apply bg-card text-card-foreground border-border rounded-xl border;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
--background: 0 0% 100%;
|
||||
|
||||
/* 主体区域背景色 */
|
||||
--background-deep: 210 11.11% 96.47%;
|
||||
--background-deep: 216 20.11% 95.47%;
|
||||
--foreground: 210 6% 21%;
|
||||
|
||||
/* Background color for <Card /> */
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
/* 主题颜色 */
|
||||
|
||||
--primary: 231 98% 65%;
|
||||
--primary: 212 100% 45%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
/* Used for destructive actions such as <Button variant="destructive"> */
|
||||
@@ -77,7 +77,8 @@
|
||||
/* ============= custom ============= */
|
||||
|
||||
/* 遮罩颜色 */
|
||||
--overlay: 0 0% 0% / 30%;
|
||||
--overlay: 0 0% 0% / 45%;
|
||||
--overlay-light: 0 0% 95% / 45%;
|
||||
|
||||
/* 基本文字大小 */
|
||||
--font-size-base: 16px;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/icons",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"lucide-vue-next": "^0.436.0",
|
||||
"vue": "^3.4.38"
|
||||
"lucide-vue-next": "^0.439.0",
|
||||
"vue": "^3.5.4"
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ export {
|
||||
CircleHelp,
|
||||
Copy,
|
||||
CornerDownLeft,
|
||||
Disc as IconDefault,
|
||||
Ellipsis,
|
||||
Expand,
|
||||
ExternalLink,
|
||||
@@ -35,6 +34,7 @@ export {
|
||||
LogOut,
|
||||
MailCheck,
|
||||
Maximize,
|
||||
Menu as IconDefault,
|
||||
Menu,
|
||||
Minimize,
|
||||
Minimize2,
|
||||
|
@@ -4,7 +4,6 @@ export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
'src/index',
|
||||
'src/store',
|
||||
'src/constants/index',
|
||||
'src/utils/index',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/shared",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -17,14 +17,7 @@
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
},
|
||||
"./constants": {
|
||||
"types": "./src/constants/index.ts",
|
||||
"development": "./src/constants/index.ts",
|
||||
@@ -53,16 +46,32 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
"./constants": {
|
||||
"types": "./dist/constants/index.d.ts",
|
||||
"default": "./dist/constants/index.mjs"
|
||||
},
|
||||
"./utils": {
|
||||
"types": "./dist/utils/index.d.ts",
|
||||
"default": "./dist/utils/index.mjs"
|
||||
},
|
||||
"./color": {
|
||||
"types": "./dist/color/index.d.ts",
|
||||
"default": "./dist/color/index.mjs"
|
||||
},
|
||||
"./cache": {
|
||||
"types": "./dist/cache/index.d.ts",
|
||||
"default": "./dist/cache/index.mjs"
|
||||
},
|
||||
"./store": {
|
||||
"types": "./dist/store/index.d.ts",
|
||||
"default": "./dist/store.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
"@tanstack/vue-store": "^0.5.5",
|
||||
"@vue/shared": "^3.4.38",
|
||||
"@vue/shared": "^3.5.4",
|
||||
"clsx": "^2.1.1",
|
||||
"defu": "^6.1.4",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
@@ -1,5 +0,0 @@
|
||||
export * from './cache';
|
||||
export * from './color';
|
||||
export * from './constants';
|
||||
export * from './store';
|
||||
export * from './utils';
|
@@ -0,0 +1,60 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { StateHandler } from '../state-handler';
|
||||
|
||||
describe('stateHandler', () => {
|
||||
it('should resolve when condition is set to true', async () => {
|
||||
const handler = new StateHandler();
|
||||
|
||||
// 模拟异步设置 condition 为 true
|
||||
setTimeout(() => {
|
||||
handler.setConditionTrue(); // 明确触发 condition 为 true
|
||||
}, 10);
|
||||
|
||||
// 等待条件被设置为 true
|
||||
await handler.waitForCondition();
|
||||
expect(handler.isConditionTrue()).toBe(true);
|
||||
});
|
||||
|
||||
it('should resolve immediately if condition is already true', async () => {
|
||||
const handler = new StateHandler();
|
||||
handler.setConditionTrue(); // 提前设置为 true
|
||||
|
||||
// 立即 resolve,因为 condition 已经是 true
|
||||
await handler.waitForCondition();
|
||||
expect(handler.isConditionTrue()).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject when condition is set to false after waiting', async () => {
|
||||
const handler = new StateHandler();
|
||||
|
||||
// 模拟异步设置 condition 为 false
|
||||
setTimeout(() => {
|
||||
handler.setConditionFalse(); // 明确触发 condition 为 false
|
||||
}, 10);
|
||||
|
||||
// 等待过程中,期望 Promise 被 reject
|
||||
await expect(handler.waitForCondition()).rejects.toThrow();
|
||||
expect(handler.isConditionTrue()).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset condition to false', () => {
|
||||
const handler = new StateHandler();
|
||||
handler.setConditionTrue(); // 设置为 true
|
||||
handler.reset(); // 重置为 false
|
||||
|
||||
expect(handler.isConditionTrue()).toBe(false);
|
||||
});
|
||||
|
||||
it('should resolve when condition is set to true after reset', async () => {
|
||||
const handler = new StateHandler();
|
||||
handler.reset(); // 确保初始为 false
|
||||
|
||||
setTimeout(() => {
|
||||
handler.setConditionTrue(); // 重置后设置为 true
|
||||
}, 10);
|
||||
|
||||
await handler.waitForCondition();
|
||||
expect(handler.isConditionTrue()).toBe(true);
|
||||
});
|
||||
});
|
80
packages/@core/base/shared/src/utils/__tests__/util.test.ts
Normal file
80
packages/@core/base/shared/src/utils/__tests__/util.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { bindMethods } from '../util';
|
||||
|
||||
class TestClass {
|
||||
public value: string;
|
||||
|
||||
constructor(value: string) {
|
||||
this.value = value;
|
||||
bindMethods(this); // 调用通用方法
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
setValue(newValue: string) {
|
||||
this.value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
describe('bindMethods', () => {
|
||||
it('should bind methods to the instance correctly', () => {
|
||||
const instance = new TestClass('initial');
|
||||
|
||||
// 解构方法
|
||||
const { getValue } = instance;
|
||||
|
||||
// 检查 getValue 是否能正确调用,并且 this 绑定了 instance
|
||||
expect(getValue()).toBe('initial');
|
||||
});
|
||||
|
||||
it('should bind multiple methods', () => {
|
||||
const instance = new TestClass('initial');
|
||||
|
||||
const { getValue, setValue } = instance;
|
||||
|
||||
// 检查 getValue 和 setValue 方法是否正确绑定了 this
|
||||
setValue('newValue');
|
||||
expect(getValue()).toBe('newValue');
|
||||
});
|
||||
|
||||
it('should not bind non-function properties', () => {
|
||||
const instance = new TestClass('initial');
|
||||
|
||||
// 检查普通属性是否保持原样
|
||||
expect(instance.value).toBe('initial');
|
||||
});
|
||||
|
||||
it('should not bind constructor method', () => {
|
||||
const instance = new TestClass('test');
|
||||
|
||||
// 检查 constructor 是否没有被绑定
|
||||
expect(instance.constructor.name).toBe('TestClass');
|
||||
});
|
||||
|
||||
it('should not bind getter/setter properties', () => {
|
||||
class TestWithGetterSetter {
|
||||
private _value: string = 'test';
|
||||
|
||||
constructor() {
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(newValue: string) {
|
||||
this._value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new TestWithGetterSetter();
|
||||
const { value } = instance;
|
||||
|
||||
// Getter 和 setter 不应被绑定
|
||||
expect(value).toBe('test');
|
||||
});
|
||||
});
|
@@ -5,9 +5,11 @@ export * from './inference';
|
||||
export * from './letter';
|
||||
export * from './merge';
|
||||
export * from './nprogress';
|
||||
export * from './state-handler';
|
||||
export * from './to';
|
||||
export * from './tree';
|
||||
export * from './unique';
|
||||
export * from './update-css-variables';
|
||||
export * from './util';
|
||||
export * from './window';
|
||||
export { default as cloneDeep } from 'lodash.clonedeep';
|
||||
|
50
packages/@core/base/shared/src/utils/state-handler.ts
Normal file
50
packages/@core/base/shared/src/utils/state-handler.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
export class StateHandler {
|
||||
private condition: boolean = false;
|
||||
private rejectCondition: (() => void) | null = null;
|
||||
private resolveCondition: (() => void) | null = null;
|
||||
|
||||
// 清理 resolve/reject 函数
|
||||
private clearPromises() {
|
||||
this.resolveCondition = null;
|
||||
this.rejectCondition = null;
|
||||
}
|
||||
|
||||
isConditionTrue(): boolean {
|
||||
return this.condition;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.condition = false;
|
||||
this.clearPromises();
|
||||
}
|
||||
|
||||
// 触发状态为 false 时,reject
|
||||
setConditionFalse() {
|
||||
this.condition = false;
|
||||
if (this.rejectCondition) {
|
||||
this.rejectCondition();
|
||||
this.clearPromises();
|
||||
}
|
||||
}
|
||||
|
||||
// 触发状态为 true 时,resolve
|
||||
setConditionTrue() {
|
||||
this.condition = true;
|
||||
if (this.resolveCondition) {
|
||||
this.resolveCondition();
|
||||
this.clearPromises();
|
||||
}
|
||||
}
|
||||
|
||||
// 返回一个 Promise,等待 condition 变为 true
|
||||
waitForCondition(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.condition) {
|
||||
resolve(); // 如果 condition 已经为 true,立即 resolve
|
||||
} else {
|
||||
this.resolveCondition = resolve;
|
||||
this.rejectCondition = reject;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
19
packages/@core/base/shared/src/utils/util.ts
Normal file
19
packages/@core/base/shared/src/utils/util.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export function bindMethods<T extends object>(instance: T): void {
|
||||
const prototype = Object.getPrototypeOf(instance);
|
||||
const propertyNames = Object.getOwnPropertyNames(prototype);
|
||||
|
||||
propertyNames.forEach((propertyName) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, propertyName);
|
||||
const propertyValue = instance[propertyName as keyof T];
|
||||
|
||||
if (
|
||||
typeof propertyValue === 'function' &&
|
||||
propertyName !== 'constructor' &&
|
||||
descriptor &&
|
||||
!descriptor.get &&
|
||||
!descriptor.set
|
||||
) {
|
||||
instance[propertyName as keyof T] = propertyValue.bind(instance);
|
||||
}
|
||||
});
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/typings",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -38,7 +38,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.38",
|
||||
"vue": "^3.5.4",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/composables",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -38,8 +38,8 @@
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"radix-vue": "^1.9.5",
|
||||
"sortablejs": "^1.15.2",
|
||||
"vue": "^3.4.38"
|
||||
"sortablejs": "^1.15.3",
|
||||
"vue": "^3.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sortablejs": "^1.15.8"
|
||||
|
@@ -4,9 +4,11 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import {
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT,
|
||||
CSS_VARIABLE_LAYOUT_CONTENT_WIDTH,
|
||||
} from '@vben-core/shared/constants';
|
||||
import {
|
||||
getElementVisibleRect,
|
||||
type VisibleDomRect,
|
||||
} from '@vben-core/shared';
|
||||
} from '@vben-core/shared/utils';
|
||||
|
||||
import { useCssVar, useDebounceFn } from '@vueuse/core';
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { DEFAULT_NAMESPACE } from '@vben-core/shared';
|
||||
import { DEFAULT_NAMESPACE } from '@vben-core/shared/constants';
|
||||
|
||||
/**
|
||||
* @see copy https://github.com/element-plus/element-plus/blob/dev/packages/hooks/use-namespace/index.ts
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import type { Ref } from 'vue';
|
||||
import { computed, getCurrentInstance, useAttrs, useSlots } from 'vue';
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import { computed, getCurrentInstance, unref, useAttrs, useSlots } from 'vue';
|
||||
|
||||
import {
|
||||
getFirstNonNullOrUndefined,
|
||||
kebabToCamelCase,
|
||||
} from '@vben-core/shared';
|
||||
} from '@vben-core/shared/utils';
|
||||
|
||||
/**
|
||||
* 依次从插槽、attrs、props、state 中获取值
|
||||
@@ -45,3 +45,49 @@ export function usePriorityValue<
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取state中的值(每个值都是ref)
|
||||
* @param props
|
||||
* @param state
|
||||
*/
|
||||
export function usePriorityValues<
|
||||
T extends Record<string, any>,
|
||||
S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>,
|
||||
>(props: T, state: S | undefined) {
|
||||
const result: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;
|
||||
|
||||
(Object.keys(props) as (keyof T)[]).forEach((key) => {
|
||||
result[key] = usePriorityValue(key as keyof typeof props, props, state);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取state中的值(集中在一个computed,用于透传)
|
||||
* @param props
|
||||
* @param state
|
||||
*/
|
||||
export function useForwardPriorityValues<
|
||||
T extends Record<string, any>,
|
||||
S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>,
|
||||
>(props: T, state: S | undefined) {
|
||||
const computedResult: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;
|
||||
|
||||
(Object.keys(props) as (keyof T)[]).forEach((key) => {
|
||||
computedResult[key] = usePriorityValue(
|
||||
key as keyof typeof props,
|
||||
props,
|
||||
state,
|
||||
);
|
||||
});
|
||||
|
||||
return computed(() => {
|
||||
const unwrapResult: Record<string, any> = {};
|
||||
Object.keys(props).forEach((key) => {
|
||||
unwrapResult[key] = unref(computedResult[key]);
|
||||
});
|
||||
return unwrapResult as { [K in keyof T]: T[K] };
|
||||
});
|
||||
}
|
||||
|
@@ -3,11 +3,19 @@ export type Locale = 'en-US' | 'zh-CN';
|
||||
export const messages: Record<Locale, Record<string, string>> = {
|
||||
'en-US': {
|
||||
cancel: 'Cancel',
|
||||
collapse: 'Collapse',
|
||||
confirm: 'Confirm',
|
||||
expand: 'Expand',
|
||||
reset: 'Reset',
|
||||
submit: 'Submit',
|
||||
},
|
||||
'zh-CN': {
|
||||
cancel: '取消',
|
||||
collapse: '收起',
|
||||
confirm: '确认',
|
||||
expand: '展开',
|
||||
reset: '重置',
|
||||
submit: '提交',
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vben-core/preferences",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0-beta.2",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
@@ -32,6 +32,6 @@
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.5.4"
|
||||
}
|
||||
}
|
||||
|
@@ -70,12 +70,12 @@ const defaultPreferences: Preferences = {
|
||||
expandOnHover: true,
|
||||
extraCollapse: true,
|
||||
hidden: false,
|
||||
width: 230,
|
||||
width: 224,
|
||||
},
|
||||
tabbar: {
|
||||
dragable: true,
|
||||
enable: true,
|
||||
height: 36,
|
||||
height: 38,
|
||||
keepAlive: true,
|
||||
persist: true,
|
||||
showIcon: true,
|
||||
@@ -87,7 +87,7 @@ const defaultPreferences: Preferences = {
|
||||
theme: {
|
||||
builtinType: 'default',
|
||||
colorDestructive: 'hsl(348 100% 61%)',
|
||||
colorPrimary: 'hsl(231 98% 65%)',
|
||||
colorPrimary: 'hsl(212 100% 45%)',
|
||||
colorSuccess: 'hsl(144 57% 58%)',
|
||||
colorWarning: 'hsl(42 84% 61%)',
|
||||
mode: 'dark',
|
||||
|
@@ -9,7 +9,7 @@ interface BuiltinThemePreset {
|
||||
|
||||
const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
|
||||
{
|
||||
color: 'hsl(231 98% 65%)',
|
||||
color: 'hsl(212 100% 45%)',
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
@@ -25,7 +25,7 @@ const BUILT_IN_THEME_PRESETS: BuiltinThemePreset[] = [
|
||||
type: 'yellow',
|
||||
},
|
||||
{
|
||||
color: 'hsl(212 100% 45%)',
|
||||
color: 'hsl(231 98% 65%)',
|
||||
type: 'sky-blue',
|
||||
},
|
||||
{
|
||||
|
@@ -4,7 +4,8 @@ import type { InitialOptions, Preferences } from './types';
|
||||
|
||||
import { markRaw, reactive, readonly, watch } from 'vue';
|
||||
|
||||
import { isMacOs, merge, StorageManager } from '@vben-core/shared';
|
||||
import { StorageManager } from '@vben-core/shared/cache';
|
||||
import { isMacOs, merge } from '@vben-core/shared/utils';
|
||||
|
||||
import {
|
||||
breakpointsTailwind,
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import type { Preferences } from './types';
|
||||
|
||||
import {
|
||||
updateCSSVariables as executeUpdateCSSVariables,
|
||||
generatorColorVariables,
|
||||
} from '@vben-core/shared';
|
||||
import { generatorColorVariables } from '@vben-core/shared/color';
|
||||
import { updateCSSVariables as executeUpdateCSSVariables } from '@vben-core/shared/utils';
|
||||
|
||||
import { BUILT_IN_THEME_PRESETS } from './constants';
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { diff } from '@vben-core/shared';
|
||||
import { diff } from '@vben-core/shared/utils';
|
||||
|
||||
import { preferencesManager } from './preferences';
|
||||
import { isDarkTheme } from './update-css-variables';
|
||||
|
21
packages/@core/ui-kit/form-ui/build.config.ts
Normal file
21
packages/@core/ui-kit/form-ui/build.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineBuildConfig } from 'unbuild';
|
||||
|
||||
export default defineBuildConfig({
|
||||
clean: true,
|
||||
declaration: true,
|
||||
entries: [
|
||||
{
|
||||
builder: 'mkdist',
|
||||
input: './src',
|
||||
loaders: ['vue'],
|
||||
pattern: ['**/*.vue'],
|
||||
},
|
||||
{
|
||||
builder: 'mkdist',
|
||||
format: 'esm',
|
||||
input: './src',
|
||||
loaders: ['js'],
|
||||
pattern: ['**/*.ts'],
|
||||
},
|
||||
],
|
||||
});
|
50
packages/@core/ui-kit/form-ui/package.json
Normal file
50
packages/@core/ui-kit/form-ui/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@vben-core/form-ui",
|
||||
"version": "5.2.1",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "packages/@vben-core/uikit/form-ui"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm unbuild",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"development": "./src/index.ts",
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/index.mjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/composables": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vee-validate/zod": "^4.13.2",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"vee-validate": "^4.13.2",
|
||||
"vue": "^3.5.4",
|
||||
"zod": "^3.23.8",
|
||||
"zod-defaults": "^0.1.3"
|
||||
}
|
||||
}
|
1
packages/@core/ui-kit/form-ui/postcss.config.mjs
Normal file
1
packages/@core/ui-kit/form-ui/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/tailwind-config/postcss';
|
103
packages/@core/ui-kit/form-ui/src/components/form-actions.vue
Normal file
103
packages/@core/ui-kit/form-ui/src/components/form-actions.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, toRaw, unref } from 'vue';
|
||||
|
||||
import { useSimpleLocale } from '@vben-core/composables';
|
||||
import { VbenExpandableArrow } from '@vben-core/shadcn-ui';
|
||||
import { cn, isFunction } from '@vben-core/shared/utils';
|
||||
|
||||
import { COMPONENT_MAP } from '../config';
|
||||
import { injectFormProps } from '../use-form-context';
|
||||
|
||||
const { $t } = useSimpleLocale();
|
||||
|
||||
const [rootProps, form] = injectFormProps();
|
||||
|
||||
const collapsed = defineModel({ default: false });
|
||||
|
||||
const resetButtonOptions = computed(() => {
|
||||
return {
|
||||
show: true,
|
||||
text: `${$t.value('reset')}`,
|
||||
...unref(rootProps).resetButtonOptions,
|
||||
};
|
||||
});
|
||||
|
||||
const submitButtonOptions = computed(() => {
|
||||
return {
|
||||
show: true,
|
||||
text: `${$t.value('submit')}`,
|
||||
...unref(rootProps).submitButtonOptions,
|
||||
};
|
||||
});
|
||||
|
||||
const isQueryForm = computed(() => {
|
||||
return !!unref(rootProps).showCollapseButton;
|
||||
});
|
||||
|
||||
const queryFormStyle = computed(() => {
|
||||
if (isQueryForm.value) {
|
||||
return {
|
||||
'grid-column': `-2 / -1`,
|
||||
marginLeft: 'auto',
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
const { valid } = await form.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
await unref(rootProps).handleSubmit?.(toRaw(form.values));
|
||||
}
|
||||
|
||||
async function handleReset(e: Event) {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
const props = unref(rootProps);
|
||||
if (isFunction(props.handleReset)) {
|
||||
await props.handleReset?.(form.values);
|
||||
} else {
|
||||
form.resetForm();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
:class="cn('col-span-full w-full text-right', rootProps.actionWrapperClass)"
|
||||
:style="queryFormStyle"
|
||||
>
|
||||
<component
|
||||
:is="COMPONENT_MAP.DefaultResetActionButton"
|
||||
v-if="resetButtonOptions.show"
|
||||
class="mr-3"
|
||||
type="button"
|
||||
@click="handleReset"
|
||||
v-bind="resetButtonOptions"
|
||||
>
|
||||
{{ resetButtonOptions.text }}
|
||||
</component>
|
||||
|
||||
<component
|
||||
:is="COMPONENT_MAP.DefaultSubmitActionButton"
|
||||
v-if="submitButtonOptions.show"
|
||||
type="button"
|
||||
@click="handleSubmit"
|
||||
v-bind="submitButtonOptions"
|
||||
>
|
||||
{{ submitButtonOptions.text }}
|
||||
</component>
|
||||
|
||||
<VbenExpandableArrow
|
||||
v-if="rootProps.showCollapseButton"
|
||||
v-model:model-value="collapsed"
|
||||
class="ml-2"
|
||||
>
|
||||
<span>{{ collapsed ? $t('expand') : $t('collapse') }}</span>
|
||||
</VbenExpandableArrow>
|
||||
</div>
|
||||
</template>
|
65
packages/@core/ui-kit/form-ui/src/config.ts
Normal file
65
packages/@core/ui-kit/form-ui/src/config.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { BaseFormComponentType, VbenFormAdapterOptions } from './types';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
import { h } from 'vue';
|
||||
|
||||
import {
|
||||
VbenButton,
|
||||
VbenCheckbox,
|
||||
Input as VbenInput,
|
||||
VbenInputPassword,
|
||||
VbenPinInput,
|
||||
VbenSelect,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { defineRule } from 'vee-validate';
|
||||
|
||||
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
|
||||
|
||||
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
|
||||
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
|
||||
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
|
||||
VbenCheckbox,
|
||||
VbenInput,
|
||||
VbenInputPassword,
|
||||
VbenPinInput,
|
||||
VbenSelect,
|
||||
};
|
||||
|
||||
export const COMPONENT_BIND_EVENT_MAP: Partial<
|
||||
Record<BaseFormComponentType, string>
|
||||
> = {
|
||||
VbenCheckbox: 'checked',
|
||||
};
|
||||
|
||||
export function setupVbenForm<
|
||||
T extends BaseFormComponentType = BaseFormComponentType,
|
||||
>(options: VbenFormAdapterOptions<T>) {
|
||||
const { components, config, defineRules } = options;
|
||||
|
||||
if (defineRules) {
|
||||
for (const key of Object.keys(defineRules)) {
|
||||
defineRule(key, defineRules[key as never]);
|
||||
}
|
||||
}
|
||||
|
||||
const baseModelPropName =
|
||||
config?.baseModelPropName ?? DEFAULT_MODEL_PROP_NAME;
|
||||
const modelPropNameMap = config?.modelPropNameMap as
|
||||
| Record<BaseFormComponentType, string>
|
||||
| undefined;
|
||||
|
||||
for (const component of Object.keys(components)) {
|
||||
const key = component as BaseFormComponentType;
|
||||
COMPONENT_MAP[key] = components[component as never];
|
||||
|
||||
if (baseModelPropName !== DEFAULT_MODEL_PROP_NAME) {
|
||||
COMPONENT_BIND_EVENT_MAP[key] = baseModelPropName;
|
||||
}
|
||||
|
||||
// 覆盖特殊组件的modelPropName
|
||||
if (modelPropNameMap && modelPropNameMap[key]) {
|
||||
COMPONENT_BIND_EVENT_MAP[key] = modelPropNameMap[key];
|
||||
}
|
||||
}
|
||||
}
|
175
packages/@core/ui-kit/form-ui/src/form-api.ts
Normal file
175
packages/@core/ui-kit/form-ui/src/form-api.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import type {
|
||||
FormState,
|
||||
GenericObject,
|
||||
ResetFormOpts,
|
||||
ValidationOptions,
|
||||
} from 'vee-validate';
|
||||
|
||||
import type { FormActions, VbenFormProps } from './types';
|
||||
|
||||
import { toRaw } from 'vue';
|
||||
|
||||
import { Store } from '@vben-core/shared/store';
|
||||
import { bindMethods, isFunction, StateHandler } from '@vben-core/shared/utils';
|
||||
|
||||
function getDefaultState(): VbenFormProps {
|
||||
return {
|
||||
actionWrapperClass: '',
|
||||
collapsed: false,
|
||||
collapsedRows: 1,
|
||||
commonConfig: {},
|
||||
handleReset: undefined,
|
||||
handleSubmit: undefined,
|
||||
layout: 'horizontal',
|
||||
resetButtonOptions: {},
|
||||
schema: [],
|
||||
showCollapseButton: false,
|
||||
showDefaultActions: true,
|
||||
submitButtonOptions: {},
|
||||
wrapperClass: 'grid-cols-1',
|
||||
};
|
||||
}
|
||||
|
||||
export class FormApi {
|
||||
// private prevState!: ModalState;
|
||||
private state: null | VbenFormProps = null;
|
||||
// private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
|
||||
public form = {} as FormActions;
|
||||
|
||||
isMounted = false;
|
||||
|
||||
stateHandler: StateHandler;
|
||||
|
||||
public store: Store<VbenFormProps>;
|
||||
|
||||
constructor(options: VbenFormProps = {}) {
|
||||
const { ...storeState } = options;
|
||||
|
||||
const defaultState = getDefaultState();
|
||||
|
||||
this.store = new Store<VbenFormProps>(
|
||||
{
|
||||
...defaultState,
|
||||
...storeState,
|
||||
},
|
||||
{
|
||||
onUpdate: () => {
|
||||
this.state = this.store.state;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
this.state = this.store.state;
|
||||
this.stateHandler = new StateHandler();
|
||||
bindMethods(this);
|
||||
}
|
||||
|
||||
private async getForm() {
|
||||
if (!this.isMounted) {
|
||||
// 等待form挂载
|
||||
await this.stateHandler.waitForCondition();
|
||||
}
|
||||
if (!this.form?.meta) {
|
||||
throw new Error('<VbenForm /> is not mounted');
|
||||
}
|
||||
return this.form;
|
||||
}
|
||||
|
||||
// 如果需要多次更新状态,可以使用 batch 方法
|
||||
batchStore(cb: () => void) {
|
||||
this.store.batch(cb);
|
||||
}
|
||||
|
||||
async getValues() {
|
||||
const form = await this.getForm();
|
||||
return form.values;
|
||||
}
|
||||
|
||||
mount(formActions: FormActions) {
|
||||
if (!this.isMounted) {
|
||||
Object.assign(this.form, formActions);
|
||||
this.stateHandler.setConditionTrue();
|
||||
this.isMounted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段名移除表单项
|
||||
* @param fields
|
||||
*/
|
||||
async removeSchemaByFields(fields: string[]) {
|
||||
const fieldSet = new Set(fields);
|
||||
const schema = this.state?.schema ?? [];
|
||||
|
||||
const filterSchema = schema.filter((item) => fieldSet.has(item.fieldName));
|
||||
|
||||
this.setState({
|
||||
schema: filterSchema,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
async resetForm(
|
||||
state?: Partial<FormState<GenericObject>> | undefined,
|
||||
opts?: Partial<ResetFormOpts>,
|
||||
) {
|
||||
const form = await this.getForm();
|
||||
return form.resetForm(state, opts);
|
||||
}
|
||||
|
||||
async resetValidate() {
|
||||
const form = await this.getForm();
|
||||
const fields = Object.keys(form.errors.value);
|
||||
fields.forEach((field) => {
|
||||
form.setFieldError(field, undefined);
|
||||
});
|
||||
}
|
||||
|
||||
async setFieldValue(field: string, value: any, shouldValidate?: boolean) {
|
||||
const form = await this.getForm();
|
||||
form.setFieldValue(field, value, shouldValidate);
|
||||
}
|
||||
|
||||
setState(
|
||||
stateOrFn:
|
||||
| ((prev: VbenFormProps) => Partial<VbenFormProps>)
|
||||
| Partial<VbenFormProps>,
|
||||
) {
|
||||
if (isFunction(stateOrFn)) {
|
||||
this.store.setState(stateOrFn as (prev: VbenFormProps) => VbenFormProps);
|
||||
} else {
|
||||
this.store.setState((prev) => ({ ...prev, ...stateOrFn }));
|
||||
}
|
||||
}
|
||||
|
||||
async setValues(
|
||||
fields: Record<string, any>,
|
||||
shouldValidate: boolean = false,
|
||||
) {
|
||||
const form = await this.getForm();
|
||||
form.setValues(fields, shouldValidate);
|
||||
}
|
||||
|
||||
async submitForm(e?: Event) {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
const form = await this.getForm();
|
||||
await form.submitForm();
|
||||
const rawValues = toRaw(form.values || {});
|
||||
await this.state?.handleSubmit?.(rawValues);
|
||||
return rawValues;
|
||||
}
|
||||
|
||||
unmounted() {
|
||||
this.state = null;
|
||||
this.isMounted = false;
|
||||
this.stateHandler.reset();
|
||||
}
|
||||
|
||||
async validate(opts?: Partial<ValidationOptions>) {
|
||||
const form = await this.getForm();
|
||||
return await form.validate(opts);
|
||||
}
|
||||
}
|
24
packages/@core/ui-kit/form-ui/src/form-render/context.ts
Normal file
24
packages/@core/ui-kit/form-ui/src/form-render/context.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { FormRenderProps } from '../types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { createContext } from '@vben-core/shadcn-ui';
|
||||
|
||||
export const [injectRenderFormProps, provideFormRenderProps] =
|
||||
createContext<FormRenderProps>('FormRenderProps');
|
||||
|
||||
export const useFormContext = () => {
|
||||
const formRenderProps = injectRenderFormProps();
|
||||
|
||||
const isVertical = computed(() => formRenderProps.layout === 'vertical');
|
||||
|
||||
const componentMap = computed(() => formRenderProps.componentMap);
|
||||
const componentBindEventMap = computed(
|
||||
() => formRenderProps.componentBindEventMap,
|
||||
);
|
||||
return {
|
||||
componentBindEventMap,
|
||||
componentMap,
|
||||
isVertical,
|
||||
};
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user