Merge remote-tracking branch 'yudao/v5-next' into v5-next-tmp
commit
53d2d33ab0
|
|
@ -2,6 +2,7 @@ import type { PageResult } from '@vben/request';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
// TODO @puhui999:代码风格的统一
|
||||||
export namespace SystemMailAccountApi {
|
export namespace SystemMailAccountApi {
|
||||||
export interface MailAccountVO {
|
export interface MailAccountVO {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import type { PageResult } from '@vben/request';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
// TODO @puhui999:代码风格的统一
|
||||||
export namespace SystemMailLogApi {
|
export namespace SystemMailLogApi {
|
||||||
export interface MailLogVO {
|
export interface MailLogVO {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import type { PageResult } from '@vben/request';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
// TODO @puhui999:代码风格的统一
|
||||||
export namespace SystemMailTemplateApi {
|
export namespace SystemMailTemplateApi {
|
||||||
export interface MailTemplateVO {
|
export interface MailTemplateVO {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import type { PageResult } from '@vben/request';
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace SystemSmsChannelApi {
|
export namespace SystemSmsChannelApi {
|
||||||
/** 短信渠道信息 */
|
/** 短信渠道信息 */
|
||||||
export interface SmsChannelVO {
|
export interface SmsChannel {
|
||||||
id?: number;
|
id?: number;
|
||||||
code: string;
|
code: string;
|
||||||
status: number;
|
status: number;
|
||||||
|
|
@ -18,8 +18,8 @@ export namespace SystemSmsChannelApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询短信渠道列表 */
|
/** 查询短信渠道列表 */
|
||||||
export function getSmsChannelPage(params: any) {
|
export function getSmsChannelPage(params: PageParam) {
|
||||||
return requestClient.get<PageResult<SystemSmsChannelApi.SmsChannelVO>>(
|
return requestClient.get<PageResult<SystemSmsChannelApi.SmsChannel>>(
|
||||||
'/system/sms-channel/page',
|
'/system/sms-channel/page',
|
||||||
{ params },
|
{ params },
|
||||||
);
|
);
|
||||||
|
|
@ -27,25 +27,21 @@ export function getSmsChannelPage(params: any) {
|
||||||
|
|
||||||
/** 获得短信渠道精简列表 */
|
/** 获得短信渠道精简列表 */
|
||||||
export function getSimpleSmsChannelList() {
|
export function getSimpleSmsChannelList() {
|
||||||
return requestClient.get<SystemSmsChannelApi.SmsChannelVO[]>(
|
return requestClient.get<SystemSmsChannelApi.SmsChannel[]>('/system/sms-channel/simple-list');
|
||||||
'/system/sms-channel/simple-list',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询短信渠道详情 */
|
/** 查询短信渠道详情 */
|
||||||
export function getSmsChannel(id: number) {
|
export function getSmsChannel(id: number) {
|
||||||
return requestClient.get<SystemSmsChannelApi.SmsChannelVO>(
|
return requestClient.get<SystemSmsChannelApi.SmsChannel>(`/system/sms-channel/get?id=${id}`);
|
||||||
`/system/sms-channel/get?id=${id}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增短信渠道 */
|
/** 新增短信渠道 */
|
||||||
export function createSmsChannel(data: SystemSmsChannelApi.SmsChannelVO) {
|
export function createSmsChannel(data: SystemSmsChannelApi.SmsChannel) {
|
||||||
return requestClient.post('/system/sms-channel/create', data);
|
return requestClient.post('/system/sms-channel/create', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改短信渠道 */
|
/** 修改短信渠道 */
|
||||||
export function updateSmsChannel(data: SystemSmsChannelApi.SmsChannelVO) {
|
export function updateSmsChannel(data: SystemSmsChannelApi.SmsChannel) {
|
||||||
return requestClient.put('/system/sms-channel/update', data);
|
return requestClient.put('/system/sms-channel/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { PageResult } from '@vben/request';
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
|
@ -32,11 +32,8 @@ export namespace SystemSmsLogApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查询短信日志列表 */
|
/** 查询短信日志列表 */
|
||||||
export function getSmsLogPage(params: any) {
|
export function getSmsLogPage(params: PageParam) {
|
||||||
return requestClient.get<PageResult<SystemSmsLogApi.SmsLogVO>>(
|
return requestClient.get<PageResult<SystemSmsLogApi.SmsLogVO>>('/system/sms-log/page', { params });
|
||||||
'/system/sms-log/page',
|
|
||||||
{ params },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 导出短信日志 */
|
/** 导出短信日志 */
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace SystemSmsTemplateApi {
|
export namespace SystemSmsTemplateApi {
|
||||||
/** 短信模板信息 */
|
/** 短信模板信息 */
|
||||||
export interface SmsTemplateVO {
|
export interface SmsTemplate {
|
||||||
id?: number;
|
id?: number;
|
||||||
type?: number;
|
type?: number;
|
||||||
status: number;
|
status: number;
|
||||||
|
|
@ -20,7 +20,7 @@ export namespace SystemSmsTemplateApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送短信请求 */
|
/** 发送短信请求 */
|
||||||
export interface SendSmsReqVO {
|
export interface SmsSendReqVO {
|
||||||
mobile: string;
|
mobile: string;
|
||||||
templateCode: string;
|
templateCode: string;
|
||||||
templateParams: Record<string, any>;
|
templateParams: Record<string, any>;
|
||||||
|
|
@ -29,7 +29,7 @@ export namespace SystemSmsTemplateApi {
|
||||||
|
|
||||||
/** 查询短信模板列表 */
|
/** 查询短信模板列表 */
|
||||||
export function getSmsTemplatePage(params: any) {
|
export function getSmsTemplatePage(params: any) {
|
||||||
return requestClient.get<PageResult<SystemSmsTemplateApi.SmsTemplateVO>>(
|
return requestClient.get<PageResult<SystemSmsTemplateApi.SmsTemplate>>(
|
||||||
'/system/sms-template/page',
|
'/system/sms-template/page',
|
||||||
{ params },
|
{ params },
|
||||||
);
|
);
|
||||||
|
|
@ -37,18 +37,16 @@ export function getSmsTemplatePage(params: any) {
|
||||||
|
|
||||||
/** 查询短信模板详情 */
|
/** 查询短信模板详情 */
|
||||||
export function getSmsTemplate(id: number) {
|
export function getSmsTemplate(id: number) {
|
||||||
return requestClient.get<SystemSmsTemplateApi.SmsTemplateVO>(
|
return requestClient.get<SystemSmsTemplateApi.SmsTemplate>(`/system/sms-template/get?id=${id}`);
|
||||||
`/system/sms-template/get?id=${id}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增短信模板 */
|
/** 新增短信模板 */
|
||||||
export function createSmsTemplate(data: SystemSmsTemplateApi.SmsTemplateVO) {
|
export function createSmsTemplate(data: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
return requestClient.post('/system/sms-template/create', data);
|
return requestClient.post('/system/sms-template/create', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改短信模板 */
|
/** 修改短信模板 */
|
||||||
export function updateSmsTemplate(data: SystemSmsTemplateApi.SmsTemplateVO) {
|
export function updateSmsTemplate(data: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
return requestClient.put('/system/sms-template/update', data);
|
return requestClient.put('/system/sms-template/update', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,12 +57,10 @@ export function deleteSmsTemplate(id: number) {
|
||||||
|
|
||||||
/** 导出短信模板 */
|
/** 导出短信模板 */
|
||||||
export function exportSmsTemplate(params: any) {
|
export function exportSmsTemplate(params: any) {
|
||||||
return requestClient.download('/system/sms-template/export-excel', {
|
return requestClient.download('/system/sms-template/export-excel', { params });
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送短信 */
|
/** 发送短信 */
|
||||||
export function sendSms(data: SystemSmsTemplateApi.SendSmsReqVO) {
|
export function sendSms(data: SystemSmsTemplateApi.SmsSendReqVO) {
|
||||||
return requestClient.post('/system/sms-template/send-sms', data);
|
return requestClient.post('/system/sms-template/send-sms', data);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemTenantPackageApi {
|
||||||
|
/** 租户套餐信息 */
|
||||||
|
export interface SystemTenantPackage {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
status: number;
|
||||||
|
remark: string;
|
||||||
|
creator: string;
|
||||||
|
updater: string;
|
||||||
|
updateTime: string;
|
||||||
|
menuIds: number[];
|
||||||
|
createTime: Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 租户套餐列表 */
|
||||||
|
export function getTenantPackagePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<SystemTenantPackageApi.SystemTenantPackage>>(
|
||||||
|
'/system/tenant-package/page',
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询租户套餐详情 */
|
||||||
|
export function getTenantPackage(id: number) {
|
||||||
|
return requestClient.get(`/system/tenant-package/get?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增租户套餐 */
|
||||||
|
export function createTenantPackage(data: SystemTenantPackageApi.SystemTenantPackage) {
|
||||||
|
return requestClient.post('/system/tenant-package/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改租户套餐 */
|
||||||
|
export function updateTenantPackage(data: SystemTenantPackageApi.SystemTenantPackage) {
|
||||||
|
return requestClient.put('/system/tenant-package/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除租户套餐 */
|
||||||
|
export function deleteTenantPackage(id: number) {
|
||||||
|
return requestClient.delete(`/system/tenant-package/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取租户套餐精简信息列表 */
|
||||||
|
export function getTenantPackageList() {
|
||||||
|
return requestClient.get<SystemTenantPackageApi.SystemTenantPackage[]>('/system/tenant-package/get-simple-list');
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import type { PageParam, PageResult } from '@vben/request';
|
||||||
|
|
||||||
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
export namespace SystemTenantApi {
|
||||||
|
/** 租户信息 */
|
||||||
|
export interface SystemTenant {
|
||||||
|
id?: number;
|
||||||
|
name: string;
|
||||||
|
packageId: number;
|
||||||
|
contactName: string;
|
||||||
|
contactMobile: string;
|
||||||
|
accountCount: number;
|
||||||
|
expireTime: Date;
|
||||||
|
website: string;
|
||||||
|
status: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 租户列表 */
|
||||||
|
export function getTenantPage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<SystemTenantApi.SystemTenant>>('/system/tenant/page', { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取租户精简信息列表 */
|
||||||
|
export function getSimpleTenantList() {
|
||||||
|
return requestClient.get<SystemTenantApi.SystemTenant[]>('/system/tenant/simple-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询租户详情 */
|
||||||
|
export function getTenant(id: number) {
|
||||||
|
return requestClient.get<SystemTenantApi.SystemTenant>(`/system/tenant/get?id=${id}`,);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增租户 */
|
||||||
|
export function createTenant(data: SystemTenantApi.SystemTenant) {
|
||||||
|
return requestClient.post('/system/tenant/create', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改租户 */
|
||||||
|
export function updateTenant(data: SystemTenantApi.SystemTenant) {
|
||||||
|
return requestClient.put('/system/tenant/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除租户 */
|
||||||
|
export function deleteTenant(id: number) {
|
||||||
|
return requestClient.delete(`/system/tenant/delete?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出租户 */
|
||||||
|
export function exportTenant(params: any) {
|
||||||
|
return requestClient.download('/system/tenant/export-excel', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
fieldName: 'id',
|
fieldName: 'id',
|
||||||
label: 'id',
|
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: [''],
|
triggerFields: [''],
|
||||||
|
|
@ -22,6 +21,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'signature',
|
fieldName: 'signature',
|
||||||
label: '短信签名',
|
label: '短信签名',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入短信签名',
|
||||||
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -29,8 +31,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
label: '渠道编码',
|
label: '渠道编码',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
class: 'w-full',
|
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'),
|
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'),
|
||||||
|
class: 'w-full',
|
||||||
|
placeholder: '请选择短信渠道',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
@ -49,22 +52,34 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'remark',
|
fieldName: 'remark',
|
||||||
label: '备注',
|
label: '备注',
|
||||||
component: 'Textarea',
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入备注',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'apiKey',
|
fieldName: 'apiKey',
|
||||||
label: '短信 API 的账号',
|
label: '短信 API 的账号',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入短信 API 的账号',
|
||||||
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'apiSecret',
|
fieldName: 'apiSecret',
|
||||||
label: '短信 API 的密钥',
|
label: '短信 API 的密钥',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入短信 API 的密钥',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'callbackUrl',
|
fieldName: 'callbackUrl',
|
||||||
label: '短信发送回调 URL',
|
label: '短信发送回调 URL',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入短信发送回调 URL',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +91,10 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'signature',
|
fieldName: 'signature',
|
||||||
label: '短信签名',
|
label: '短信签名',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入短信签名',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
|
|
@ -84,6 +103,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'),
|
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'),
|
||||||
|
placeholder: '请选择短信渠道',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -96,6 +116,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// TODO @芋艿:怎么解决范围检索
|
||||||
fieldName: 'createTime',
|
fieldName: 'createTime',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
component: 'RangePicker',
|
component: 'RangePicker',
|
||||||
|
|
@ -107,7 +128,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemSmsChannelApi.SmsChannelVO>(
|
export function useGridColumns<T = SystemSmsChannelApi.SmsChannel>(
|
||||||
onActionClick: OnActionClickFn<T>,
|
onActionClick: OnActionClickFn<T>,
|
||||||
): VxeTableGridOptions['columns'] {
|
): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,18 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSmsChannelApi } from '#/api/system/sms/channel';
|
import type { SystemSmsChannelApi } from '#/api/system/sms/channel';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
import { Download, Plus } from '@vben/icons';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import {
|
|
||||||
deleteSmsChannel,
|
|
||||||
exportSmsChannel,
|
|
||||||
getSmsChannelPage,
|
|
||||||
} from '#/api/system/sms/channel';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getSmsChannelPage, deleteSmsChannel, exportSmsChannel } from '#/api/system/sms/channel';
|
||||||
import { downloadByData } from '#/utils/download';
|
import { downloadByData } from '#/utils/download';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import Form from './modules/form.vue';
|
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
|
|
@ -44,12 +36,12 @@ function onCreate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑短信渠道 */
|
/** 编辑短信渠道 */
|
||||||
function onEdit(row: SystemSmsChannelApi.SmsChannelVO) {
|
function onEdit(row: SystemSmsChannelApi.SmsChannel) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除短信渠道 */
|
/** 删除短信渠道 */
|
||||||
async function onDelete(row: SystemSmsChannelApi.SmsChannelVO) {
|
async function onDelete(row: SystemSmsChannelApi.SmsChannel) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.signature]),
|
content: $t('ui.actionMessage.deleting', [row.signature]),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
|
|
@ -71,16 +63,16 @@ async function onDelete(row: SystemSmsChannelApi.SmsChannelVO) {
|
||||||
function onActionClick({
|
function onActionClick({
|
||||||
code,
|
code,
|
||||||
row,
|
row,
|
||||||
}: OnActionClickParams<SystemSmsChannelApi.SmsChannelVO>) {
|
}: OnActionClickParams<SystemSmsChannelApi.SmsChannel>) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
case 'edit': {
|
||||||
onEdit(row);
|
onEdit(row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,7 +102,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
refresh: { code: 'query' },
|
refresh: { code: 'query' },
|
||||||
search: true,
|
search: true,
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions<SystemSmsChannelApi.SmsChannelVO>,
|
} as VxeTableGridOptions<SystemSmsChannelApi.SmsChannel>,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { SystemSmsChannelApi } from '#/api/system/sms/channel';
|
import type { SystemSmsChannelApi } from '#/api/system/sms/channel';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
|
||||||
import {
|
|
||||||
createSmsChannel,
|
|
||||||
getSmsChannel,
|
|
||||||
updateSmsChannel,
|
|
||||||
} from '#/api/system/sms/channel';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getSmsChannel, createSmsChannel, updateSmsChannel } from '#/api/system/sms/channel';
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const formData = ref<SystemSmsChannelApi.SmsChannelVO>();
|
const formData = ref<SystemSmsChannelApi.SmsChannel>();
|
||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
return formData.value?.id
|
return formData.value?.id
|
||||||
? $t('ui.actionTitle.edit', ['短信渠道'])
|
? $t('ui.actionTitle.edit', ['短信渠道'])
|
||||||
|
|
@ -29,6 +23,9 @@ const [Form, formApi] = useVbenForm({
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
commonConfig: {
|
||||||
|
labelWidth: 120
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
|
@ -39,12 +36,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
}
|
}
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const data =
|
const data = (await formApi.getValues()) as SystemSmsChannelApi.SmsChannel;
|
||||||
(await formApi.getValues()) as SystemSmsChannelApi.SmsChannelVO;
|
|
||||||
try {
|
try {
|
||||||
await (formData.value?.id
|
await (formData.value?.id ? updateSmsChannel(data) : createSmsChannel(data));
|
||||||
? updateSmsChannel(data)
|
|
||||||
: createSmsChannel(data));
|
|
||||||
// 关闭并提示
|
// 关闭并提示
|
||||||
await modalApi.close();
|
await modalApi.close();
|
||||||
emit('success');
|
emit('success');
|
||||||
|
|
@ -56,12 +50,12 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const data = modalApi.getData<SystemSmsChannelApi.SmsChannelVO>();
|
const data = modalApi.getData<SystemSmsChannelApi.SmsChannel>();
|
||||||
if (!data || !data.id) {
|
if (!data || !data.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
label: '手机号',
|
label: '手机号',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入手机号',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'channelId',
|
fieldName: 'channelId',
|
||||||
|
|
@ -22,23 +26,30 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
labelField: 'signature',
|
labelField: 'signature',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
|
placeholder: '请选择短信渠道',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'templateId',
|
fieldName: 'templateId',
|
||||||
label: '模板编号',
|
label: '模板编号',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入模板编号',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'sendStatus',
|
fieldName: 'sendStatus',
|
||||||
label: '发送状态',
|
label: '发送状态',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS, 'number'),
|
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS, 'number'),
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择发送状态',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// TODO @芋艿:怎么解决范围检索
|
||||||
fieldName: 'sendTime',
|
fieldName: 'sendTime',
|
||||||
label: '发送时间',
|
label: '发送时间',
|
||||||
component: 'RangePicker',
|
component: 'RangePicker',
|
||||||
|
|
@ -51,11 +62,13 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
label: '接收状态',
|
label: '接收状态',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS, 'number'),
|
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS, 'number'),
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择接收状态',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// TODO @芋艿:怎么解决范围检索
|
||||||
fieldName: 'receiveTime',
|
fieldName: 'receiveTime',
|
||||||
label: '接收时间',
|
label: '接收时间',
|
||||||
component: 'RangePicker',
|
component: 'RangePicker',
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSmsLogApi } from '#/api/system/sms/log';
|
import type { SystemSmsLogApi } from '#/api/system/sms/log';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download } from '@vben/icons';
|
import { Download } from '@vben/icons';
|
||||||
|
|
||||||
import { Button } from 'ant-design-vue';
|
import { Button } from 'ant-design-vue';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { exportSmsLog, getSmsLogPage } from '#/api/system/sms/log';
|
import { exportSmsLog, getSmsLogPage } from '#/api/system/sms/log';
|
||||||
import { $t } from '#/locales';
|
|
||||||
import { downloadByData } from '#/utils/download';
|
import { downloadByData } from '#/utils/download';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import Form from './modules/form.vue';
|
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { SystemSmsLogApi } from '#/api/system/sms/log';
|
import type { SystemSmsLogApi } from '#/api/system/sms/log';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const formData = ref<SystemSmsLogApi.SmsLogVO>();
|
const formData = ref<SystemSmsLogApi.SmsLogVO>();
|
||||||
const getTitle = computed(() => {
|
|
||||||
return '短信日志详情';
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -30,8 +27,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- TODO @puhui999:https://ant-design.antgroup.com/components/descriptions-cn 参考这个? -->
|
||||||
<template>
|
<template>
|
||||||
<Modal :title="getTitle">
|
<Modal title="短信日志详情">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
|
|
@ -39,6 +37,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
<div>{{ formData?.id }}</div>
|
<div>{{ formData?.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
|
<!-- TODO @puhui:格式不对 -->
|
||||||
<div class="form-label">创建时间:</div>
|
<div class="form-label">创建时间:</div>
|
||||||
<div>{{ formData?.createTime }}</div>
|
<div>{{ formData?.createTime }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -67,6 +66,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
|
|
||||||
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
|
<!-- TODO @puhui:格式不对 -->
|
||||||
<div class="form-label">发送状态:</div>
|
<div class="form-label">发送状态:</div>
|
||||||
<div>{{ formData?.sendStatus }}</div>
|
<div>{{ formData?.sendStatus }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -83,6 +83,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
<div>{{ formData?.apiSendMsg }}</div>
|
<div>{{ formData?.apiSendMsg }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
|
<!-- TODO @puhui:格式不对 -->
|
||||||
<div class="form-label">接收状态:</div>
|
<div class="form-label">接收状态:</div>
|
||||||
<div>{{ formData?.receiveStatus }}</div>
|
<div>{{ formData?.receiveStatus }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,19 +92,19 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
<div>{{ formData?.receiveTime }}</div>
|
<div>{{ formData?.receiveTime }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<div class="form-label">API接收编码:</div>
|
<div class="form-label">API 接收编码:</div>
|
||||||
<div>{{ formData?.apiReceiveCode }}</div>
|
<div>{{ formData?.apiReceiveCode }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<div class="form-label">API接收消息:</div>
|
<div class="form-label">API 接收消息:</div>
|
||||||
<div>{{ formData?.apiReceiveMsg }}</div>
|
<div>{{ formData?.apiReceiveMsg }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<div class="form-label">API请求ID:</div>
|
<div class="form-label">API 请求 ID:</div>
|
||||||
<div>{{ formData?.apiRequestId }}</div>
|
<div>{{ formData?.apiRequestId }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<div class="form-label">API序列号:</div>
|
<div class="form-label">API 序列号:</div>
|
||||||
<div>{{ formData?.apiSerialNo }}</div>
|
<div>{{ formData?.apiSerialNo }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
fieldName: 'id',
|
fieldName: 'id',
|
||||||
label: 'id',
|
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: [''],
|
triggerFields: [''],
|
||||||
|
|
@ -24,8 +23,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
label: '短信类型',
|
label: '短信类型',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
class: 'w-full',
|
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'),
|
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'),
|
||||||
|
class: 'w-full',
|
||||||
|
placeholder: '请选择短信类型',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
@ -33,12 +33,18 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
label: '模板名称',
|
label: '模板名称',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入模板名称',
|
||||||
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: '模板编码',
|
label: '模板编码',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入模板编码',
|
||||||
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -50,6 +56,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
labelField: 'signature',
|
labelField: 'signature',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
|
placeholder: '请选择短信渠道',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
@ -68,17 +75,27 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'content',
|
fieldName: 'content',
|
||||||
label: '模板内容',
|
label: '模板内容',
|
||||||
component: 'Textarea',
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入模板内容',
|
||||||
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'apiTemplateId',
|
fieldName: 'apiTemplateId',
|
||||||
label: '短信 API 的模板编号',
|
label: '短信 API 的模板编号',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入短信 API 的模板编号',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'remark',
|
fieldName: 'remark',
|
||||||
label: '备注',
|
label: '备注',
|
||||||
component: 'Textarea',
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入备注',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -91,8 +108,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
label: '短信类型',
|
label: '短信类型',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
|
||||||
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'),
|
options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'),
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择短信类型',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -100,19 +118,28 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
label: '开启状态',
|
label: '开启状态',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择开启状态',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'code',
|
fieldName: 'code',
|
||||||
label: '模板编码',
|
label: '模板编码',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入模板编码',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
label: '模板名称',
|
label: '模板名称',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入模板名称',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'channelId',
|
fieldName: 'channelId',
|
||||||
|
|
@ -123,8 +150,10 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
labelField: 'signature',
|
labelField: 'signature',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
|
placeholder: '请选择短信渠道',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// TODO @芋艿:范围检索的处理
|
||||||
{
|
{
|
||||||
fieldName: 'createTime',
|
fieldName: 'createTime',
|
||||||
label: '创建时间',
|
label: '创建时间',
|
||||||
|
|
@ -143,6 +172,9 @@ export function useSendSmsFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
label: '手机号码',
|
label: '手机号码',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入手机号码',
|
||||||
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -158,7 +190,7 @@ export function useSendSmsFormSchema(): VbenFormSchema[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemSmsTemplateApi.SmsTemplateVO>(
|
export function useGridColumns<T = SystemSmsTemplateApi.SmsTemplate>(
|
||||||
onActionClick: OnActionClickFn<T>,
|
onActionClick: OnActionClickFn<T>,
|
||||||
): VxeTableGridOptions['columns'] {
|
): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
|
|
@ -228,7 +260,7 @@ export function useGridColumns<T = SystemSmsTemplateApi.SmsTemplateVO>(
|
||||||
{
|
{
|
||||||
field: 'operation',
|
field: 'operation',
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 300,
|
minWidth: 180,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
cellRender: {
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,19 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
import { Download, Plus } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
import SendForm from './modules/send-form.vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import {
|
|
||||||
deleteSmsTemplate,
|
|
||||||
exportSmsTemplate,
|
|
||||||
getSmsTemplatePage,
|
|
||||||
} from '#/api/system/sms/template';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteSmsTemplate, exportSmsTemplate, getSmsTemplatePage } from '#/api/system/sms/template';
|
||||||
import { downloadByData } from '#/utils/download';
|
import { downloadByData } from '#/utils/download';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
import Form from './modules/form.vue';
|
|
||||||
import SendForm from './modules/send-form.vue';
|
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
|
|
@ -50,17 +42,17 @@ function onCreate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑短信模板 */
|
/** 编辑短信模板 */
|
||||||
function onEdit(row: SystemSmsTemplateApi.SmsTemplateVO) {
|
function onEdit(row: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送测试短信 */
|
/** 发送测试短信 */
|
||||||
function onSend(row: SystemSmsTemplateApi.SmsTemplateVO) {
|
function onSend(row: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
sendModalApi.setData(row).open();
|
sendModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除短信模板 */
|
/** 删除短信模板 */
|
||||||
async function onDelete(row: SystemSmsTemplateApi.SmsTemplateVO) {
|
async function onDelete(row: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
|
|
@ -82,16 +74,16 @@ async function onDelete(row: SystemSmsTemplateApi.SmsTemplateVO) {
|
||||||
function onActionClick({
|
function onActionClick({
|
||||||
code,
|
code,
|
||||||
row,
|
row,
|
||||||
}: OnActionClickParams<SystemSmsTemplateApi.SmsTemplateVO>) {
|
}: OnActionClickParams<SystemSmsTemplateApi.SmsTemplate>) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
case 'edit': {
|
||||||
onEdit(row);
|
onEdit(row);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'sms-send': {
|
case 'sms-send': {
|
||||||
onSend(row);
|
onSend(row);
|
||||||
break;
|
break;
|
||||||
|
|
@ -125,7 +117,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
refresh: { code: 'query' },
|
refresh: { code: 'query' },
|
||||||
search: true,
|
search: true,
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions<SystemSmsTemplateApi.SmsTemplateVO>,
|
} as VxeTableGridOptions<SystemSmsTemplateApi.SmsTemplate>,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
|
||||||
import {
|
|
||||||
createSmsTemplate,
|
|
||||||
getSmsTemplate,
|
|
||||||
updateSmsTemplate,
|
|
||||||
} from '#/api/system/sms/template';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createSmsTemplate, getSmsTemplate, updateSmsTemplate } from '#/api/system/sms/template';
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const formData = ref<SystemSmsTemplateApi.SmsTemplateVO>();
|
const formData = ref<SystemSmsTemplateApi.SmsTemplate>();
|
||||||
const getTitle = computed(() => {
|
const getTitle = computed(() => {
|
||||||
return formData.value?.id
|
return formData.value?.id
|
||||||
? $t('ui.actionTitle.edit', ['短信模板'])
|
? $t('ui.actionTitle.edit', ['短信模板'])
|
||||||
|
|
@ -29,6 +23,9 @@ const [Form, formApi] = useVbenForm({
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
schema: useFormSchema(),
|
schema: useFormSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
|
commonConfig: {
|
||||||
|
labelWidth: 140
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
|
@ -40,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const data =
|
const data =
|
||||||
(await formApi.getValues()) as SystemSmsTemplateApi.SmsTemplateVO;
|
(await formApi.getValues()) as SystemSmsTemplateApi.SmsTemplate;
|
||||||
try {
|
try {
|
||||||
await (formData.value?.id
|
await (formData.value?.id
|
||||||
? updateSmsTemplate(data)
|
? updateSmsTemplate(data)
|
||||||
|
|
@ -56,12 +53,12 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const data = modalApi.getData<SystemSmsTemplateApi.SmsTemplateVO>();
|
const data = modalApi.getData<SystemSmsTemplateApi.SmsTemplate>();
|
||||||
if (!data || !data.id) {
|
if (!data || !data.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,19 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
import { sendSms } from '#/api/system/sms/template';
|
import { sendSms } from '#/api/system/sms/template';
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
import { useSendSmsFormSchema } from '../data';
|
import { useSendSmsFormSchema } from '../data';
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
const templateData = ref<SystemSmsTemplateApi.SmsTemplateVO>();
|
const templateData = ref<SystemSmsTemplateApi.SmsTemplate>();
|
||||||
const getTitle = computed(() => {
|
|
||||||
return $t('ui.actionTitle.send', ['短信']);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// TODO @puhui999:貌似发送短信,少了参数展示
|
||||||
// 动态构建表单
|
// 动态构建表单
|
||||||
const buildSchema = () => {
|
const buildSchema = () => {
|
||||||
const schema = useSendSmsFormSchema();
|
const schema = useSendSmsFormSchema();
|
||||||
|
|
@ -63,7 +58,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建发送短信请求
|
// 构建发送短信请求
|
||||||
const data: SystemSmsTemplateApi.SendSmsReqVO = {
|
const data: SystemSmsTemplateApi.SmsSendReqVO = {
|
||||||
mobile: values.mobile,
|
mobile: values.mobile,
|
||||||
templateCode: templateData.value?.code || '',
|
templateCode: templateData.value?.code || '',
|
||||||
templateParams: paramsObj,
|
templateParams: paramsObj,
|
||||||
|
|
@ -84,12 +79,12 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const data = modalApi.getData<SystemSmsTemplateApi.SmsTemplateVO>();
|
const data = modalApi.getData<SystemSmsTemplateApi.SmsTemplate>();
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +98,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal :title="getTitle">
|
<Modal title="发送短信">
|
||||||
<Form class="mx-4" />
|
<Form class="mx-4" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantApi } from '#/api/system/tenant';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { getTenantPackageList } from '#/api/system/tenant-package';
|
||||||
|
import { CommonStatusEnum } from '#/utils/constants';
|
||||||
|
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
|
/** 新增/修改的表单 */
|
||||||
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'id',
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '租户名称',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'packageId',
|
||||||
|
label: '租户套餐',
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
api: () => getTenantPackageList(),
|
||||||
|
class: 'w-full',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id',
|
||||||
|
placeholder: '请选择租户套餐',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'contactName',
|
||||||
|
label: '联系人',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'contactMobile',
|
||||||
|
label: '联系手机',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户名称',
|
||||||
|
fieldName: 'username',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['id'],
|
||||||
|
show: (values) => !values.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户密码',
|
||||||
|
fieldName: 'password',
|
||||||
|
component: 'InputPassword',
|
||||||
|
rules: 'required',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: ['id'],
|
||||||
|
show: (values) => !values.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '账号额度',
|
||||||
|
fieldName: 'accountCount',
|
||||||
|
component: 'InputNumber',
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
placeholder: '请输入账号额度',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '过期时间',
|
||||||
|
fieldName: 'expireTime',
|
||||||
|
component: 'DatePicker',
|
||||||
|
componentProps: {
|
||||||
|
format: 'YYYY-MM-DD',
|
||||||
|
valueFormat: 'x',
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '绑定域名',
|
||||||
|
fieldName: 'website',
|
||||||
|
component: 'Input',
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '租户状态',
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
rules: z.number().default(CommonStatusEnum.ENABLE),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '租户名',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'contactName',
|
||||||
|
label: '联系人',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'contactMobile',
|
||||||
|
label: '联系手机',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'createTime',
|
||||||
|
label: '创建时间',
|
||||||
|
component: 'RangePicker',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的字段 */
|
||||||
|
const tenantPackageList = await getTenantPackageList();
|
||||||
|
export function useGridColumns<T = SystemTenantApi.SystemTenant>(onActionClick: OnActionClickFn<T>): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: '租户编号',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '租户名',
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'packageId',
|
||||||
|
title: '租户套餐',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: (row) => {
|
||||||
|
const packageId = row.cellValue;
|
||||||
|
return packageId === 0 ? '系统租户' : tenantPackageList.find((tenantPackage) => tenantPackage.id === packageId)?.name || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'contactName',
|
||||||
|
title: '联系人',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'contactMobile',
|
||||||
|
title: '联系手机',
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'accountCount',
|
||||||
|
title: '账号额度',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'expireTime',
|
||||||
|
title: '过期时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'website',
|
||||||
|
title: '绑定域名',
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '租户状态',
|
||||||
|
minWidth: 100,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellDict',
|
||||||
|
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
title: '创建时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operation',
|
||||||
|
title: '操作',
|
||||||
|
minWidth: 130,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: '租户',
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantApi } from '#/api/system/tenant';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
import { Plus, Download } from '@vben/icons';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { getTenantPage, deleteTenant, exportTenant } from '#/api/system/tenant';
|
||||||
|
import { downloadByData } from '#/utils/download';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 刷新表格 */
|
||||||
|
function onRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出表格 */
|
||||||
|
async function onExport() {
|
||||||
|
const data = await exportTenant(await gridApi.formApi.getValues());
|
||||||
|
downloadByData(data, '租户.xls');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建租户 */
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑租户 */
|
||||||
|
function onEdit(row: SystemTenantApi.SystemTenant) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除租户 */
|
||||||
|
async function onDelete(row: SystemTenantApi.SystemTenant) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteTenant(row.id as number);
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
} catch {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表格操作按钮的回调函数 */
|
||||||
|
function onActionClick({
|
||||||
|
code,
|
||||||
|
row,
|
||||||
|
}: OnActionClickParams<SystemTenantApi.SystemTenant>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
// TODO @芋艿:时间筛选,后续处理;
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getTenantPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<SystemTenantApi.SystemTenant>,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="onRefresh" />
|
||||||
|
<Grid table-title="租户列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', ['租户']) }}
|
||||||
|
</Button>
|
||||||
|
<Button type="primary" class="ml-2" @click="onExport">
|
||||||
|
<Download class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.export') }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemTenantApi } from '#/api/system/tenant';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { createTenant, getTenant, updateTenant } from '#/api/system/tenant';
|
||||||
|
|
||||||
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<SystemTenantApi.SystemTenant>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value
|
||||||
|
? $t('ui.actionTitle.edit', ['租户'])
|
||||||
|
: $t('ui.actionTitle.create', ['租户']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: useFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modalApi.lock();
|
||||||
|
// 提交表单
|
||||||
|
const data = (await formApi.getValues()) as SystemTenantApi.SystemTenant;
|
||||||
|
try {
|
||||||
|
await (formData.value ? updateTenant(data) : createTenant(data));
|
||||||
|
// 关闭并提示
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.operationSuccess'),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 加载数据
|
||||||
|
const data = modalApi.getData<SystemTenantApi.SystemTenant>();
|
||||||
|
if (!data || !data.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
formData.value = await getTenant(data.id as number);
|
||||||
|
// 设置到 values
|
||||||
|
await formApi.setValues(formData.value);
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form class="mx-4" />
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
||||||
|
|
||||||
|
import { z } from '#/adapter/form';
|
||||||
|
import { CommonStatusEnum } from '#/utils/constants';
|
||||||
|
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||||
|
|
||||||
|
/** 新增/修改的表单 */
|
||||||
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'id',
|
||||||
|
component: 'Input',
|
||||||
|
dependencies: {
|
||||||
|
triggerFields: [''],
|
||||||
|
show: () => false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '套餐名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入套餐名称',
|
||||||
|
},
|
||||||
|
rules: 'required',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'menuIds',
|
||||||
|
label: '菜单权限',
|
||||||
|
component: 'Input',
|
||||||
|
formItemClass: 'items-start',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
component: 'RadioGroup',
|
||||||
|
componentProps: {
|
||||||
|
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||||
|
buttonStyle: 'solid',
|
||||||
|
optionType: 'button',
|
||||||
|
},
|
||||||
|
rules: z.number().default(CommonStatusEnum.ENABLE),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'remark',
|
||||||
|
label: '备注',
|
||||||
|
component: 'Textarea',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入备注',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
label: '套餐名称',
|
||||||
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请输入套餐名称',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'status',
|
||||||
|
label: '状态',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '请选择状态',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'createTime',
|
||||||
|
label: '创建时间',
|
||||||
|
component: 'RangePicker',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 列表的字段 */
|
||||||
|
export function useGridColumns<T = SystemTenantPackageApi.SystemTenantPackage>(onActionClick: OnActionClickFn<T>): VxeTableGridOptions['columns'] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: '套餐编号',
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: '套餐名称',
|
||||||
|
minWidth: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: '状态',
|
||||||
|
minWidth: 100,
|
||||||
|
cellRender: {
|
||||||
|
name: 'CellDict',
|
||||||
|
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'remark',
|
||||||
|
title: '备注',
|
||||||
|
minWidth: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'createTime',
|
||||||
|
title: '创建时间',
|
||||||
|
minWidth: 180,
|
||||||
|
formatter: 'formatDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operation',
|
||||||
|
title: '操作',
|
||||||
|
minWidth: 130,
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
cellRender: {
|
||||||
|
attrs: {
|
||||||
|
nameField: 'name',
|
||||||
|
nameTitle: '套餐',
|
||||||
|
onClick: onActionClick,
|
||||||
|
},
|
||||||
|
name: 'CellOperation',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
||||||
|
|
||||||
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
import { Button, message } from 'ant-design-vue';
|
||||||
|
import Form from './modules/form.vue';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import { deleteTenantPackage, getTenantPackagePage } from '#/api/system/tenant-package';
|
||||||
|
|
||||||
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Form,
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 刷新表格 */
|
||||||
|
function onRefresh() {
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建租户套餐 */
|
||||||
|
function onCreate() {
|
||||||
|
formModalApi.setData(null).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 编辑租户套餐 */
|
||||||
|
function onEdit(row: SystemTenantPackageApi.SystemTenantPackage) {
|
||||||
|
formModalApi.setData(row).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除租户套餐 */
|
||||||
|
async function onDelete(row: SystemTenantPackageApi.SystemTenantPackage) {
|
||||||
|
const hideLoading = message.loading({
|
||||||
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
|
duration: 0,
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await deleteTenantPackage(row.id as number);
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
onRefresh();
|
||||||
|
} catch {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表格操作按钮的回调函数 */
|
||||||
|
function onActionClick({ code, row }: OnActionClickParams<SystemTenantPackageApi.SystemTenantPackage>) {
|
||||||
|
switch (code) {
|
||||||
|
case 'edit': {
|
||||||
|
onEdit(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'delete': {
|
||||||
|
onDelete(row);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
// TODO @芋艿:时间筛选,后续处理;
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(onActionClick),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
return await getTenantPackagePage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: { code: 'query' },
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<SystemTenantPackageApi.SystemTenantPackage>,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Page auto-content-height>
|
||||||
|
<FormModal @success="onRefresh" />
|
||||||
|
<Grid table-title="租户套餐列表">
|
||||||
|
<template #toolbar-tools>
|
||||||
|
<Button type="primary" @click="onCreate">
|
||||||
|
<Plus class="size-5" />
|
||||||
|
{{ $t('ui.actionTitle.create', ['套餐']) }}
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
||||||
|
|
||||||
|
import { useVbenModal, VbenTree } from '@vben/common-ui';
|
||||||
|
import { Checkbox, message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getMenuList } from '#/api/system/menu';
|
||||||
|
import { createTenantPackage, getTenantPackage, updateTenantPackage } from '#/api/system/tenant-package';
|
||||||
|
import { handleTree } from '#/utils/tree';
|
||||||
|
|
||||||
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']);
|
||||||
|
const formData = ref<SystemTenantPackageApi.SystemTenantPackage>();
|
||||||
|
const getTitle = computed(() => {
|
||||||
|
return formData.value ? $t('ui.actionTitle.edit', ['套餐']) : $t('ui.actionTitle.create', ['套餐']);
|
||||||
|
});
|
||||||
|
const menuTree = ref<SystemDeptApi.SystemDept[]>([]); // 菜单树
|
||||||
|
const menuLoading = ref(false); // 加载菜单列表
|
||||||
|
const isAllSelected = ref(false); // 全选状态
|
||||||
|
const isExpanded = ref(false); // 展开状态
|
||||||
|
const expandedKeys = ref<number[]>([]); // 展开的节点
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
layout: 'horizontal',
|
||||||
|
schema: useFormSchema(),
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
async onConfirm() {
|
||||||
|
const { valid } = await formApi.validate();
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modalApi.lock();
|
||||||
|
// 提交表单
|
||||||
|
const data = (await formApi.getValues()) as SystemTenantPackageApi.SystemTenantPackage;
|
||||||
|
try {
|
||||||
|
await (formData.value ? updateTenantPackage(data) : createTenantPackage(data));
|
||||||
|
// 关闭并提示
|
||||||
|
await modalApi.close();
|
||||||
|
emit('success');
|
||||||
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.operationSuccess'),
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
// 加载菜单列表
|
||||||
|
await loadMenuTree();
|
||||||
|
if (!isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = modalApi.getData<SystemTenantPackageApi.SystemTenantPackage>();
|
||||||
|
if (!data || !data.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modalApi.lock();
|
||||||
|
try {
|
||||||
|
formData.value = await getTenantPackage(data.id as number);
|
||||||
|
await formApi.setValues(data);
|
||||||
|
} finally {
|
||||||
|
modalApi.lock(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 加载菜单树 */
|
||||||
|
async function loadMenuTree() {
|
||||||
|
menuLoading.value = true;
|
||||||
|
try {
|
||||||
|
const data = await getMenuList();
|
||||||
|
menuTree.value = handleTree(data);
|
||||||
|
} finally {
|
||||||
|
menuLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 全选/全不选 */
|
||||||
|
function toggleSelectAll() {
|
||||||
|
isAllSelected.value = !isAllSelected.value;
|
||||||
|
if (isAllSelected.value) {
|
||||||
|
const allIds = getAllNodeIds(menuTree.value);
|
||||||
|
formApi.setFieldValue('menuIds', allIds);
|
||||||
|
} else {
|
||||||
|
formApi.setFieldValue('menuIds', []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 展开/折叠所有节点 */
|
||||||
|
function toggleExpandAll() {
|
||||||
|
isExpanded.value = !isExpanded.value;
|
||||||
|
expandedKeys.value = isExpanded.value ? getAllNodeIds(menuTree.value) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 递归获取所有节点 ID */
|
||||||
|
function getAllNodeIds(nodes: any[], ids: number[] = []): number[] {
|
||||||
|
nodes.forEach((node: any) => {
|
||||||
|
ids.push(node.id);
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
getAllNodeIds(node.children, ids);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal :title="getTitle">
|
||||||
|
<Form class="mx-6">
|
||||||
|
<template #menuIds="slotProps">
|
||||||
|
<Spin :spinning="menuLoading" class="w-full">
|
||||||
|
<!-- TODO @芋艿:可优化,使用 antd 的 tree?原因是,更原生 -->
|
||||||
|
<VbenTree :tree-data="menuTree" multiple bordered :expanded="expandedKeys" v-bind="slotProps" value-field="id" label-field="name" />
|
||||||
|
</Spin>
|
||||||
|
</template>
|
||||||
|
</Form>
|
||||||
|
<template #prepend-footer>
|
||||||
|
<div class="flex flex-auto items-center">
|
||||||
|
<Checkbox :checked="isAllSelected" @change="toggleSelectAll"> 全选 </Checkbox>
|
||||||
|
<Checkbox :checked="isExpanded" @change="toggleExpandAll"> 全部展开 </Checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -103,17 +103,26 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
fieldName: 'username',
|
fieldName: 'username',
|
||||||
label: '用户名称',
|
label: '用户名称',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入用户名称',
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'mobile',
|
fieldName: 'mobile',
|
||||||
label: '手机号码',
|
label: '手机号码',
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入手机号码',
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldName: 'status',
|
fieldName: 'status',
|
||||||
label: '状态',
|
label: '状态',
|
||||||
component: 'Select',
|
component: 'Select',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
placeholder: '请输入用户状态',
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||||
},
|
},
|
||||||
|
|
@ -124,6 +133,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
component: 'RangePicker',
|
component: 'RangePicker',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
|
showTime: true,
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
placeholder: ['开始日期', '结束日期'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -150,7 +162,7 @@ export function useGridColumns<T = SystemUserApi.SystemUser>(
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'deptId',
|
field: 'deptName',
|
||||||
title: '部门',
|
title: '部门',
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
|
|
@ -159,13 +171,18 @@ export function useGridColumns<T = SystemUserApi.SystemUser>(
|
||||||
title: '手机号码',
|
title: '手机号码',
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
|
// TODO @芋艿:switch 的接入
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
|
align: 'center',
|
||||||
cellRender: {
|
cellRender: {
|
||||||
name: 'CellDict',
|
name: 'CellSwitch',
|
||||||
props: { type: DICT_TYPE.COMMON_STATUS },
|
props: {
|
||||||
|
activeValue: 0,
|
||||||
|
inactiveValue: 1
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -177,16 +194,28 @@ export function useGridColumns<T = SystemUserApi.SystemUser>(
|
||||||
{
|
{
|
||||||
field: 'operation',
|
field: 'operation',
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 180,
|
minWidth: 160,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
|
align: 'center',
|
||||||
cellRender: {
|
cellRender: {
|
||||||
attrs: {
|
attrs: {
|
||||||
nameField: 'username',
|
nameField: 'name',
|
||||||
nameTitle: '用户',
|
nameTitle: '角色',
|
||||||
onClick: onActionClick,
|
onClick: onActionClick,
|
||||||
},
|
},
|
||||||
name: 'CellOperation',
|
name: 'CellOperation',
|
||||||
|
options: [
|
||||||
|
'edit', // 默认的编辑按钮
|
||||||
|
'delete', // 默认的删除按钮
|
||||||
|
{
|
||||||
|
code: 'assign-data-permission',
|
||||||
|
text: '数据权限',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'assign-menu',
|
||||||
|
text: '菜单权限',
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
modalApi.lock(false);
|
modalApi.lock(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onOpenChange(isOpen) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
||||||
link: 'common-ui/vben-api-component',
|
link: 'common-ui/vben-api-component',
|
||||||
text: 'ApiComponent Api组件包装器',
|
text: 'ApiComponent Api组件包装器',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
link: 'common-ui/vben-alert',
|
||||||
|
text: 'Alert 轻量提示框',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
link: 'common-ui/vben-modal',
|
link: 'common-ui/vben-modal',
|
||||||
text: 'Modal 模态框',
|
text: 'Modal 模态框',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vben Alert 轻量提示框
|
||||||
|
|
||||||
|
框架提供的一些用于轻量提示的弹窗,仅使用js代码即可快速动态创建提示而不需要在template写任何代码。
|
||||||
|
|
||||||
|
::: info 应用场景
|
||||||
|
|
||||||
|
Alert提供的功能与Modal类似,但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求,请使用VbenModal
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: tip README
|
||||||
|
|
||||||
|
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
使用 `alert` 创建只有一个确认按钮的提示框。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-alert/alert" />
|
||||||
|
|
||||||
|
使用 `confirm` 创建有确认和取消按钮的提示框。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-alert/confirm" />
|
||||||
|
|
||||||
|
使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。
|
||||||
|
|
||||||
|
<DemoPreview dir="demos/vben-alert/prompt" />
|
||||||
|
|
||||||
|
## 类型说明
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/** 预置的图标类型 */
|
||||||
|
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
|
export type BeforeCloseScope = {
|
||||||
|
/** 是否为点击确认按钮触发的关闭 */
|
||||||
|
isConfirm: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AlertProps = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: (
|
||||||
|
scope: BeforeCloseScope,
|
||||||
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 边框 */
|
||||||
|
bordered?: boolean;
|
||||||
|
/** 取消按钮的标题 */
|
||||||
|
cancelText?: string;
|
||||||
|
/** 是否居中显示 */
|
||||||
|
centered?: boolean;
|
||||||
|
/** 确认按钮的标题 */
|
||||||
|
confirmText?: string;
|
||||||
|
/** 弹窗容器的额外样式 */
|
||||||
|
containerClass?: string;
|
||||||
|
/** 弹窗提示内容 */
|
||||||
|
content: Component | string;
|
||||||
|
/** 弹窗内容的额外样式 */
|
||||||
|
contentClass?: string;
|
||||||
|
/** 弹窗的图标(在标题的前面) */
|
||||||
|
icon?: Component | IconType;
|
||||||
|
/** 是否显示取消按钮 */
|
||||||
|
showCancel?: boolean;
|
||||||
|
/** 弹窗标题 */
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 函数签名
|
||||||
|
* alert和confirm的函数签名相同。
|
||||||
|
* confirm默认会显示取消按钮,而alert默认只有一个按钮
|
||||||
|
* */
|
||||||
|
export function alert(options: AlertProps): Promise<void>;
|
||||||
|
export function alert(
|
||||||
|
message: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
export function alert(
|
||||||
|
message: string,
|
||||||
|
title?: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹出输入框的函数签名。
|
||||||
|
* beforeClose的参数会传入用户当前输入的值
|
||||||
|
* component指定接受用户输入的组件,默认为Input
|
||||||
|
* componentProps 为输入组件设置的属性数据
|
||||||
|
* defaultValue 默认的值
|
||||||
|
* modelPropName 输入组件的值属性名称。默认为modelValue
|
||||||
|
*/
|
||||||
|
export async function prompt<T = any>(
|
||||||
|
options: Omit<AlertProps, 'beforeClose'> & {
|
||||||
|
beforeClose?: (
|
||||||
|
scope: BeforeCloseScope & {
|
||||||
|
/** 输入组件的当前值 */
|
||||||
|
value: T;
|
||||||
|
},
|
||||||
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
component?: Component;
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
defaultValue?: T;
|
||||||
|
modelPropName?: string;
|
||||||
|
},
|
||||||
|
): Promise<T | undefined>;
|
||||||
|
```
|
||||||
|
|
@ -123,6 +123,10 @@ function fetchApi(): Promise<Record<string, any>> {
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## 并发和缓存
|
||||||
|
|
||||||
|
有些场景下可能需要使用多个ApiComponent,它们使用了相同的远程数据源(例如用在可编辑的表格中)。如果直接将请求后端接口的函数传递给api属性,则每一个实例都会访问一次接口,这会造成资源浪费,是完全没有必要的。Tanstack Query提供了并发控制、缓存、重试等诸多特性,我们可以将接口请求函数用useQuery包装一下再传递给ApiComponent,这样的话无论页面有多少个使用相同数据源的ApiComponent实例,都只会发起一次远程请求。演示效果请参考 [Playground vue-query](https://www.vben.pro/#/demos/features/vue-query),具体代码请查看项目文件[concurrency-caching](https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/views/demos/features/vue-query/concurrency-caching.vue)
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|
@ -147,3 +151,10 @@ function fetchApi(): Promise<Record<string, any>> {
|
||||||
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
|
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
|
||||||
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
|
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
|
||||||
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
|
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
| 方法 | 描述 | 类型 | 版本要求 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |
|
||||||
|
| updateParam | 设置接口请求参数(将与params属性合并) | (newParams: Record<string, any>)=>void | >5.5.4 |
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
||||||
| collapsed | 是否折叠,在`showCollapseButton`为`true`时生效 | `boolean` | `false` |
|
| collapsed | 是否折叠,在`showCollapseButton`为`true`时生效 | `boolean` | `false` |
|
||||||
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
|
| collapseTriggerResize | 折叠时,触发`resize`事件 | `boolean` | `false` |
|
||||||
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
|
| collapsedRows | 折叠时保持的行数 | `number` | `1` |
|
||||||
| fieldMappingTime | 用于将表单内的数组值值映射成 2 个字段 | `[string, [string, string],Nullable<string>\|[string,string]\|((any,string)=>any)?][]` | - |
|
| fieldMappingTime | 用于将表单内的数组值映射成 2 个字段 | `[string, [string, string],Nullable<string>\|[string,string]\|((any,string)=>any)?][]` | - |
|
||||||
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
|
||||||
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
|
| schema | 表单项的每一项配置 | `FormSchema[]` | - |
|
||||||
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
| submitOnEnter | 按下回车健时提交表单 | `boolean` | false |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { alert, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Empty } from 'ant-design-vue';
|
||||||
|
|
||||||
|
function showAlert() {
|
||||||
|
alert('This is an alert message');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showIconAlert() {
|
||||||
|
alert({
|
||||||
|
content: 'This is an alert message with icon',
|
||||||
|
icon: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCustomAlert() {
|
||||||
|
alert({
|
||||||
|
content: h(Empty, { description: '什么都没有' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<VbenButton @click="showAlert">Alert</VbenButton>
|
||||||
|
<VbenButton @click="showIconAlert">Alert With Icon</VbenButton>
|
||||||
|
<VbenButton @click="showCustomAlert">Alert With Custom Content</VbenButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
function showConfirm() {
|
||||||
|
confirm('This is an alert message')
|
||||||
|
.then(() => {
|
||||||
|
alert('Confirmed');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Canceled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showIconConfirm() {
|
||||||
|
confirm({
|
||||||
|
content: 'This is an alert message with icon',
|
||||||
|
icon: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAsyncConfirm() {
|
||||||
|
confirm({
|
||||||
|
beforeClose({ isConfirm }) {
|
||||||
|
if (isConfirm) {
|
||||||
|
// 这里可以执行一些异步操作。如果最终返回了false,将阻止关闭弹窗
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: 'This is an alert message with async confirm',
|
||||||
|
icon: 'success',
|
||||||
|
}).then(() => {
|
||||||
|
alert('Confirmed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
||||||
|
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
||||||
|
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { VbenSelect } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
|
function showPrompt() {
|
||||||
|
prompt({
|
||||||
|
content: '请输入一些东西',
|
||||||
|
})
|
||||||
|
.then((val) => {
|
||||||
|
alert(`已收到你的输入:${val}`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert('Canceled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSelectPrompt() {
|
||||||
|
prompt({
|
||||||
|
component: VbenSelect,
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: 'Option 1', value: 'option1' },
|
||||||
|
{ label: 'Option 2', value: 'option2' },
|
||||||
|
{ label: 'Option 3', value: 'option3' },
|
||||||
|
],
|
||||||
|
placeholder: '请选择',
|
||||||
|
},
|
||||||
|
content: 'This is an alert message with icon',
|
||||||
|
icon: 'question',
|
||||||
|
}).then((val) => {
|
||||||
|
alert(`你选择的是${val}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||||
|
<VbenButton @click="showSelectPrompt">Confirm With Select</VbenButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -150,6 +150,73 @@ To run the `docs` application:
|
||||||
pnpm dev:docs
|
pnpm dev:docs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Distinguishing Build Environments
|
||||||
|
|
||||||
|
In actual business development, multiple environments are usually distinguished during the build process, such as the test environment `test` and the production environment `build`.
|
||||||
|
|
||||||
|
At this point, you can modify three files and add corresponding script configurations to distinguish between production environments.
|
||||||
|
|
||||||
|
Take the addition of the test environment `test` to `@vben/web-antd` as an example:
|
||||||
|
|
||||||
|
- `apps\web-antd\package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build:prod": "pnpm vite build --mode production",
|
||||||
|
"build:test": "pnpm vite build --mode test",
|
||||||
|
"build:analyze": "pnpm vite build --mode analyze",
|
||||||
|
"dev": "pnpm vite --mode development",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the command `"build:test"` and change the original `"build"` to `"build:prod"` to avoid building packages for two environments simultaneously.
|
||||||
|
|
||||||
|
- `package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
|
||||||
|
"build:analyze": "turbo build:analyze",
|
||||||
|
"build:antd": "pnpm run build --filter=@vben/web-antd",
|
||||||
|
"build-test:antd": "pnpm run build --filter=@vben/web-antd build:test",
|
||||||
|
|
||||||
|
······
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the command to build the test environment in the root directory `package.json`.
|
||||||
|
|
||||||
|
- `turbo.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": [
|
||||||
|
"dist/**",
|
||||||
|
"dist.zip",
|
||||||
|
".vitepress/dist.zip",
|
||||||
|
".vitepress/dist/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"build-test:antd": {
|
||||||
|
"dependsOn": ["@vben/web-antd#build:test"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"@vben/web-antd#build:test": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
······
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the relevant dependent commands in `turbo.json`.
|
||||||
|
|
||||||
## Public Static Resources
|
## Public Static Resources
|
||||||
|
|
||||||
If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.
|
If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,73 @@ pnpm dev:ele
|
||||||
pnpm dev:docs
|
pnpm dev:docs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 区分构建环境
|
||||||
|
|
||||||
|
在实际的业务开发中,通常会在构建时区分多种环境,如测试环境`test`、生产环境`build`等。
|
||||||
|
|
||||||
|
此时可以修改三个文件,在其中增加对应的脚本配置来达到区分生产环境的效果。
|
||||||
|
|
||||||
|
以`@vben/web-antd`添加测试环境`test`为例:
|
||||||
|
|
||||||
|
- `apps\web-antd\package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build:prod": "pnpm vite build --mode production",
|
||||||
|
"build:test": "pnpm vite build --mode test",
|
||||||
|
"build:analyze": "pnpm vite build --mode analyze",
|
||||||
|
"dev": "pnpm vite --mode development",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
增加命令`"build:test"`, 并将原`"build"`改为`"build:prod"`以避免同时构建两个环境的包。
|
||||||
|
|
||||||
|
- `package.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build",
|
||||||
|
"build:analyze": "turbo build:analyze",
|
||||||
|
"build:antd": "pnpm run build --filter=@vben/web-antd",
|
||||||
|
"build-test:antd": "pnpm run build --filter=@vben/web-antd build:test",
|
||||||
|
|
||||||
|
······
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在根目录`package.json`中加入构建测试环境的命令
|
||||||
|
|
||||||
|
- `turbo.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": [
|
||||||
|
"dist/**",
|
||||||
|
"dist.zip",
|
||||||
|
".vitepress/dist.zip",
|
||||||
|
".vitepress/dist/**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"build-test:antd": {
|
||||||
|
"dependsOn": ["@vben/web-antd#build:test"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"@vben/web-antd#build:test": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
|
||||||
|
······
|
||||||
|
```
|
||||||
|
|
||||||
|
在`turbo.json`中加入相关依赖的命令
|
||||||
|
|
||||||
## 公共静态资源
|
## 公共静态资源
|
||||||
|
|
||||||
项目中需要使用到的公共静态资源,如:图片、静态HTML等,需要在开发中通过 `src="/xxx.png"` 直接引入的。
|
项目中需要使用到的公共静态资源,如:图片、静态HTML等,需要在开发中通过 `src="/xxx.png"` 直接引入的。
|
||||||
|
|
|
||||||
|
|
@ -46,3 +46,47 @@ async function getVersionTag() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 替换为第三方库检查更新方式
|
||||||
|
|
||||||
|
如果需要通过其他方式检查更新,例如使用其他版本控制方式(chunkHash、version.json)、使用`Web Worker`在后台轮询更新、自定义检查更新时机(不使用轮询),你可以通过JS库`version-polling`来实现。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add version-polling
|
||||||
|
```
|
||||||
|
|
||||||
|
以`apps/web-antd`项目为例,在项目入口文件`main.ts`或者`app.vue`添加以下代码
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { Button, notification } from 'ant-design-vue';
|
||||||
|
import { createVersionPolling } from 'version-polling';
|
||||||
|
|
||||||
|
createVersionPolling({
|
||||||
|
silent: import.meta.env.MODE === 'development', // 开发环境下不检测
|
||||||
|
onUpdate: (self) => {
|
||||||
|
const key = `open${Date.now()}`;
|
||||||
|
notification.info({
|
||||||
|
message: '提示',
|
||||||
|
description: '检测到网页有更新, 是否刷新页面加载最新版本?',
|
||||||
|
btn: () =>
|
||||||
|
h(
|
||||||
|
Button,
|
||||||
|
{
|
||||||
|
type: 'primary',
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => {
|
||||||
|
notification.close(key);
|
||||||
|
self.onRefresh();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ default: () => '刷新' },
|
||||||
|
),
|
||||||
|
key,
|
||||||
|
duration: null,
|
||||||
|
placement: 'bottomRight',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@
|
||||||
"node": ">=20.10.0",
|
"node": ">=20.10.0",
|
||||||
"pnpm": ">=9.12.0"
|
"pnpm": ">=9.12.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.15.7",
|
"packageManager": "pnpm@9.15.9",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,10 @@ export {
|
||||||
ChevronsLeft,
|
ChevronsLeft,
|
||||||
ChevronsRight,
|
ChevronsRight,
|
||||||
Circle,
|
Circle,
|
||||||
|
CircleAlert,
|
||||||
CircleCheckBig,
|
CircleCheckBig,
|
||||||
CircleHelp,
|
CircleHelp,
|
||||||
|
CircleX,
|
||||||
Copy,
|
Copy,
|
||||||
CornerDownLeft,
|
CornerDownLeft,
|
||||||
Ellipsis,
|
Ellipsis,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export const messages: Record<Locale, Record<string, string>> = {
|
||||||
collapse: 'Collapse',
|
collapse: 'Collapse',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
expand: 'Expand',
|
expand: 'Expand',
|
||||||
|
prompt: 'Prompt',
|
||||||
reset: 'Reset',
|
reset: 'Reset',
|
||||||
submit: 'Submit',
|
submit: 'Submit',
|
||||||
},
|
},
|
||||||
|
|
@ -14,6 +15,7 @@ export const messages: Record<Locale, Record<string, string>> = {
|
||||||
collapse: '收起',
|
collapse: '收起',
|
||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
expand: '展开',
|
expand: '展开',
|
||||||
|
prompt: '提示',
|
||||||
reset: '重置',
|
reset: '重置',
|
||||||
submit: '提交',
|
submit: '提交',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
|
import type { AlertProps, BeforeCloseScope } from './alert';
|
||||||
|
|
||||||
|
import { h, ref, render } from 'vue';
|
||||||
|
|
||||||
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
|
import { Input } from '@vben-core/shadcn-ui';
|
||||||
|
import { isFunction, isString } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import Alert from './alert.vue';
|
||||||
|
|
||||||
|
const alerts = ref<Array<{ container: HTMLElement; instance: Component }>>([]);
|
||||||
|
|
||||||
|
const { $t } = useSimpleLocale();
|
||||||
|
|
||||||
|
export function vbenAlert(options: AlertProps): Promise<void>;
|
||||||
|
export function vbenAlert(
|
||||||
|
message: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
export function vbenAlert(
|
||||||
|
message: string,
|
||||||
|
title?: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
export function vbenAlert(
|
||||||
|
arg0: AlertProps | string,
|
||||||
|
arg1?: Partial<AlertProps> | string,
|
||||||
|
arg2?: Partial<AlertProps>,
|
||||||
|
): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const options: AlertProps = isString(arg0)
|
||||||
|
? {
|
||||||
|
content: arg0,
|
||||||
|
}
|
||||||
|
: { ...arg0 };
|
||||||
|
if (arg1) {
|
||||||
|
if (isString(arg1)) {
|
||||||
|
options.title = arg1;
|
||||||
|
} else if (!isString(arg1)) {
|
||||||
|
// 如果第二个参数是对象,则合并到选项中
|
||||||
|
Object.assign(options, arg1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg2 && !isString(arg2)) {
|
||||||
|
Object.assign(options, arg2);
|
||||||
|
}
|
||||||
|
// 创建容器元素
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.body.append(container);
|
||||||
|
|
||||||
|
// 创建一个引用,用于在回调中访问实例
|
||||||
|
const alertRef = { container, instance: null as any };
|
||||||
|
|
||||||
|
const props: AlertProps & Recordable<any> = {
|
||||||
|
onClosed: (isConfirm: boolean) => {
|
||||||
|
// 移除组件实例以及创建的所有dom(恢复页面到打开前的状态)
|
||||||
|
// 从alerts数组中移除该实例
|
||||||
|
alerts.value = alerts.value.filter((item) => item !== alertRef);
|
||||||
|
|
||||||
|
// 从DOM中移除容器
|
||||||
|
render(null, container);
|
||||||
|
if (container.parentNode) {
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 Promise,传递用户操作结果
|
||||||
|
if (isConfirm) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error('dialog cancelled'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
open: true,
|
||||||
|
title: options.title ?? $t.value('prompt'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建Alert组件的VNode
|
||||||
|
const vnode = h(Alert, props);
|
||||||
|
|
||||||
|
// 渲染组件到容器
|
||||||
|
render(vnode, container);
|
||||||
|
|
||||||
|
// 保存组件实例引用
|
||||||
|
alertRef.instance = vnode.component?.proxy as Component;
|
||||||
|
|
||||||
|
// 将实例和容器添加到alerts数组中
|
||||||
|
alerts.value.push(alertRef);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function vbenConfirm(options: AlertProps): Promise<void>;
|
||||||
|
export function vbenConfirm(
|
||||||
|
message: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
export function vbenConfirm(
|
||||||
|
message: string,
|
||||||
|
title?: string,
|
||||||
|
options?: Partial<AlertProps>,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
export function vbenConfirm(
|
||||||
|
arg0: AlertProps | string,
|
||||||
|
arg1?: Partial<AlertProps> | string,
|
||||||
|
arg2?: Partial<AlertProps>,
|
||||||
|
): Promise<void> {
|
||||||
|
const defaultProps: Partial<AlertProps> = {
|
||||||
|
showCancel: true,
|
||||||
|
};
|
||||||
|
if (!arg1) {
|
||||||
|
return isString(arg0)
|
||||||
|
? vbenAlert(arg0, defaultProps)
|
||||||
|
: vbenAlert({ ...defaultProps, ...arg0 });
|
||||||
|
} else if (!arg2) {
|
||||||
|
return isString(arg1)
|
||||||
|
? vbenAlert(arg0 as string, arg1, defaultProps)
|
||||||
|
: vbenAlert(arg0 as string, { ...defaultProps, ...arg1 });
|
||||||
|
}
|
||||||
|
return vbenAlert(arg0 as string, arg1 as string, {
|
||||||
|
...defaultProps,
|
||||||
|
...arg2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function vbenPrompt<T = any>(
|
||||||
|
options: Omit<AlertProps, 'beforeClose'> & {
|
||||||
|
beforeClose?: (scope: {
|
||||||
|
isConfirm: boolean;
|
||||||
|
value: T | undefined;
|
||||||
|
}) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
component?: Component;
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
defaultValue?: T;
|
||||||
|
modelPropName?: string;
|
||||||
|
},
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
const {
|
||||||
|
component: _component,
|
||||||
|
componentProps: _componentProps,
|
||||||
|
content,
|
||||||
|
defaultValue,
|
||||||
|
modelPropName: _modelPropName,
|
||||||
|
...delegated
|
||||||
|
} = options;
|
||||||
|
const contents: Component[] = [];
|
||||||
|
const modelValue = ref<T | undefined>(defaultValue);
|
||||||
|
if (isString(content)) {
|
||||||
|
contents.push(h('span', content));
|
||||||
|
} else {
|
||||||
|
contents.push(content);
|
||||||
|
}
|
||||||
|
const componentProps = _componentProps || {};
|
||||||
|
const modelPropName = _modelPropName || 'modelValue';
|
||||||
|
componentProps[modelPropName] = modelValue.value;
|
||||||
|
componentProps[`onUpdate:${modelPropName}`] = (val: any) => {
|
||||||
|
modelValue.value = val;
|
||||||
|
};
|
||||||
|
const componentRef = h(_component || Input, componentProps);
|
||||||
|
contents.push(componentRef);
|
||||||
|
const props: AlertProps & Recordable<any> = {
|
||||||
|
...delegated,
|
||||||
|
async beforeClose(scope: BeforeCloseScope) {
|
||||||
|
if (delegated.beforeClose) {
|
||||||
|
return await delegated.beforeClose({
|
||||||
|
...scope,
|
||||||
|
value: modelValue.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: h(
|
||||||
|
'div',
|
||||||
|
{ class: 'flex flex-col gap-2' },
|
||||||
|
{ default: () => contents },
|
||||||
|
),
|
||||||
|
onOpened() {
|
||||||
|
// 组件挂载完成后,自动聚焦到输入组件
|
||||||
|
if (
|
||||||
|
componentRef.component?.exposed &&
|
||||||
|
isFunction(componentRef.component.exposed.focus)
|
||||||
|
) {
|
||||||
|
componentRef.component.exposed.focus();
|
||||||
|
} else if (componentRef.el && isFunction(componentRef.el.focus)) {
|
||||||
|
componentRef.el.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await vbenConfirm(props);
|
||||||
|
return modelValue.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAllAlerts() {
|
||||||
|
alerts.value.forEach((alert) => {
|
||||||
|
// 从DOM中移除容器
|
||||||
|
render(null, alert.container);
|
||||||
|
if (alert.container.parentNode) {
|
||||||
|
alert.container.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alerts.value = [];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
|
export type BeforeCloseScope = {
|
||||||
|
isConfirm: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AlertProps = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: (
|
||||||
|
scope: BeforeCloseScope,
|
||||||
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 边框 */
|
||||||
|
bordered?: boolean;
|
||||||
|
/** 取消按钮的标题 */
|
||||||
|
cancelText?: string;
|
||||||
|
/** 是否居中显示 */
|
||||||
|
centered?: boolean;
|
||||||
|
/** 确认按钮的标题 */
|
||||||
|
confirmText?: string;
|
||||||
|
/** 弹窗容器的额外样式 */
|
||||||
|
containerClass?: string;
|
||||||
|
/** 弹窗提示内容 */
|
||||||
|
content: Component | string;
|
||||||
|
/** 弹窗内容的额外样式 */
|
||||||
|
contentClass?: string;
|
||||||
|
/** 弹窗的图标(在标题的前面) */
|
||||||
|
icon?: Component | IconType;
|
||||||
|
/** 是否显示取消按钮 */
|
||||||
|
showCancel?: boolean;
|
||||||
|
/** 弹窗标题 */
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
import type { AlertProps } from './alert';
|
||||||
|
|
||||||
|
import { computed, h, nextTick, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
|
import {
|
||||||
|
CircleAlert,
|
||||||
|
CircleCheckBig,
|
||||||
|
CircleHelp,
|
||||||
|
CircleX,
|
||||||
|
Info,
|
||||||
|
X,
|
||||||
|
} from '@vben-core/icons';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogTitle,
|
||||||
|
VbenButton,
|
||||||
|
VbenLoading,
|
||||||
|
VbenRenderContent,
|
||||||
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<AlertProps>(), {
|
||||||
|
bordered: true,
|
||||||
|
centered: true,
|
||||||
|
containerClass: 'w-[520px]',
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['closed', 'confirm', 'opened']);
|
||||||
|
const open = defineModel<boolean>('open', { default: false });
|
||||||
|
const { $t } = useSimpleLocale();
|
||||||
|
const components = globalShareState.getComponents();
|
||||||
|
const isConfirm = ref(false);
|
||||||
|
watch(open, async (val) => {
|
||||||
|
await nextTick();
|
||||||
|
if (val) {
|
||||||
|
isConfirm.value = false;
|
||||||
|
} else {
|
||||||
|
emits('closed', isConfirm.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const getIconRender = computed(() => {
|
||||||
|
let iconRender: Component | null = null;
|
||||||
|
if (props.icon) {
|
||||||
|
if (typeof props.icon === 'string') {
|
||||||
|
switch (props.icon) {
|
||||||
|
case 'error': {
|
||||||
|
iconRender = h(CircleX, {
|
||||||
|
style: { color: 'hsl(var(--destructive))' },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'info': {
|
||||||
|
iconRender = h(Info, { style: { color: 'hsl(var(--info))' } });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'question': {
|
||||||
|
iconRender = CircleHelp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'success': {
|
||||||
|
iconRender = h(CircleCheckBig, {
|
||||||
|
style: { color: 'hsl(var(--success))' },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'warning': {
|
||||||
|
iconRender = h(CircleAlert, {
|
||||||
|
style: { color: 'hsl(var(--warning))' },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
iconRender = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iconRender = props.icon ?? null;
|
||||||
|
}
|
||||||
|
return iconRender;
|
||||||
|
});
|
||||||
|
function handleConfirm() {
|
||||||
|
isConfirm.value = true;
|
||||||
|
emits('confirm');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancel() {
|
||||||
|
isConfirm.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
async function handleOpenChange(val: boolean) {
|
||||||
|
if (!val && props.beforeClose) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await props.beforeClose({ isConfirm: isConfirm.value });
|
||||||
|
if (res !== false) {
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
open.value = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<AlertDialog :open="open" @update:open="handleOpenChange">
|
||||||
|
<AlertDialogContent
|
||||||
|
:open="open"
|
||||||
|
:centered="centered"
|
||||||
|
@opened="emits('opened')"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
containerClass,
|
||||||
|
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
|
||||||
|
{
|
||||||
|
'border-border border': bordered,
|
||||||
|
'shadow-3xl': !bordered,
|
||||||
|
'top-1/2 !-translate-y-1/2': centered,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div :class="cn('relative flex-1 overflow-y-auto p-3', contentClass)">
|
||||||
|
<AlertDialogTitle v-if="title">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<component :is="getIconRender" class="mr-2" />
|
||||||
|
<span class="flex-auto">{{ $t(title) }}</span>
|
||||||
|
<AlertDialogCancel v-if="showCancel">
|
||||||
|
<VbenButton
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
class="rounded-full"
|
||||||
|
:disabled="loading"
|
||||||
|
@click="handleCancel"
|
||||||
|
>
|
||||||
|
<X class="text-muted-foreground size-4" />
|
||||||
|
</VbenButton>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
</div>
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
<div class="m-4 mb-6 min-h-[30px]">
|
||||||
|
<VbenRenderContent :content="content" render-br />
|
||||||
|
</div>
|
||||||
|
<VbenLoading v-if="loading" :spinning="loading" />
|
||||||
|
</AlertDialogDescription>
|
||||||
|
<div class="flex justify-end gap-x-2">
|
||||||
|
<AlertDialogCancel v-if="showCancel" :disabled="loading">
|
||||||
|
<component
|
||||||
|
:is="components.DefaultButton || VbenButton"
|
||||||
|
variant="ghost"
|
||||||
|
@click="handleCancel"
|
||||||
|
>
|
||||||
|
{{ cancelText || $t('cancel') }}
|
||||||
|
</component>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction>
|
||||||
|
<component
|
||||||
|
:is="components.PrimaryButton || VbenButton"
|
||||||
|
:loading="loading"
|
||||||
|
@click="handleConfirm"
|
||||||
|
>
|
||||||
|
{{ confirmText || $t('confirm') }}
|
||||||
|
</component>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export * from './alert';
|
||||||
|
|
||||||
|
export { default as Alert } from './alert.vue';
|
||||||
|
export {
|
||||||
|
vbenAlert as alert,
|
||||||
|
clearAllAlerts,
|
||||||
|
vbenConfirm as confirm,
|
||||||
|
vbenPrompt as prompt,
|
||||||
|
} from './AlertBuilder';
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
export * from './alert';
|
||||||
export * from './drawer';
|
export * from './drawer';
|
||||||
export * from './modal';
|
export * from './modal';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { Component, PropType } from 'vue';
|
||||||
|
|
||||||
import { defineComponent, h } from 'vue';
|
import { defineComponent, h } from 'vue';
|
||||||
|
|
||||||
import { isFunction, isObject } from '@vben-core/shared/utils';
|
import { isFunction, isObject, isString } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RenderContent',
|
name: 'RenderContent',
|
||||||
|
|
@ -14,6 +14,10 @@ export default defineComponent({
|
||||||
| undefined,
|
| undefined,
|
||||||
type: [Object, String, Function],
|
type: [Object, String, Function],
|
||||||
},
|
},
|
||||||
|
renderBr: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props, { attrs, slots }) {
|
setup(props, { attrs, slots }) {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -24,7 +28,20 @@ export default defineComponent({
|
||||||
(isObject(props.content) || isFunction(props.content)) &&
|
(isObject(props.content) || isFunction(props.content)) &&
|
||||||
props.content !== null;
|
props.content !== null;
|
||||||
if (!isComponent) {
|
if (!isComponent) {
|
||||||
return props.content;
|
if (props.renderBr && isString(props.content)) {
|
||||||
|
const lines = props.content.split('\n');
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
result.push(h('span', { key: i }, line));
|
||||||
|
if (i < lines.length - 1) {
|
||||||
|
result.push(h('br'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return props.content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return h(props.content as never, {
|
return h(props.content as never, {
|
||||||
...attrs,
|
...attrs,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogEmits, AlertDialogProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { AlertDialogRoot, useForwardPropsEmits } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogProps>();
|
||||||
|
const emits = defineEmits<AlertDialogEmits>();
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogRoot v-bind="forwarded">
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogRoot>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogActionProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { AlertDialogAction } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogActionProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogAction v-bind="props">
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogCancelProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { AlertDialogCancel } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogCancelProps>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogCancel v-bind="props">
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogCancel>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type {
|
||||||
|
AlertDialogContentEmits,
|
||||||
|
AlertDialogContentProps,
|
||||||
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
import type { ClassType } from '@vben-core/typings';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogPortal,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from 'radix-vue';
|
||||||
|
|
||||||
|
import AlertDialogOverlay from './AlertDialogOverlay.vue';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<
|
||||||
|
AlertDialogContentProps & {
|
||||||
|
centered?: boolean;
|
||||||
|
class?: ClassType;
|
||||||
|
modal?: boolean;
|
||||||
|
open?: boolean;
|
||||||
|
overlayBlur?: number;
|
||||||
|
zIndex?: number;
|
||||||
|
}
|
||||||
|
>(),
|
||||||
|
{ modal: true },
|
||||||
|
);
|
||||||
|
const emits = defineEmits<
|
||||||
|
AlertDialogContentEmits & { close: []; closed: []; opened: [] }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, modal: _modal, open: _open, ...delegated } = props;
|
||||||
|
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
|
const contentRef = ref<InstanceType<typeof AlertDialogContent> | null>(null);
|
||||||
|
function onAnimationEnd(event: AnimationEvent) {
|
||||||
|
// 只有在 contentRef 的动画结束时才触发 opened/closed 事件
|
||||||
|
if (event.target === contentRef.value?.$el) {
|
||||||
|
if (props.open) {
|
||||||
|
emits('opened');
|
||||||
|
} else {
|
||||||
|
emits('closed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
getContentRef: () => contentRef.value,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<Transition name="fade">
|
||||||
|
<AlertDialogOverlay
|
||||||
|
v-if="open && modal"
|
||||||
|
:style="{
|
||||||
|
...(zIndex ? { zIndex } : {}),
|
||||||
|
position: 'fixed',
|
||||||
|
backdropFilter:
|
||||||
|
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
|
||||||
|
}"
|
||||||
|
@click="() => emits('close')"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<AlertDialogContent
|
||||||
|
ref="contentRef"
|
||||||
|
:style="{ ...(zIndex ? { zIndex } : {}), position: 'fixed' }"
|
||||||
|
@animationend="onAnimationEnd"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { AlertDialogDescriptionProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import { AlertDialogDescription, useForwardProps } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogDescriptionProps & { class?: any }>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props;
|
||||||
|
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogDescription
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useScrollLock } from '@vben-core/composables';
|
||||||
|
|
||||||
|
useScrollLock();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="bg-overlay z-popup inset-0"></div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { AlertDialogTitleProps } from 'radix-vue';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
|
import { AlertDialogTitle, useForwardProps } from 'radix-vue';
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogTitleProps & { class?: any }>();
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props;
|
||||||
|
|
||||||
|
return delegated;
|
||||||
|
});
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogTitle
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="
|
||||||
|
cn('text-lg font-semibold leading-none tracking-tight', props.class)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</AlertDialogTitle>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export { default as AlertDialog } from './AlertDialog.vue';
|
||||||
|
export { default as AlertDialogAction } from './AlertDialogAction.vue';
|
||||||
|
export { default as AlertDialogCancel } from './AlertDialogCancel.vue';
|
||||||
|
export { default as AlertDialogContent } from './AlertDialogContent.vue';
|
||||||
|
export { default as AlertDialogDescription } from './AlertDialogDescription.vue';
|
||||||
|
export { default as AlertDialogTitle } from './AlertDialogTitle.vue';
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './accordion';
|
export * from './accordion';
|
||||||
|
export * from './alert-dialog';
|
||||||
export * from './avatar';
|
export * from './avatar';
|
||||||
export * from './badge';
|
export * from './badge';
|
||||||
export * from './breadcrumb';
|
export * from './breadcrumb';
|
||||||
|
|
|
||||||
|
|
@ -102,3 +102,11 @@
|
||||||
.vxe-tools--operate:not(:has(button)) {
|
.vxe-tools--operate:not(:has(button)) {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vxe-grid--layout-header-wrapper {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vxe-grid--layout-body-content-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ async function generateRoutesByBackend(
|
||||||
return [...options.routes, ...routes];
|
return [...options.routes, ...routes];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return [];
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/vue-query';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getMenuList } from '#/api';
|
||||||
|
|
||||||
|
const queryKey = ['demo', 'api', 'options'];
|
||||||
|
const count = 4;
|
||||||
|
|
||||||
|
const { dataUpdatedAt, promise: fetchDataFn } = useQuery({
|
||||||
|
// 在组件渲染期间预取数据
|
||||||
|
experimental_prefetchInRender: true,
|
||||||
|
// 获取接口数据的函数
|
||||||
|
queryFn: getMenuList,
|
||||||
|
queryKey,
|
||||||
|
// 每次组件挂载时都重新获取数据。如果不需要每次都重新获取就不要设置为always
|
||||||
|
refetchOnMount: 'always',
|
||||||
|
// 缓存时间
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchOptions() {
|
||||||
|
return await fetchDataFn.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
schema.push({
|
||||||
|
component: 'ApiSelect',
|
||||||
|
componentProps: {
|
||||||
|
api: fetchOptions,
|
||||||
|
class: 'w-full',
|
||||||
|
filterOption: (input: string, option: Recordable<any>) => {
|
||||||
|
return option.label.toLowerCase().includes(input.toLowerCase());
|
||||||
|
},
|
||||||
|
labelField: 'name',
|
||||||
|
showSearch: true,
|
||||||
|
valueField: 'id',
|
||||||
|
},
|
||||||
|
fieldName: `field${i}`,
|
||||||
|
label: `Select ${i}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Form] = useVbenForm({
|
||||||
|
schema,
|
||||||
|
showDefaultActions: false,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="mb-2 flex gap-2">
|
||||||
|
<div>以下{{ count }}个组件共用一个数据源。</div>
|
||||||
|
<div>缓存更新时间:{{ new Date(dataUpdatedAt).toLocaleString() }}</div>
|
||||||
|
</div>
|
||||||
|
<Form />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Card } from 'ant-design-vue';
|
import { refAutoReset } from '@vueuse/core';
|
||||||
|
import { Button, Card, Empty } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import ConcurrencyCaching from './concurrency-caching.vue';
|
||||||
import InfiniteQueries from './infinite-queries.vue';
|
import InfiniteQueries from './infinite-queries.vue';
|
||||||
import PaginatedQueries from './paginated-queries.vue';
|
import PaginatedQueries from './paginated-queries.vue';
|
||||||
import QueryRetries from './query-retries.vue';
|
import QueryRetries from './query-retries.vue';
|
||||||
|
|
||||||
|
const showCaching = refAutoReset(true, 1000);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -20,6 +24,17 @@ import QueryRetries from './query-retries.vue';
|
||||||
<Card title="错误重试">
|
<Card title="错误重试">
|
||||||
<QueryRetries />
|
<QueryRetries />
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card
|
||||||
|
title="并发和缓存"
|
||||||
|
v-spinning="!showCaching"
|
||||||
|
:body-style="{ minHeight: '330px' }"
|
||||||
|
>
|
||||||
|
<template #extra>
|
||||||
|
<Button @click="showCaching = false">重新加载</Button>
|
||||||
|
</template>
|
||||||
|
<ConcurrencyCaching v-if="showCaching" />
|
||||||
|
<Empty v-else description="正在加载..." />
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { onBeforeUnmount } from 'vue';
|
||||||
|
|
||||||
import { Button, Card, Flex } from 'ant-design-vue';
|
import {
|
||||||
|
alert,
|
||||||
|
clearAllAlerts,
|
||||||
|
confirm,
|
||||||
|
Page,
|
||||||
|
prompt,
|
||||||
|
useVbenModal,
|
||||||
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Button, Card, Flex, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import DocButton from '../doc-button.vue';
|
import DocButton from '../doc-button.vue';
|
||||||
import AutoHeightDemo from './auto-height-demo.vue';
|
import AutoHeightDemo from './auto-height-demo.vue';
|
||||||
|
|
@ -103,6 +112,62 @@ function openFormModal() {
|
||||||
})
|
})
|
||||||
.open();
|
.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAlert() {
|
||||||
|
alert({
|
||||||
|
content: '这是一个弹窗',
|
||||||
|
icon: 'success',
|
||||||
|
}).then(() => {
|
||||||
|
message.info('用户关闭了弹窗');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
// 清除所有弹窗
|
||||||
|
clearAllAlerts();
|
||||||
|
});
|
||||||
|
|
||||||
|
function openConfirm() {
|
||||||
|
confirm({
|
||||||
|
beforeClose({ isConfirm }) {
|
||||||
|
if (!isConfirm) return;
|
||||||
|
// 这里可以做一些异步操作
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(true);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
content: '这是一个确认弹窗',
|
||||||
|
icon: 'question',
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
message.success('用户确认了操作');
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.error('用户取消了操作');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openPrompt() {
|
||||||
|
prompt<string>({
|
||||||
|
async beforeClose({ isConfirm, value }) {
|
||||||
|
if (isConfirm && value === '芝士') {
|
||||||
|
message.error('不能吃芝士');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentProps: { placeholder: '不能吃芝士...' },
|
||||||
|
content: '中午吃了什么?',
|
||||||
|
icon: 'question',
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
message.success(`用户输入了:${res}`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
message.error('用户取消了输入');
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -195,6 +260,14 @@ function openFormModal() {
|
||||||
<Button type="primary" @click="openBlurModal">打开弹窗</Button>
|
<Button type="primary" @click="openBlurModal">打开弹窗</Button>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card class="w-[300px]" title="轻量提示弹窗">
|
||||||
|
<p>通过快捷方法创建动态提示弹窗,适合一些轻量的提示和确认、输入等</p>
|
||||||
|
<template #actions>
|
||||||
|
<Button type="primary" @click="openAlert">Alert</Button>
|
||||||
|
<Button type="primary" @click="openConfirm">Confirm</Button>
|
||||||
|
<Button type="primary" @click="openPrompt">Prompt</Button>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
2095
pnpm-lock.yaml
2095
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -21,22 +21,22 @@ catalog:
|
||||||
'@commitlint/cli': ^19.8.0
|
'@commitlint/cli': ^19.8.0
|
||||||
'@commitlint/config-conventional': ^19.8.0
|
'@commitlint/config-conventional': ^19.8.0
|
||||||
'@ctrl/tinycolor': ^4.1.0
|
'@ctrl/tinycolor': ^4.1.0
|
||||||
'@eslint/js': ^9.22.0
|
'@eslint/js': ^9.23.0
|
||||||
'@faker-js/faker': ^9.6.0
|
'@faker-js/faker': ^9.6.0
|
||||||
'@iconify/json': ^2.2.314
|
'@iconify/json': ^2.2.323
|
||||||
'@iconify/tailwind': ^1.2.0
|
'@iconify/tailwind': ^1.2.0
|
||||||
'@iconify/vue': ^4.3.0
|
'@iconify/vue': ^4.3.0
|
||||||
'@intlify/core-base': ^11.1.2
|
'@intlify/core-base': ^11.1.2
|
||||||
'@intlify/unplugin-vue-i18n': ^6.0.3
|
'@intlify/unplugin-vue-i18n': ^6.0.5
|
||||||
'@jspm/generator': ^2.5.1
|
'@jspm/generator': ^2.5.1
|
||||||
'@manypkg/get-packages': ^2.2.2
|
'@manypkg/get-packages': ^2.2.2
|
||||||
'@nolebase/vitepress-plugin-git-changelog': ^2.15.0
|
'@nolebase/vitepress-plugin-git-changelog': ^2.15.1
|
||||||
'@playwright/test': ^1.51.0
|
'@playwright/test': ^1.51.1
|
||||||
'@pnpm/workspace.read-manifest': ^1000.1.1
|
'@pnpm/workspace.read-manifest': ^1000.1.2
|
||||||
'@stylistic/stylelint-plugin': ^3.1.2
|
'@stylistic/stylelint-plugin': ^3.1.2
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.16
|
'@tailwindcss/typography': ^0.5.16
|
||||||
'@tanstack/vue-query': ^5.67.2
|
'@tanstack/vue-query': ^5.71.1
|
||||||
'@tanstack/vue-store': ^0.7.0
|
'@tanstack/vue-store': ^0.7.0
|
||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
|
|
@ -45,20 +45,20 @@ catalog:
|
||||||
'@types/lodash.clonedeep': ^4.5.9
|
'@types/lodash.clonedeep': ^4.5.9
|
||||||
'@types/lodash.get': ^4.4.9
|
'@types/lodash.get': ^4.4.9
|
||||||
'@types/lodash.isequal': ^4.5.8
|
'@types/lodash.isequal': ^4.5.8
|
||||||
'@types/lodash.set': ^4.3.2
|
'@types/lodash.set': ^4.3.9
|
||||||
'@types/node': ^22.13.10
|
'@types/node': ^22.13.17
|
||||||
'@types/nprogress': ^0.2.3
|
'@types/nprogress': ^0.2.3
|
||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
'@types/qs': ^6.9.18
|
'@types/qs': ^6.9.18
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@types/crypto-js': ^4.2.2
|
'@types/crypto-js': ^4.2.2
|
||||||
'@typescript-eslint/eslint-plugin': ^8.26.0
|
'@typescript-eslint/eslint-plugin': ^8.29.0
|
||||||
'@typescript-eslint/parser': ^8.26.0
|
'@typescript-eslint/parser': ^8.29.0
|
||||||
'@vee-validate/zod': ^4.15.0
|
'@vee-validate/zod': ^4.15.0
|
||||||
'@vite-pwa/vitepress': ^0.5.3
|
'@vite-pwa/vitepress': ^0.5.4
|
||||||
'@vitejs/plugin-vue': ^5.2.1
|
'@vitejs/plugin-vue': ^5.2.3
|
||||||
'@vitejs/plugin-vue-jsx': ^4.1.1
|
'@vitejs/plugin-vue-jsx': ^4.1.2
|
||||||
'@vue/reactivity': ^3.5.13
|
'@vue/reactivity': ^3.5.13
|
||||||
'@vue/shared': ^3.5.13
|
'@vue/shared': ^3.5.13
|
||||||
'@vue/test-utils': ^2.4.6
|
'@vue/test-utils': ^2.4.6
|
||||||
|
|
@ -67,8 +67,8 @@ catalog:
|
||||||
'@vueuse/integrations': ^12.8.2
|
'@vueuse/integrations': ^12.8.2
|
||||||
ant-design-vue: ^4.2.6
|
ant-design-vue: ^4.2.6
|
||||||
archiver: ^7.0.1
|
archiver: ^7.0.1
|
||||||
autoprefixer: ^10.4.20
|
autoprefixer: ^10.4.21
|
||||||
axios: ^1.8.2
|
axios: ^1.8.4
|
||||||
axios-mock-adapter: ^2.1.0
|
axios-mock-adapter: ^2.1.0
|
||||||
cac: ^6.7.14
|
cac: ^6.7.14
|
||||||
chalk: ^5.4.1
|
chalk: ^5.4.1
|
||||||
|
|
@ -77,10 +77,10 @@ catalog:
|
||||||
class-variance-authority: ^0.7.1
|
class-variance-authority: ^0.7.1
|
||||||
clsx: ^2.1.1
|
clsx: ^2.1.1
|
||||||
commitlint-plugin-function-rules: ^4.0.1
|
commitlint-plugin-function-rules: ^4.0.1
|
||||||
consola: ^3.4.0
|
consola: ^3.4.2
|
||||||
cross-env: ^7.0.3
|
cross-env: ^7.0.3
|
||||||
crypto-js: ^4.2.0
|
crypto-js: ^4.2.0
|
||||||
cspell: ^8.17.5
|
cspell: ^8.18.1
|
||||||
cssnano: ^7.0.6
|
cssnano: ^7.0.6
|
||||||
cz-git: ^1.11.1
|
cz-git: ^1.11.1
|
||||||
czg: ^1.11.1
|
czg: ^1.11.1
|
||||||
|
|
@ -89,18 +89,18 @@ catalog:
|
||||||
depcheck: ^1.4.7
|
depcheck: ^1.4.7
|
||||||
dotenv: ^16.4.7
|
dotenv: ^16.4.7
|
||||||
echarts: ^5.6.0
|
echarts: ^5.6.0
|
||||||
element-plus: ^2.9.6
|
element-plus: ^2.9.7
|
||||||
eslint: ^9.22.0
|
eslint: ^9.23.0
|
||||||
eslint-config-turbo: ^2.4.4
|
eslint-config-turbo: ^2.4.4
|
||||||
eslint-plugin-command: ^0.2.7
|
eslint-plugin-command: ^0.2.7
|
||||||
eslint-plugin-eslint-comments: ^3.2.0
|
eslint-plugin-eslint-comments: ^3.2.0
|
||||||
eslint-plugin-import-x: ^4.6.1
|
eslint-plugin-import-x: ^4.10.0
|
||||||
eslint-plugin-jsdoc: ^50.6.3
|
eslint-plugin-jsdoc: ^50.6.9
|
||||||
eslint-plugin-jsonc: ^2.19.1
|
eslint-plugin-jsonc: ^2.20.0
|
||||||
eslint-plugin-n: ^17.16.2
|
eslint-plugin-n: ^17.17.0
|
||||||
eslint-plugin-no-only-tests: ^3.3.0
|
eslint-plugin-no-only-tests: ^3.3.0
|
||||||
eslint-plugin-perfectionist: ^4.10.0
|
eslint-plugin-perfectionist: ^4.11.0
|
||||||
eslint-plugin-prettier: ^5.2.3
|
eslint-plugin-prettier: ^5.2.5
|
||||||
eslint-plugin-regexp: ^2.7.0
|
eslint-plugin-regexp: ^2.7.0
|
||||||
eslint-plugin-unicorn: ^56.0.1
|
eslint-plugin-unicorn: ^56.0.1
|
||||||
eslint-plugin-unused-imports: ^4.1.4
|
eslint-plugin-unused-imports: ^4.1.4
|
||||||
|
|
@ -117,7 +117,7 @@ catalog:
|
||||||
is-ci: ^4.1.0
|
is-ci: ^4.1.0
|
||||||
jsonc-eslint-parser: ^2.4.0
|
jsonc-eslint-parser: ^2.4.0
|
||||||
jsonwebtoken: ^9.0.2
|
jsonwebtoken: ^9.0.2
|
||||||
lint-staged: ^15.4.3
|
lint-staged: ^15.5.0
|
||||||
lodash.clonedeep: ^4.5.0
|
lodash.clonedeep: ^4.5.0
|
||||||
lodash.get: ^4.4.2
|
lodash.get: ^4.4.2
|
||||||
lodash.set: ^4.3.2
|
lodash.set: ^4.3.2
|
||||||
|
|
@ -125,13 +125,13 @@ catalog:
|
||||||
lucide-vue-next: ^0.469.0
|
lucide-vue-next: ^0.469.0
|
||||||
medium-zoom: ^1.1.0
|
medium-zoom: ^1.1.0
|
||||||
naive-ui: ^2.41.0
|
naive-ui: ^2.41.0
|
||||||
nitropack: ^2.11.6
|
nitropack: ^2.11.8
|
||||||
nprogress: ^0.2.0
|
nprogress: ^0.2.0
|
||||||
ora: ^8.2.0
|
ora: ^8.2.0
|
||||||
pinia: ^2.3.1
|
pinia: ^2.3.1
|
||||||
pinia-plugin-persistedstate: ^4.2.0
|
pinia-plugin-persistedstate: ^4.2.0
|
||||||
pkg-types: ^1.3.1
|
pkg-types: ^1.3.1
|
||||||
playwright: ^1.51.0
|
playwright: ^1.51.1
|
||||||
postcss: ^8.5.3
|
postcss: ^8.5.3
|
||||||
postcss-antd-fixes: ^0.2.0
|
postcss-antd-fixes: ^0.2.0
|
||||||
postcss-html: ^1.8.0
|
postcss-html: ^1.8.0
|
||||||
|
|
@ -146,11 +146,11 @@ catalog:
|
||||||
radix-vue: ^1.9.17
|
radix-vue: ^1.9.17
|
||||||
resolve.exports: ^2.0.3
|
resolve.exports: ^2.0.3
|
||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
rollup: ^4.35.0
|
rollup: ^4.39.0
|
||||||
rollup-plugin-visualizer: ^5.14.0
|
rollup-plugin-visualizer: ^5.14.0
|
||||||
sass: ^1.85.1
|
sass: ^1.86.1
|
||||||
sortablejs: ^1.15.6
|
sortablejs: ^1.15.6
|
||||||
stylelint: ^16.15.0
|
stylelint: ^16.17.0
|
||||||
stylelint-config-recess-order: ^5.1.1
|
stylelint-config-recess-order: ^5.1.1
|
||||||
stylelint-config-recommended: ^14.0.1
|
stylelint-config-recommended: ^14.0.1
|
||||||
stylelint-config-recommended-scss: ^14.1.0
|
stylelint-config-recommended-scss: ^14.1.0
|
||||||
|
|
@ -165,29 +165,29 @@ catalog:
|
||||||
theme-colors: ^0.1.0
|
theme-colors: ^0.1.0
|
||||||
tippy.js: ^6.2.5
|
tippy.js: ^6.2.5
|
||||||
turbo: ^2.4.4
|
turbo: ^2.4.4
|
||||||
typescript: ^5.7.3
|
typescript: ^5.8.2
|
||||||
unbuild: ^3.5.0
|
unbuild: ^3.5.0
|
||||||
unplugin-element-plus: ^0.9.1
|
unplugin-element-plus: ^0.9.1
|
||||||
vee-validate: ^4.15.0
|
vee-validate: ^4.15.0
|
||||||
vite: ^6.2.1
|
vite: ^6.2.4
|
||||||
vite-plugin-compression: ^0.5.1
|
vite-plugin-compression: ^0.5.1
|
||||||
vite-plugin-dts: ^4.5.3
|
vite-plugin-dts: ^4.5.3
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
vite-plugin-lazy-import: ^1.0.7
|
vite-plugin-lazy-import: ^1.0.7
|
||||||
vite-plugin-pwa: ^0.21.1
|
vite-plugin-pwa: ^0.21.2
|
||||||
vite-plugin-vue-devtools: ^7.7.2
|
vite-plugin-vue-devtools: ^7.7.2
|
||||||
vitepress: ^1.6.3
|
vitepress: ^1.6.3
|
||||||
vitepress-plugin-group-icons: ^1.3.6
|
vitepress-plugin-group-icons: ^1.3.8
|
||||||
vitest: ^2.1.9
|
vitest: ^2.1.9
|
||||||
vue: ^3.5.13
|
vue: ^3.5.13
|
||||||
vue-eslint-parser: ^9.4.3
|
vue-eslint-parser: ^9.4.3
|
||||||
vue-i18n: ^11.1.2
|
vue-i18n: ^11.1.2
|
||||||
vue-json-viewer: ^3.0.4
|
vue-json-viewer: ^3.0.4
|
||||||
vue-router: ^4.5.0
|
vue-router: ^4.5.0
|
||||||
vue-tippy: ^6.6.0
|
vue-tippy: ^6.7.0
|
||||||
vue-tsc: 2.1.10
|
vue-tsc: 2.1.10
|
||||||
vxe-pc-ui: ^4.4.8
|
vxe-pc-ui: ^4.5.11
|
||||||
vxe-table: 4.10.0
|
vxe-table: ^4.12.5
|
||||||
watermark-js-plus: ^1.5.8
|
watermark-js-plus: ^1.5.8
|
||||||
zod: ^3.24.2
|
zod: ^3.24.2
|
||||||
zod-defaults: ^0.1.3
|
zod-defaults: ^0.1.3
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue