Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -132,6 +132,7 @@ watch(
|
||||
:text="userStore.userInfo?.realName"
|
||||
description="ann.vben@gmail.com"
|
||||
tag-text="Pro"
|
||||
trigger="both"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
</template>
|
||||
|
@@ -4,7 +4,9 @@
|
||||
"register": "Register",
|
||||
"codeLogin": "Code Login",
|
||||
"qrcodeLogin": "Qr Code Login",
|
||||
"forgetPassword": "Forget Password"
|
||||
"forgetPassword": "Forget Password",
|
||||
"sendingCode": "SMS Code is sending...",
|
||||
"codeSentTo": "Code has been sent to {0}"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
|
@@ -4,7 +4,9 @@
|
||||
"register": "注册",
|
||||
"codeLogin": "验证码登录",
|
||||
"qrcodeLogin": "二维码登录",
|
||||
"forgetPassword": "忘记密码"
|
||||
"forgetPassword": "忘记密码",
|
||||
"sendingCode": "正在发送验证码",
|
||||
"codeSentTo": "验证码已发送至{0}"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "概览",
|
||||
|
@@ -2,15 +2,36 @@
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const CODE_LENGTH = 6;
|
||||
const loginRef =
|
||||
useTemplateRef<InstanceType<typeof AuthenticationCodeLogin>>('loginRef');
|
||||
function sendCodeApi(phoneNumber: string) {
|
||||
message.loading({
|
||||
content: $t('page.auth.sendingCode'),
|
||||
duration: 0,
|
||||
key: 'sending-code',
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
message.success({
|
||||
content: $t('page.auth.codeSentTo', [phoneNumber]),
|
||||
duration: 3,
|
||||
key: 'sending-code',
|
||||
});
|
||||
resolve({ code: '123456', phoneNumber });
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
@@ -30,6 +51,7 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
{
|
||||
component: 'VbenPinInput',
|
||||
componentProps: {
|
||||
codeLength: CODE_LENGTH,
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
@@ -37,11 +59,32 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
handleSendCode: async () => {
|
||||
// 模拟发送验证码
|
||||
// Simulate sending verification code
|
||||
loading.value = true;
|
||||
const formApi = loginRef.value?.getFormApi();
|
||||
if (!formApi) {
|
||||
loading.value = false;
|
||||
throw new Error('formApi is not ready');
|
||||
}
|
||||
await formApi.validateField('phoneNumber');
|
||||
const isPhoneReady = await formApi.isFieldValid('phoneNumber');
|
||||
if (!isPhoneReady) {
|
||||
loading.value = false;
|
||||
throw new Error('Phone number is not Ready');
|
||||
}
|
||||
const { phoneNumber } = await formApi.getValues();
|
||||
await sendCodeApi(phoneNumber);
|
||||
loading.value = false;
|
||||
},
|
||||
placeholder: $t('authentication.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('authentication.code'),
|
||||
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
|
||||
rules: z.string().length(CODE_LENGTH, {
|
||||
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -58,6 +101,7 @@ async function handleLogin(values: Recordable<any>) {
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
ref="loginRef"
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleLogin"
|
||||
|
@@ -1,7 +1,116 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
import { reactive } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
import { MenuBadge } from '@vben-core/menu-ui';
|
||||
|
||||
import { Button, Card, Radio, RadioGroup } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
const colors = [
|
||||
{ label: '预设:默认', value: 'default' },
|
||||
{ label: '预设:关键', value: 'destructive' },
|
||||
{ label: '预设:主要', value: 'primary' },
|
||||
{ label: '预设:成功', value: 'success' },
|
||||
{ label: '自定义', value: 'bg-gray-200 text-black' },
|
||||
];
|
||||
|
||||
const route = useRoute();
|
||||
const accessStore = useAccessStore();
|
||||
const menu = accessStore.getMenuByPath(route.path);
|
||||
const badgeProps = reactive({
|
||||
badge: menu?.badge as string,
|
||||
badgeType: menu?.badge ? 'normal' : (menu?.badgeType as 'dot' | 'normal'),
|
||||
badgeVariants: menu?.badgeVariants as string,
|
||||
});
|
||||
|
||||
const [Form] = useVbenForm({
|
||||
handleValuesChange(values) {
|
||||
badgeProps.badge = values.badge;
|
||||
badgeProps.badgeType = values.badgeType;
|
||||
badgeProps.badgeVariants = values.badgeVariants;
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{ label: '点徽标', value: 'dot' },
|
||||
{ label: '文字徽标', value: 'normal' },
|
||||
],
|
||||
optionType: 'button',
|
||||
},
|
||||
defaultValue: badgeProps.badgeType,
|
||||
fieldName: 'badgeType',
|
||||
label: '类型',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
maxLength: 4,
|
||||
placeholder: '请输入徽标内容',
|
||||
style: { width: '200px' },
|
||||
},
|
||||
defaultValue: badgeProps.badge,
|
||||
fieldName: 'badge',
|
||||
label: '徽标内容',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
defaultValue: badgeProps.badgeVariants,
|
||||
fieldName: 'badgeVariants',
|
||||
label: '颜色',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'action',
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
function updateMenuBadge() {
|
||||
if (menu) {
|
||||
menu.badge = badgeProps.badge;
|
||||
menu.badgeType = badgeProps.badgeType;
|
||||
menu.badgeVariants = badgeProps.badgeVariants;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback description="用于徽标示例" status="coming-soon" title="徽标示例" />
|
||||
<Page
|
||||
description="菜单项上可以显示徽标,这些徽标可以主动更新"
|
||||
title="菜单徽标"
|
||||
>
|
||||
<Card title="徽标更新">
|
||||
<Form>
|
||||
<template #badgeVariants="slotProps">
|
||||
<RadioGroup v-bind="slotProps">
|
||||
<Radio
|
||||
v-for="color in colors"
|
||||
:key="color.value"
|
||||
:value="color.value"
|
||||
>
|
||||
<div
|
||||
:title="color.label"
|
||||
class="flex h-[14px] w-[50px] items-center justify-start"
|
||||
>
|
||||
<MenuBadge
|
||||
v-bind="{ ...badgeProps, badgeVariants: color.value }"
|
||||
/>
|
||||
</div>
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
</template>
|
||||
<template #action>
|
||||
<Button type="primary" @click="updateMenuBadge">更新徽标</Button>
|
||||
</template>
|
||||
</Form>
|
||||
</Card>
|
||||
</Page>
|
||||
</template>
|
||||
|
@@ -16,6 +16,8 @@ const activeTab = ref('basic');
|
||||
const [BaseForm, baseFormApi] = useVbenForm({
|
||||
// 所有表单项共用,可单独在表单内覆盖
|
||||
commonConfig: {
|
||||
// 在label后显示一个冒号
|
||||
colon: true,
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
@@ -40,6 +42,7 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
||||
fieldName: 'username',
|
||||
// 界面显示的label
|
||||
label: '字符串',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||
|
@@ -82,6 +82,7 @@ const gridOptions: VxeGridProps<RowType> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
showOverflow: false,
|
||||
};
|
||||
|
||||
const [Grid] = useVbenVxeGrid({ gridOptions });
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
@@ -71,7 +71,7 @@ const formOptions: VbenFormProps = {
|
||||
submitOnEnter: false,
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps<RowType> = {
|
||||
const gridOptions: VxeTableGridOptions<RowType> = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
labelField: 'name',
|
||||
@@ -85,6 +85,7 @@ const gridOptions: VxeGridProps<RowType> = {
|
||||
{ field: 'price', title: 'Price' },
|
||||
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
|
||||
],
|
||||
exportConfig: {},
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
@@ -100,9 +101,20 @@ const gridOptions: VxeGridProps<RowType> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
toolbarConfig: {
|
||||
custom: true,
|
||||
export: true,
|
||||
refresh: true,
|
||||
resizable: true,
|
||||
search: true,
|
||||
zoom: true,
|
||||
},
|
||||
};
|
||||
|
||||
const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -25,10 +25,10 @@ const gridOptions: VxeGridProps<RowType> = {
|
||||
columns: [
|
||||
{ title: '序号', type: 'seq', width: 50 },
|
||||
{ align: 'left', title: 'Name', type: 'checkbox', width: 100 },
|
||||
{ field: 'category', title: 'Category' },
|
||||
{ field: 'color', title: 'Color' },
|
||||
{ field: 'productName', title: 'Product Name' },
|
||||
{ field: 'price', title: 'Price' },
|
||||
{ field: 'category', sortable: true, title: 'Category' },
|
||||
{ field: 'color', sortable: true, title: 'Color' },
|
||||
{ field: 'productName', sortable: true, title: 'Product Name' },
|
||||
{ field: 'price', sortable: true, title: 'Price' },
|
||||
{ field: 'releaseDate', formatter: 'formatDateTime', title: 'DateTime' },
|
||||
],
|
||||
exportConfig: {},
|
||||
@@ -36,19 +36,26 @@ const gridOptions: VxeGridProps<RowType> = {
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }) => {
|
||||
query: async ({ page, sort }) => {
|
||||
return await getExampleTableApi({
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
sortBy: sort.field,
|
||||
sortOrder: sort.order,
|
||||
});
|
||||
},
|
||||
},
|
||||
sort: true,
|
||||
},
|
||||
sortConfig: {
|
||||
defaultSort: { field: 'category', order: 'desc' },
|
||||
remote: true,
|
||||
},
|
||||
toolbarConfig: {
|
||||
custom: true,
|
||||
export: true,
|
||||
// import: true,
|
||||
refresh: true,
|
||||
refresh: { code: 'query' },
|
||||
zoom: true,
|
||||
},
|
||||
};
|
||||
|
Reference in New Issue
Block a user