mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-11 07:05:33 +08:00
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:
@@ -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';
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -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 };
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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:"
|
||||
|
||||
@@ -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'));
|
||||
@@ -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';
|
||||
@@ -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
12
pnpm-lock.yaml
generated
@@ -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:'
|
||||
|
||||
Reference in New Issue
Block a user