refactor(ui): adapter 抽取到 @daxpay/ui-biz 共享包

将 admin/merchant 各自的 component adapter 与 form adapter 迁移到
@daxpay/ui-biz/adapter(含 vxe-table 样式入口), 消除两端重复;
ui-biz 补充 exports、vxe-pc-ui/vxe-table/common-ui/types 依赖与 less sideEffects.
This commit is contained in:
DaxPay Dev
2026-07-25 11:36:20 +08:00
parent ba13e8fa7a
commit 7a4dbf1dc1
16 changed files with 55 additions and 693 deletions

View File

@@ -1,9 +0,0 @@
/* vxe-table 样式入口文件 */
/* 统一导入 vxe 相关样式 */
/* vxe 基础样式 */
@import 'vxe-table/lib/style.css';
@import 'vxe-pc-ui/lib/style.css';
/* 项目自定义 vxe 样式 */
@import './vxe-table.less';

View File

@@ -14,16 +14,18 @@ import Antd from 'antdv-next';
import VxeUI from 'vxe-pc-ui';
import VxeUITable from 'vxe-table';
import { setDark } from '#/adapter/component/vxe-table/vxe-table';
import { setDark } from '@daxpay/ui-biz/adapter/component/vxe-table';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import { initComponentAdapter } from '@daxpay/ui-biz/adapter/component';
import { initSetupVbenForm } from '@daxpay/ui-biz/adapter/form';
import App from './app.vue';
import { router } from './router';
// vxe 样式入口
import '#/adapter/component/vxe-table/index.less';
// vxe 样式: 基础库样式 + 项目自定义样式
import 'vxe-pc-ui/lib/style.css';
import 'vxe-table/lib/style.css';
import '@daxpay/ui-biz/adapter/component/vxe-table/style';
// 项目公共样式入口
import '#/styles/index.less';

View File

@@ -1,519 +0,0 @@
/**
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
/* eslint-disable vue/one-component-per-file */
import type { UploadChangeParam, UploadFile, UploadProps } from 'antdv-next';
import type { Component, Ref } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, defineAsyncComponent, defineComponent, h, ref, render, unref, watch } from 'vue';
import { ApiComponent, globalShareState, VCropper } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { isEmpty } from '@vben/utils';
import { Modal } from 'antdv-next';
import { IconPicker } from '#/components/icon-picker';
import { useMessage } from '#/hooks/useMessage';
const AutoComplete = defineAsyncComponent(() => import('antdv-next/dist/auto-complete/index'));
const Button = defineAsyncComponent(() => import('antdv-next/dist/button/index'));
const Checkbox = defineAsyncComponent(() => import('antdv-next/dist/checkbox/index'));
const CheckboxGroup = defineAsyncComponent(() => import('antdv-next/dist/checkbox/Group'));
const DatePicker = defineAsyncComponent(() => import('antdv-next/dist/date-picker/index'));
const Divider = defineAsyncComponent(() => import('antdv-next/dist/divider/index'));
const Input = defineAsyncComponent(() => import('antdv-next/dist/input/index'));
const InputNumber = defineAsyncComponent(() => import('antdv-next/dist/input-number/index'));
const InputPassword = defineAsyncComponent(() =>
import('antdv-next/dist/input/index').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(() => import('antdv-next/dist/mentions/index'));
const Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index'));
const RadioGroup = defineAsyncComponent(() => import('antdv-next/dist/radio/index').then((res) => res.RadioGroup));
const RangePicker = defineAsyncComponent(() =>
import('antdv-next/dist/date-picker/index').then((res) => res.DateRangePicker),
);
const Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index'));
const Select = defineAsyncComponent(() => import('antdv-next/dist/select/index'));
const Space = defineAsyncComponent(() => import('antdv-next/dist/space/index'));
const Switch = defineAsyncComponent(() => import('antdv-next/dist/switch/index'));
const Textarea = defineAsyncComponent(() => import('antdv-next/dist/input/TextArea'));
const TimePicker = defineAsyncComponent(() => import('antdv-next/dist/time-picker/index'));
const TreeSelect = defineAsyncComponent(() => import('antdv-next/dist/tree-select/index'));
const Cascader = defineAsyncComponent(() => import('antdv-next/dist/cascader/index'));
const Upload = defineAsyncComponent(() => import('antdv-next/dist/upload/index'));
const Image = defineAsyncComponent(() => import('antdv-next/dist/image/index'));
const PreviewGroup = defineAsyncComponent(() =>
import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup),
);
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return defineComponent({
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
// 占位符
const placeholder = props?.placeholder || attrs?.placeholder || $t(`ui.placeholder.${type}`);
// 透传组件暴露的方法
const innerRef = ref();
expose(
new Proxy(
{},
{
get: (_target, key) => innerRef.value?.[key],
has: (_target, key) => key in (innerRef.value || {}),
},
),
);
return () => h(component, { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, slots);
},
});
};
const withPreviewUpload = () => {
// 检查是否为图片文件的辅助函数
const isImageFile = (file: UploadFile): boolean => {
const imageExtensions = new Set(['bmp', 'gif', 'jpeg', 'jpg', 'png', 'svg', 'webp']);
if (file.url) {
try {
const pathname = new URL(file.url, 'http://localhost').pathname;
const ext = pathname.split('.').pop()?.toLowerCase();
return ext ? imageExtensions.has(ext) : false;
} catch {
const ext = file.url?.split('.').pop()?.toLowerCase();
return ext ? imageExtensions.has(ext) : false;
}
}
if (!file.type) {
const ext = file.name?.split('.').pop()?.toLowerCase();
return ext ? imageExtensions.has(ext) : false;
}
return file.type.startsWith('image/');
};
// 创建默认的上传按钮插槽
const createDefaultSlotsWithUpload = (listType: string, placeholder: string) => {
switch (listType) {
case 'picture-card': {
return {
default: () => placeholder,
};
}
default: {
return {
default: () =>
h(
Button,
{
icon: h(IconifyIcon, {
icon: 'ant-design:upload-outlined',
class: 'mb-1 size-4',
}),
},
() => placeholder,
),
};
}
}
};
// 构建预览图片组
const previewImage = async (file: UploadFile, visible: Ref<boolean>, fileList: Ref<UploadProps['fileList']>) => {
// 如果当前文件不是图片,直接打开
if (!isImageFile(file)) {
if (file.url) {
window.open(file.url, '_blank');
} else if (file.preview) {
window.open(file.preview, '_blank');
} else {
// 预览警告
const { message } = useMessage();
message.error($t('ui.formRules.previewWarning'));
}
return;
}
// 对于图片文件,继续使用预览组
const [ImageComponent, PreviewGroupComponent] = await Promise.all([Image, PreviewGroup]);
const getBase64 = (file: File) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.addEventListener('load', () => resolve(reader.result));
reader.addEventListener('error', (error) => reject(error));
});
};
// 从fileList中过滤出所有图片文件
const imageFiles = (unref(fileList) || []).filter((element) => isImageFile(element));
// 为所有没有预览地址的图片生成预览
for (const imgFile of imageFiles) {
if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
}
}
const container: HTMLElement | null = document.createElement('div');
document.body.append(container);
// 用于追踪组件是否已卸载
let isUnmounted = false;
const PreviewWrapper = {
setup() {
return () => {
if (isUnmounted) return null;
return h(
PreviewGroupComponent,
{
class: 'hidden',
preview: {
open: visible.value,
// 设置初始显示的图片索引
current: imageFiles.findIndex((f) => f.uid === file.uid),
onOpenChange: (value: boolean) => {
visible.value = value;
if (!value) {
// 延迟清理,确保动画完成
setTimeout(() => {
if (!isUnmounted && container) {
isUnmounted = true;
render(null, container);
container.remove();
}
}, 300);
}
},
},
},
() =>
// 渲染所有图片文件
imageFiles.map((imgFile) =>
h(ImageComponent, {
key: imgFile.uid,
src: imgFile.url || imgFile.preview,
}),
),
);
};
},
};
render(h(PreviewWrapper), container);
};
// 图片裁剪操作
const cropImage = (file: File, aspectRatio: string | undefined) => {
return new Promise((resolve, reject) => {
const container: HTMLElement | null = document.createElement('div');
document.body.append(container);
// 用于追踪组件是否已卸载
let isUnmounted = false;
let objectUrl: null | string = null;
const open = ref<boolean>(true);
const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
const closeModal = () => {
open.value = false;
// 延迟清理,确保动画完成
setTimeout(() => {
if (!isUnmounted && container) {
if (objectUrl) {
URL.revokeObjectURL(objectUrl);
}
isUnmounted = true;
render(null, container);
container.remove();
}
}, 300);
};
const CropperWrapper = {
setup() {
return () => {
if (isUnmounted) return null;
if (!objectUrl) {
objectUrl = URL.createObjectURL(file);
}
return h(
Modal,
{
open: open.value,
title: h('div', {}, [
// 图片裁剪标题
$t('ui.crop.title'),
h(
'span',
{
class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
},
// 图片裁剪标题提示
$t('ui.crop.titleTip', [aspectRatio]),
),
]),
centered: true,
width: 548,
keyboard: false,
maskClosable: false,
closable: false,
// 取消
cancelText: $t('common.cancel'),
// 确认裁剪
okText: $t('ui.crop.confirm'),
destroyOnHidden: true,
onOk: async () => {
const cropper = cropperRef.value;
if (!cropper) {
reject(new Error('Cropper not found'));
closeModal();
return;
}
try {
const dataUrl = await cropper.getCropImage();
resolve(dataUrl);
} catch {
// 裁剪失败提示
reject(new Error($t('ui.crop.errorTip')));
} finally {
closeModal();
}
},
onCancel() {
resolve('');
closeModal();
},
},
() =>
h(VCropper, {
ref: (ref: any) => (cropperRef.value = ref),
img: objectUrl as string,
aspectRatio,
}),
);
};
},
};
render(h(CropperWrapper), container);
});
};
return defineComponent({
name: 'AUpload',
emits: ['update:modelValue'],
setup: (props: any, { attrs, slots, emit }: { attrs: any; emit: any; slots: any }) => {
const previewVisible = ref<boolean>(false);
// 上传占位符
const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`);
const listType = attrs?.listType || attrs?.['list-type'] || 'text';
const fileList = ref<UploadProps['fileList']>(attrs?.fileList || attrs?.['file-list'] || []);
const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);
const aspectRatio = computed(() => attrs?.aspectRatio ?? attrs?.['aspect-ratio']);
const handleBeforeUpload = async (file: UploadFile, originFileList: Array<File>) => {
if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
// 文件大小限制提示
const { message } = useMessage();
message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
file.status = 'removed';
return false;
}
// 多选或者非图片不唤起裁剪框
if (attrs.crop && !attrs.multiple && originFileList[0] && isImageFile(file)) {
file.status = 'removed';
// antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取
const blob = await cropImage(originFileList[0], aspectRatio.value);
return new Promise((resolve, reject) => {
if (!blob) {
// 裁剪失败提示
return reject(new Error($t('ui.crop.errorTip')));
}
resolve(blob);
});
}
return attrs.beforeUpload?.(file) ?? true;
};
const handleChange = (event: UploadChangeParam) => {
try {
// 行内写法 handleChange: (event) => {}
attrs.handleChange?.(event);
// template写法 @handle-change="(event) => {}"
attrs.onHandleChange?.(event);
} catch (error) {
// Avoid breaking internal v-model sync on user handler errors
console.error(error);
}
fileList.value = event.fileList.filter((file) => file.status !== 'removed');
emit('update:modelValue', event.fileList?.length ? fileList.value : undefined);
};
const handlePreview = async (file: UploadFile) => {
previewVisible.value = true;
await previewImage(file, previewVisible, fileList);
};
const renderUploadButton = (): any => {
const isDisabled = attrs.disabled;
// 如果禁用,不渲染上传按钮
if (isDisabled) {
return null;
}
// 否则渲染默认上传按钮
return isEmpty(slots) ? createDefaultSlotsWithUpload(listType, placeholder) : slots;
};
// 可以监听到表单API设置的值
watch(
() => attrs.modelValue,
(res) => {
fileList.value = res;
},
);
return () =>
h(
Upload,
{
...props,
...attrs,
fileList: fileList.value,
beforeUpload: handleBeforeUpload,
onChange: handleChange,
onPreview: handlePreview,
},
renderUploadButton(),
);
},
});
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiCascader'
| 'ApiSelect'
| 'ApiTreeSelect'
| 'AutoComplete'
| 'Cascader'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'IconPicker'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
component: Cascader,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
}),
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: Select,
loadingSlot: 'suffixIcon',
modelPropName: 'value',
visibleEvent: 'onVisibleChange',
}),
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
}),
AutoComplete,
Cascader,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
modelValueProp: 'modelValue',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
RadioGroup,
RangePicker,
Rate,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload: withPreviewUpload(),
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
const { notification } = useMessage();
notification.success({
description: content,
title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };

View File

@@ -1,9 +0,0 @@
/* vxe-table 样式入口文件 */
/* 统一导入 vxe 相关样式 */
/* vxe 基础样式 */
@import 'vxe-table/lib/style.css';
@import 'vxe-pc-ui/lib/style.css';
/* 项目自定义 vxe 样式 */
@import './vxe-table.less';

View File

@@ -1,20 +0,0 @@
/* vxe模态框样式 */
.vxe-modal--wrapper {
/* 层级与保持antdv一致 */
z-index: 1000 !important;
}
/* vxe表格相关 */
.vxe-custom--option-wrapper {
/* 需要低于顶栏 */
z-index: 8 !important;
}
.vxe-table--border-line {
/* 需要低于顶栏 */
z-index: 8 !important;
}
/* 工具栏自定义列配置, 优化高度和定位 */
.vxe-table-custom-wrapper.is--active {
max-height: 500px !important;
top: 0 !important;
}

View File

@@ -1,76 +0,0 @@
import enUS from 'vxe-pc-ui/lib/language/en-US';
import idID from 'vxe-pc-ui/lib/language/id-ID';
import jaJP from 'vxe-pc-ui/lib/language/ja-JP';
import koKR from 'vxe-pc-ui/lib/language/ko-KR';
import msMY from 'vxe-pc-ui/lib/language/ms-MY';
import thTH from 'vxe-pc-ui/lib/language/th-TH';
import viVN from 'vxe-pc-ui/lib/language/vi-VN';
import zhCN from 'vxe-pc-ui/lib/language/zh-CN';
import zhHK from 'vxe-pc-ui/lib/language/zh-HK';
import zhTW from 'vxe-pc-ui/lib/language/zh-TW';
import VXETable from 'vxe-table';
import { VxeUI } from 'vxe-table';
// 国际化配置
// 不知道为什么, 必须添加default之后才能正常使用
VxeUI.setI18n('en-US', enUS.default);
VxeUI.setI18n('zh-CN', zhCN.default);
VxeUI.setI18n('zh-TW', zhTW.default);
VxeUI.setI18n('zh-HK', zhHK.default);
VxeUI.setI18n('ja-JP', jaJP.default);
VxeUI.setI18n('ko-KR', koKR.default);
VxeUI.setI18n('id-ID', idID.default);
VxeUI.setI18n('vi-VN', viVN.default);
VxeUI.setI18n('th-TH', thTH.default);
VxeUI.setI18n('ms-MY', msMY.default);
/**
* 是否设置暗黑模式
* @param dark
*/
export function setDark(dark: boolean) {
if (dark) {
VXETable.setTheme('dark');
} else {
VXETable.setTheme('light');
}
}
/**
* 配置
*/
VXETable.setConfig({
// 表格配置
table: {
border: true,
stripe: true,
round: true,
showOverflow: 'title',
showHeaderOverflow: 'title',
size: 'medium',
tooltipConfig: {
enterable: true,
},
columnConfig: {
resizable: true,
useKey: true,
isHover: true,
},
rowConfig: {
isCurrent: true,
isHover: true,
},
},
// 工具条配置
toolbar: {
custom: true,
buttons: [],
tools: [],
size: 'medium',
},
// 分页配置
pager: {
border: true,
size: 'medium',
},
});

View File

@@ -1,46 +0,0 @@
import type { VbenFormSchema as FormSchema, VbenFormProps } from '@vben/common-ui';
import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
},
},
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (value === undefined || value === null) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
}
const useVbenForm = useForm<ComponentType>;
export { initSetupVbenForm, useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };

View File

@@ -14,16 +14,18 @@ import Antd from 'antdv-next';
import VxeUI from 'vxe-pc-ui';
import VxeUITable from 'vxe-table';
import { setDark } from '#/adapter/component/vxe-table/vxe-table';
import { setDark } from '@daxpay/ui-biz/adapter/component/vxe-table';
import { $t, setupI18n } from '#/locales';
import { initComponentAdapter } from './adapter/component';
import { initSetupVbenForm } from './adapter/form';
import { initComponentAdapter } from '@daxpay/ui-biz/adapter/component';
import { initSetupVbenForm } from '@daxpay/ui-biz/adapter/form';
import App from './app.vue';
import { router } from './router';
// vxe 样式入口
import '#/adapter/component/vxe-table/index.less';
// vxe 样式: 基础库样式 + 项目自定义样式
import 'vxe-pc-ui/lib/style.css';
import 'vxe-table/lib/style.css';
import '@daxpay/ui-biz/adapter/component/vxe-table/style';
// 项目公共样式入口
import '#/styles/index.less';

View File

@@ -5,6 +5,7 @@
"type": "module",
"sideEffects": [
"**/*.css",
"**/*.less",
"**/*.vue",
"**/*.svg"
],
@@ -29,6 +30,21 @@
"types": "./src/request/index.ts",
"default": "./src/request/index.ts"
},
"./adapter/component": {
"types": "./src/adapter/component/index.ts",
"default": "./src/adapter/component/index.ts"
},
"./adapter/form": {
"types": "./src/adapter/form.ts",
"default": "./src/adapter/form.ts"
},
"./adapter/component/vxe-table": {
"types": "./src/adapter/component/vxe-table/vxe-table.ts",
"default": "./src/adapter/component/vxe-table/vxe-table.ts"
},
"./adapter/component/vxe-table/style": {
"default": "./src/adapter/component/vxe-table/index.less"
},
"./enums/clientCode": {
"types": "./src/enums/clientCode.ts",
"default": "./src/enums/clientCode.ts"
@@ -181,19 +197,23 @@
"dependencies": {
"@vben-core/icons": "workspace:*",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/preferences": "workspace:*",
"@vben/request": "workspace:*",
"@vben/stores": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"@types/qrcode": "catalog:",
"antdv-next": "catalog:",
"qrcode": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"
"vue-router": "catalog:",
"vxe-pc-ui": "catalog:",
"vxe-table": "catalog:"
},
"devDependencies": {
"axios": "catalog:"

View File

@@ -21,8 +21,8 @@ import { isEmpty } from '@vben/utils';
import { Modal } from 'antdv-next';
import { IconPicker } from '#/components/icon-picker';
import { useMessage } from '#/hooks/useMessage';
import { IconPicker } from '../../components/icon-picker';
import { useMessage } from '../../hooks/useMessage';
const AutoComplete = defineAsyncComponent(() => import('antdv-next/dist/auto-complete/index'));
const Button = defineAsyncComponent(() => import('antdv-next/dist/button/index'));

View File

@@ -0,0 +1,5 @@
/* vxe-table 项目自定义样式 */
/* vxe 基础库样式vxe-table / vxe-pc-ui由各 app 的 bootstrap.ts 通过 JS import 引入, */
/* 避免包内 less @import node_modules 的 .css 在 postcss 阶段解析失败 */
@import './vxe-table.less';

View File

@@ -1,8 +1,8 @@
<script lang="ts" setup>
import { App } from 'antdv-next';
import DeleteConfirmModal from './delete-confirm/DeleteConfirmModal.vue';
import { setAntdAppContext } from '../hooks/useMessage';
import DeleteConfirmModal from './delete-confirm/DeleteConfirmModal.vue';
setAntdAppContext(App.useApp());
</script>

12
pnpm-lock.yaml generated
View File

@@ -1293,6 +1293,9 @@ importers:
'@vben/access':
specifier: workspace:*
version: link:../../effects/access
'@vben/common-ui':
specifier: workspace:*
version: link:../../effects/common-ui
'@vben/hooks':
specifier: workspace:*
version: link:../../effects/hooks
@@ -1311,6 +1314,9 @@ importers:
'@vben/stores':
specifier: workspace:*
version: link:../../stores
'@vben/types':
specifier: workspace:*
version: link:../../types
'@vben/utils':
specifier: workspace:*
version: link:../../utils
@@ -1329,6 +1335,12 @@ importers:
vue-router:
specifier: 'catalog:'
version: 5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.38(typescript@5.9.3)))(vite@8.1.3(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(less@4.6.6)(sass-embedded@1.100.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3))
vxe-pc-ui:
specifier: 'catalog:'
version: 4.15.7(vue@3.5.38(typescript@5.9.3))
vxe-table:
specifier: 'catalog:'
version: 4.19.17(vue@3.5.38(typescript@5.9.3))
devDependencies:
axios:
specifier: 'catalog:'