This commit is contained in:
dap
2024-12-17 08:01:02 +08:00
101 changed files with 853 additions and 193 deletions

View File

@@ -132,6 +132,7 @@ watch(
:text="userStore.userInfo?.realName"
description="ann.vben@gmail.com"
tag-text="Pro"
trigger="both"
@logout="handleLogout"
/>
</template>

View File

@@ -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",

View File

@@ -4,7 +4,9 @@
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
"forgetPassword": "忘记密码",
"sendingCode": "正在发送验证码",
"codeSentTo": "验证码已发送至{0}"
},
"dashboard": {
"title": "概览",

View File

@@ -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"

View File

@@ -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>

View File

@@ -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内注册并加上类型

View File

@@ -82,6 +82,7 @@ const gridOptions: VxeGridProps<RowType> = {
},
},
},
showOverflow: false,
};
const [Grid] = useVbenVxeGrid({ gridOptions });

View File

@@ -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>

View File

@@ -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,
},
};