diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 9da1daba8..d4e28662b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,8 @@ "recommendations": [ // Vue 3 的语言支持 "Vue.volar", + // 将 eslint 集成到 VS Code 中。 + "dbaeumer.vscode-eslint", // 将 oxlint 集成到 VS Code 中。 "oxc.oxc-vscode", // Visual Studio Code 的官方 Stylelint 扩展 @@ -10,6 +12,8 @@ "oxc.oxc-vscode", // 支持 dotenv 文件语法 "mikestead.dotenv", + // YAML 语言支持,供 ESLint 校验 pnpm-workspace.yaml 等文件 + "redhat.vscode-yaml", // 源代码的拼写检查器 "streetsidesoftware.code-spell-checker", // Tailwind CSS 的官方 VS Code 插件 diff --git a/.vscode/settings.json b/.vscode/settings.json index d58dc940c..ae1f5c929 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "tailwindCSS.experimental.configFile": "packages/@core/base/design/src/css/global.css", + "tailwindCSS.experimental.configFile": "internal/tailwind-config/src/theme.css", "tailwindCSS.lint.suggestCanonicalClasses": "ignore", // workbench "workbench.list.smoothScrolling": true, @@ -41,7 +41,9 @@ "oxc.typeAware": true, "oxc.configPath": "oxlint.config.ts", "oxc.fmt.configPath": "oxfmt.config.ts", + "eslint.useFlatConfig": true, "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", "source.fixAll.oxc": "explicit", "source.fixAll.stylelint": "explicit", "source.organizeImports": "never" @@ -171,7 +173,7 @@ "emmet.triggerExpansionOnTab": false, "errorLens.enabledDiagnosticLevels": ["warning", "error"], - "errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"], + "errorLens.excludeBySource": ["cSpell", "Grammarly"], "stylelint.enable": true, "stylelint.packageManager": "pnpm", diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index 5bac14d67..def7ff2f9 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -14,6 +14,7 @@ import type { import type { Component, Ref } from 'vue'; import type { BaseFormComponentType } from '@vben/common-ui'; +import type { Sortable } from '@vben/hooks'; import type { Recordable } from '@vben/types'; import { @@ -21,6 +22,9 @@ import { defineAsyncComponent, defineComponent, h, + nextTick, + onMounted, + onUnmounted, ref, render, unref, @@ -33,6 +37,7 @@ import { IconPicker, VCropper, } from '@vben/common-ui'; +import { useSortable } from '@vben/hooks'; import { IconifyIcon } from '@vben/icons'; import { $t } from '@vben/locales'; import { isEmpty } from '@vben/utils'; @@ -132,260 +137,261 @@ const withDefaultPlaceholder = ( }); }; -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; - } +const IMAGE_EXTENSIONS = new Set([ + 'bmp', + 'gif', + 'jpeg', + 'jpg', + 'png', + 'svg', + 'webp', +]); + +/** + * 检查是否为图片文件 + */ +function isImageFile(file: UploadFile): boolean { + if (file.url) { + try { + const pathname = new URL(file.url, 'http://localhost').pathname; + const ext = pathname.split('.').pop()?.toLowerCase(); + return ext ? IMAGE_EXTENSIONS.has(ext) : false; + } catch { + const ext = file.url?.split('.').pop()?.toLowerCase(); + return ext ? IMAGE_EXTENSIONS.has(ext) : false; } - if (!file.type) { - const ext = file.name?.split('.').pop()?.toLowerCase(); - return ext ? imageExtensions.has(ext) : false; - } - return file.type.startsWith('image/'); + } + if (!file.type) { + const ext = file.name?.split('.').pop()?.toLowerCase(); + return ext ? IMAGE_EXTENSIONS.has(ext) : false; + } + return file.type.startsWith('image/'); +} + +/** + * 创建默认的上传按钮插槽 + */ +function createDefaultUploadSlots(listType: string, placeholder: string) { + if (listType === 'picture-card') { + return { default: () => placeholder }; + } + return { + default: () => + h( + Button, + { + icon: h(IconifyIcon, { + icon: 'ant-design:upload-outlined', + class: 'mb-1 size-4', + }), + }, + () => placeholder, + ), }; - // 创建默认的上传按钮插槽 - 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', - }), +} + +/** + * 获取文件的 Base64 + */ +function getBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.addEventListener('load', () => resolve(reader.result as string)); + reader.addEventListener('error', reject); + }); +} + +/** + * 预览图片 + */ +async function previewImage( + file: UploadFile, + visible: Ref, + fileList: Ref, +) { + // 非图片文件直接打开链接 + if (!isImageFile(file)) { + const url = file.url || file.preview; + if (url) { + window.open(url, '_blank'); + } else { + message.error($t('ui.formRules.previewWarning')); + } + return; + } + + const [ImageComponent, PreviewGroupComponent] = await Promise.all([ + Image, + PreviewGroup, + ]); + + // 过滤图片文件并生成预览 + const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f)); + + for (const imgFile of imageFiles) { + if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) { + imgFile.preview = await getBase64(imgFile.originFileObj); + } + } + + const container = document.createElement('div'); + document.body.append(container); + let isUnmounted = false; + + const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid); + + const PreviewWrapper = { + setup() { + return () => { + if (isUnmounted) return null; + return h( + PreviewGroupComponent, + { + class: 'hidden', + preview: { + visible: visible.value, + current: currentIndex, + onVisibleChange: (value: boolean) => { + visible.value = value; + if (!value) { + setTimeout(() => { + if (!isUnmounted && container) { + isUnmounted = true; + render(null, container); + container.remove(); + } + }, 300); + } }, - () => placeholder, + }, + }, + () => + imageFiles.map((imgFile) => + h(ImageComponent, { + key: imgFile.uid, + src: imgFile.url || imgFile.preview, + }), ), - }; - } - } + ); + }; + }, }; - // 构建预览图片组 - const previewImage = async ( - file: UploadFile, - visible: Ref, - fileList: Ref, - ) => { - // 如果当前文件不是图片,直接打开 - if (!isImageFile(file)) { - if (file.url) { - window.open(file.url, '_blank'); - } else if (file.preview) { - window.open(file.preview, '_blank'); - } else { - message.error($t('ui.formRules.previewWarning')); - } - return; - } - // 对于图片文件,继续使用预览组 - const [ImageComponent, PreviewGroupComponent] = await Promise.all([ - Image, - PreviewGroup, - ]); + render(h(PreviewWrapper), container); +} - 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'); +/** + * 图片裁剪操作 + */ +function cropImage(file: File, aspectRatio: string | undefined) { + return new Promise((resolve, reject) => { + const container = document.createElement('div'); document.body.append(container); - // 用于追踪组件是否已卸载 let isUnmounted = false; + let objectUrl: null | string = null; - const PreviewWrapper = { + const open = ref(true); + const cropperRef = ref | 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( - PreviewGroupComponent, + Modal, { - class: 'hidden', - preview: { - visible: visible.value, - // 设置初始显示的图片索引 - current: imageFiles.findIndex((f) => f.uid === file.uid), - onVisibleChange: (value: boolean) => { - visible.value = value; - if (!value) { - // 延迟清理,确保动画完成 - setTimeout(() => { - if (!isUnmounted && container) { - isUnmounted = true; - render(null, container); - container.remove(); - } - }, 300); + 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'), + destroyOnClose: true, + onOk: async () => { + const cropper = cropperRef.value; + if (!cropper) { + reject(new Error('Cropper not found')); + closeModal(); + return; + } + try { + const dataUrl = await cropper.getCropImage(); + if (dataUrl) { + resolve(dataUrl); + } else { + reject(new Error($t('ui.crop.errorTip'))); } - }, + } catch { + reject(new Error($t('ui.crop.errorTip'))); + } finally { + closeModal(); + } + }, + onCancel() { + resolve(''); + closeModal(); }, }, () => - // 渲染所有图片文件 - imageFiles.map((imgFile) => - h(ImageComponent, { - key: imgFile.uid, - src: imgFile.url || imgFile.preview, - }), - ), + h(VCropper, { + ref: (ref: any) => (cropperRef.value = ref), + img: objectUrl as string, + aspectRatio, + }), ); }; }, }; - 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(true); - const cropperRef = ref | 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'), - destroyOnClose: 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); - }); - }; + render(h(CropperWrapper), container); + }); +} +/** + * 带预览功能的上传组件 + */ +const withPreviewUpload = () => { return defineComponent({ name: Upload.name, emits: ['update:modelValue'], - setup: ( + setup( props: any, { attrs, slots, emit }: { attrs: any; emit: any; slots: any }, - ) => { + ) { const previewVisible = ref(false); - - const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`); - + const placeholder = attrs?.placeholder || $t('ui.placeholder.upload'); const listType = attrs?.listType || attrs?.['list-type'] || 'text'; - const fileList = ref( attrs?.fileList || attrs?.['file-list'] || [], ); @@ -399,12 +405,14 @@ const withPreviewUpload = () => { file: UploadFile, originFileList: Array, ) => { + // 文件大小限制 if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) { message.error($t('ui.formRules.sizeLimit', [maxSize.value])); file.status = 'removed'; return false; } - // 多选或者非图片不唤起裁剪框 + + // 图片裁剪处理 if ( attrs.crop && !attrs.multiple && @@ -412,14 +420,11 @@ const withPreviewUpload = () => { 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); - }); + if (!blob) { + throw new Error($t('ui.crop.errorTip')); + } + return blob; } return attrs.beforeUpload?.(file) ?? true; @@ -427,12 +432,9 @@ const withPreviewUpload = () => { 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( @@ -449,21 +451,88 @@ const withPreviewUpload = () => { await previewImage(file, previewVisible, fileList); }; - const renderUploadButton = (): any => { - const isDisabled = attrs.disabled; - - // 如果禁用,不渲染上传按钮 - if (isDisabled) { - return null; - } - - // 否则渲染默认上传按钮 + const renderUploadButton = () => { + if (attrs.disabled) return null; return isEmpty(slots) - ? createDefaultSlotsWithUpload(listType, placeholder) + ? createDefaultUploadSlots(listType, placeholder) : slots; }; - // 可以监听到表单API设置的值 + // 拖拽排序 + const draggable = computed( + () => (attrs.draggable ?? false) && !attrs.disabled, + ); + const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`; + const sortableInstance = ref(null); + + const styleId = `upload-drag-style-${uploadId}`; + + function injectDragStyle() { + if (!document.querySelector(`[id="${styleId}"]`)) { + const style = document.createElement('style'); + style.id = styleId; + style.textContent = ` + [data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; } + [data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); } + `; + document.head.append(style); + } + } + + function removeDragStyle() { + document.querySelector(`[id="${styleId}"]`)?.remove(); + } + + async function initSortable(retryCount = 0) { + if (!draggable.value) return; + + injectDragStyle(); + await nextTick(); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const container = document.querySelector( + `[data-upload-id="${uploadId}"] .ant-upload-list`, + ) as HTMLElement; + + if (!container) { + if (retryCount < 5) { + setTimeout(() => initSortable(retryCount + 1), 200); + } + return; + } + + const { initializeSortable } = useSortable(container, { + animation: 300, + delay: 400, + delayOnTouchOnly: true, + filter: + '.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading', + onEnd: (evt) => { + const { oldIndex, newIndex } = evt; + if ( + oldIndex === undefined || + newIndex === undefined || + oldIndex === newIndex + ) { + return; + } + + const list = [...(fileList.value || [])]; + const [movedItem] = list.splice(oldIndex, 1); + if (movedItem) { + list.splice(newIndex, 0, movedItem); + fileList.value = list; + } + + attrs.onDragSort?.(oldIndex, newIndex); + emit('update:modelValue', fileList.value); + }, + }); + + sortableInstance.value = await initializeSortable(); + } + + // 监听表单值变化 watch( () => attrs.modelValue, (res) => { @@ -471,18 +540,28 @@ const withPreviewUpload = () => { }, ); + onMounted(initSortable); + onUnmounted(() => { + sortableInstance.value?.destroy(); + removeDragStyle(); + }); + return () => h( - Upload, - { - ...props, - ...attrs, - fileList: fileList.value, - beforeUpload: handleBeforeUpload, - onChange: handleChange, - onPreview: handlePreview, - }, - renderUploadButton(), + 'div', + { 'data-upload-id': uploadId, class: 'w-full' }, + h( + Upload, + { + ...props, + ...attrs, + fileList: fileList.value, + beforeUpload: handleBeforeUpload, + onChange: handleChange, + onPreview: handlePreview, + }, + renderUploadButton() as any, + ), ); }, }); diff --git a/apps/web-antd/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js b/apps/web-antd/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js index 325726a9b..3956be680 100644 --- a/apps/web-antd/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js +++ b/apps/web-antd/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js @@ -3,7 +3,7 @@ import { isFunction, isObject } from '@vben/utils'; const WILDCARD = '*'; function CamundaModdleExtension(eventBus) { - // eslint-disable-next-line unicorn/no-this-assignment + // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias const self = this; eventBus.on('moddleCopy.canCopyProperty', (context) => { diff --git a/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue b/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue index a0eff3e1d..091063160 100644 --- a/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue +++ b/apps/web-antd/src/views/mall/promotion/discountActivity/modules/form.vue @@ -64,12 +64,12 @@ const [Form, formApi] = useVbenForm({ // ================= 商品选择相关 ================= /** SKU 扩展类型 */ -interface SkuExtension extends MallSpuApi.Sku { +interface SkuExtension extends MallSpuApi.Sku { productConfig: MallDiscountActivityApi.DiscountProduct; } /** SPU 扩展类型 */ -interface SpuExtension extends MallSpuApi.Spu { +interface SpuExtension extends MallSpuApi.Spu { skus?: SkuExtension[]; } diff --git a/apps/web-antd/src/views/mall/promotion/kefu/modules/conversation-list.vue b/apps/web-antd/src/views/mall/promotion/kefu/modules/conversation-list.vue index 174d845a3..cb081f833 100644 --- a/apps/web-antd/src/views/mall/promotion/kefu/modules/conversation-list.vue +++ b/apps/web-antd/src/views/mall/promotion/kefu/modules/conversation-list.vue @@ -50,7 +50,7 @@ function openRightMessage(item: MallKefuConversationApi.Conversation) { /** 获得消息类型 */ const getConversationDisplayText = computed( - () => (lastMessageContentType: number, lastMessageContent: string) => { + () => (lastMessageContentType: number, lastMessageContent: string) => { switch (lastMessageContentType) { case KeFuMessageContentTypeEnum.IMAGE: { return '[图片消息]'; diff --git a/apps/web-antd/src/views/mall/promotion/kefu/modules/member/member-info.vue b/apps/web-antd/src/views/mall/promotion/kefu/modules/member/member-info.vue index 742509d83..f7d00af86 100644 --- a/apps/web-antd/src/views/mall/promotion/kefu/modules/member/member-info.vue +++ b/apps/web-antd/src/views/mall/promotion/kefu/modules/member/member-info.vue @@ -21,7 +21,7 @@ import ProductBrowsingHistory from './product-browsing-history.vue'; const activeTab = ref('会员信息'); -const tabActivation = computed(() => (tab: string) => activeTab.value === tab); +const tabActivation = computed(() => (tab: string) => activeTab.value === tab); /** tab 切换 */ const productBrowsingHistoryRef = diff --git a/apps/web-antd/src/views/mall/promotion/kefu/modules/message-list.vue b/apps/web-antd/src/views/mall/promotion/kefu/modules/message-list.vue index dcb34aaca..2ba335483 100644 --- a/apps/web-antd/src/views/mall/promotion/kefu/modules/message-list.vue +++ b/apps/web-antd/src/views/mall/promotion/kefu/modules/message-list.vue @@ -51,7 +51,7 @@ const loadHistory = ref(false); // 加载历史消息 /** 获悉消息内容 */ const getMessageContent = computed( - () => (item: any) => jsonParse(item.content), + () => (item: any) => jsonParse(item.content), ); /** 获得消息列表 */ diff --git a/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue b/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue index fef6297ed..8ae5bb572 100644 --- a/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue +++ b/apps/web-antd/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue @@ -25,7 +25,7 @@ const emits = defineEmits<{ }>(); /** 选择赠送的优惠类型拓展 */ -interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate { +interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate { giveCount?: number; } diff --git a/apps/web-antdv-next/package.json b/apps/web-antdv-next/package.json index 928084d99..c7dceba60 100644 --- a/apps/web-antdv-next/package.json +++ b/apps/web-antdv-next/package.json @@ -16,7 +16,7 @@ }, "type": "module", "scripts": { - "build": "pnpm vite build --mode production", + "#build": "pnpm vite build --mode production", "build:analyze": "pnpm vite build --mode analyze", "dev": "pnpm vite --mode development", "preview": "vite preview", diff --git a/apps/web-antdv-next/public/wx-xingyu.png b/apps/web-antdv-next/public/wx-xingyu.png new file mode 100644 index 000000000..da2e45a34 Binary files /dev/null and b/apps/web-antdv-next/public/wx-xingyu.png differ diff --git a/apps/web-ele/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js b/apps/web-ele/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js index 325726a9b..3956be680 100644 --- a/apps/web-ele/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js +++ b/apps/web-ele/src/views/bpm/components/bpmn-process-designer/package/designer/plugins/extension-moddle/camunda/extension.js @@ -3,7 +3,7 @@ import { isFunction, isObject } from '@vben/utils'; const WILDCARD = '*'; function CamundaModdleExtension(eventBus) { - // eslint-disable-next-line unicorn/no-this-assignment + // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias const self = this; eventBus.on('moddleCopy.canCopyProperty', (context) => { diff --git a/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue index 3dd8c1867..4f4ad31b2 100644 --- a/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue +++ b/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue @@ -64,12 +64,12 @@ const [Form, formApi] = useVbenForm({ // ================= 商品选择相关 ================= /** SKU 扩展类型 */ -interface SkuExtension extends MallSpuApi.Sku { +interface SkuExtension extends MallSpuApi.Sku { productConfig: MallDiscountActivityApi.DiscountProduct; } /** SPU 扩展类型 */ -interface SpuExtension extends MallSpuApi.Spu { +interface SpuExtension extends MallSpuApi.Spu { skus?: SkuExtension[]; } diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/conversation-list.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/conversation-list.vue index 9f22f84b6..7f6b91bcf 100644 --- a/apps/web-ele/src/views/mall/promotion/kefu/modules/conversation-list.vue +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/conversation-list.vue @@ -50,7 +50,7 @@ function openRightMessage(item: MallKefuConversationApi.Conversation) { /** 获得消息类型 */ const getConversationDisplayText = computed( - () => (lastMessageContentType: number, lastMessageContent: string) => { + () => (lastMessageContentType: number, lastMessageContent: string) => { switch (lastMessageContentType) { case KeFuMessageContentTypeEnum.IMAGE: { return '[图片消息]'; diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/member/member-info.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/member-info.vue index 4b87f377a..39be701a5 100644 --- a/apps/web-ele/src/views/mall/promotion/kefu/modules/member/member-info.vue +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/member-info.vue @@ -21,7 +21,7 @@ import ProductBrowsingHistory from './product-browsing-history.vue'; const activeTab = ref('会员信息'); -const tabActivation = computed(() => (tab: string) => activeTab.value === tab); +const tabActivation = computed(() => (tab: string) => activeTab.value === tab); /** tab 切换 */ const productBrowsingHistoryRef = diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/message-list.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/message-list.vue index 633187084..495253399 100644 --- a/apps/web-ele/src/views/mall/promotion/kefu/modules/message-list.vue +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/message-list.vue @@ -57,7 +57,7 @@ const loadHistory = ref(false); // 加载历史消息 /** 获悉消息内容 */ const getMessageContent = computed( - () => (item: any) => jsonParse(item.content), + () => (item: any) => jsonParse(item.content), ); /** 获得消息列表 */ diff --git a/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue b/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue index ae400c2a8..c3f622977 100644 --- a/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue +++ b/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/reward-rule-coupon-select.vue @@ -25,7 +25,7 @@ const emits = defineEmits<{ }>(); /** 选择赠送的优惠类型拓展 */ -interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate { +interface GiveCoupon extends MallCouponTemplateApi.CouponTemplate { giveCount?: number; } diff --git a/cspell.json b/cspell.json index be3527752..8e43d3293 100644 --- a/cspell.json +++ b/cspell.json @@ -13,12 +13,14 @@ "axios", "brotli", "cascader", + "chatcmpl", "clsx", "cropperjs", "dedup", "defu", "demi", "dotenv", + "echart", "echarts", "ependencies", "esbuild", @@ -63,8 +65,9 @@ "tabler", "taze", "tdesign", + "tsdown", "tsgolint", - "Turborepo", + "turborepo", "ui-kit", "uicons", "unplugin", diff --git a/docs/.vitepress/config/en.mts b/docs/.vitepress/config/en.mts index a74a7e5fb..8bac04ceb 100644 --- a/docs/.vitepress/config/en.mts +++ b/docs/.vitepress/config/en.mts @@ -42,6 +42,10 @@ export const en = defineConfig({ base: '/en/commercial/', items: sidebarCommercial(), }, + '/en/components/': { + base: '/en/components/', + items: sidebarComponents(), + }, '/en/guide/': { base: '/en/guide/', items: sidebarGuide() }, }, }, @@ -63,6 +67,11 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { }, { link: 'introduction/quick-start', text: 'Quick Start' }, { link: 'introduction/thin', text: 'Lite Version' }, + { + base: '/', + link: 'components/introduction', + text: 'Components', + }, ], }, { @@ -132,6 +141,68 @@ function sidebarCommercial(): DefaultTheme.SidebarItem[] { ]; } +function sidebarComponents(): DefaultTheme.SidebarItem[] { + return [ + { + text: 'Components', + items: [ + { + link: 'introduction', + text: 'Introduction', + }, + ], + }, + { + collapsed: false, + text: 'Layout UI', + items: [ + { + link: 'layout-ui/page', + text: 'Page', + }, + ], + }, + { + collapsed: false, + text: 'Common UI', + items: [ + { + link: 'common-ui/vben-api-component', + text: 'ApiComponent', + }, + { + link: 'common-ui/vben-alert', + text: 'Alert', + }, + { + link: 'common-ui/vben-modal', + text: 'Modal', + }, + { + link: 'common-ui/vben-drawer', + text: 'Drawer', + }, + { + link: 'common-ui/vben-form', + text: 'Form', + }, + { + link: 'common-ui/vben-vxe-table', + text: 'Vxe Table', + }, + { + link: 'common-ui/vben-count-to-animator', + text: 'CountToAnimator', + }, + { + link: 'common-ui/vben-ellipsis-text', + text: 'EllipsisText', + }, + ], + }, + ]; +} + function nav(): DefaultTheme.NavItem[] { return [ { @@ -143,11 +214,11 @@ function nav(): DefaultTheme.NavItem[] { link: '/en/guide/introduction/vben', text: 'Guide', }, - // { - // activeMatch: '^/en/components/', - // link: '/en/components/introduction', - // text: 'Components', - // }, + { + activeMatch: '^/en/components/', + link: '/en/components/introduction', + text: 'Components', + }, { text: 'Historical Versions', items: [ diff --git a/docs/package.json b/docs/package.json index bee65c99a..c418d797f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -28,6 +28,7 @@ "devDependencies": { "@nolebase/vitepress-plugin-git-changelog": "catalog:", "@tailwindcss/vite": "catalog:", + "@vben/tailwind-config": "workspace:*", "@vben/vite-config": "workspace:*", "@vite-pwa/vitepress": "catalog:", "vitepress": "catalog:", diff --git a/docs/src/_env/adapter/form.ts b/docs/src/_env/adapter/form.ts index d8b51c254..7ebd4acac 100644 --- a/docs/src/_env/adapter/form.ts +++ b/docs/src/_env/adapter/form.ts @@ -11,6 +11,7 @@ import { $t } from '@vben/locales'; import { initComponentAdapter } from './component'; initComponentAdapter(); + setupVbenForm({ config: { baseModelPropName: 'value', diff --git a/docs/src/components/common-ui/vben-alert.md b/docs/src/components/common-ui/vben-alert.md index 6541b665d..eebd1f857 100644 --- a/docs/src/components/common-ui/vben-alert.md +++ b/docs/src/components/common-ui/vben-alert.md @@ -4,132 +4,90 @@ outline: deep # Vben Alert 轻量提示框 -框架提供的一些用于轻量提示的弹窗,仅使用js代码即可快速动态创建提示而不需要在template写任何代码。 +`Alert` 提供了一组纯 JavaScript 调用的轻量提示框能力,适合快速创建 `alert`、`confirm`、`prompt` 这类简单交互。 -::: info 应用场景 +::: info 适用场景 -Alert提供的功能与Modal类似,但只适用于简单应用场景。例如临时性、动态地弹出模态确认框、输入框等。如果对弹窗有更复杂的需求,请使用VbenModal - -::: +`Alert` 与 `Modal` 的能力有部分重叠,但更适合临时确认、简单提示和轻量输入场景。复杂弹窗仍然建议使用 `Vben Modal`。::: ::: tip 注意 -Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下,不支持HMR(热更新),代码变更后需要关闭这些弹窗后重新打开。 - -::: - -::: tip README - -下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。 - -::: +通过 `alert`、`confirm`、`prompt` 动态创建的弹窗,在已经打开的情况下不支持 HMR 热更新。修改相关代码后,需要关闭后重新打开。::: ## 基础用法 -使用 `alert` 创建只有一个确认按钮的提示框。 +使用 `alert` 创建只有确认按钮的提示框。 -使用 `confirm` 创建有确认和取消按钮的提示框。 +使用 `confirm` 创建带确认和取消按钮的提示框。 -使用 `prompt` 创建有确认和取消按钮、接受用户输入的提示框。 +使用 `prompt` 创建可接收用户输入的提示框。 ## useAlertContext -当弹窗的content、footer、icon使用自定义组件时,在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗。 +当 `content`、`footer` 或 `icon` 使用的是自定义组件时,可以在这些组件内部通过 `useAlertContext()` 获取当前弹窗上下文,并主动触发确认或取消。 ::: tip 注意 -`useAlertContext`只能用在setup或者函数式组件中。 - -::: +`useAlertContext` 只能在 `setup` 或函数式组件中使用。::: ### Methods -| 方法 | 描述 | 类型 | 版本要求 | -| --------- | ------------------ | -------- | -------- | -| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 | -| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 | +| 方法 | 描述 | 类型 | 版本要求 | +| --------- | ---------------------- | ------------ | -------- | +| doConfirm | 触发当前弹窗的确认操作 | `() => void` | `>5.5.4` | +| doCancel | 触发当前弹窗的取消操作 | `() => void` | `>5.5.4` | ## 类型说明 ```ts -/** 预置的图标类型 */ export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning'; export type BeforeCloseScope = { - /** 是否为点击确认按钮触发的关闭 */ isConfirm: boolean; }; -/** - * alert 属性 - */ export type AlertProps = { - /** 关闭前的回调,如果返回false,则终止关闭 */ beforeClose?: ( scope: BeforeCloseScope, ) => boolean | Promise | undefined; - /** 边框 */ bordered?: boolean; - /** 按钮对齐方式 */ buttonAlign?: 'center' | 'end' | 'start'; - /** 取消按钮的标题 */ cancelText?: string; - /** 是否居中显示 */ centered?: boolean; - /** 确认按钮的标题 */ confirmText?: string; - /** 弹窗容器的额外样式 */ containerClass?: string; - /** 弹窗提示内容 */ content: Component | string; - /** 弹窗内容的额外样式 */ contentClass?: string; - /** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/ contentMasking?: boolean; - /** 弹窗底部内容(与按钮在同一个容器中) */ footer?: Component | string; - /** 弹窗的图标(在标题的前面) */ icon?: Component | IconType; - /** - * 弹窗遮罩模糊效果 - */ overlayBlur?: number; - /** 是否显示取消按钮 */ showCancel?: boolean; - /** 弹窗标题 */ title?: string; }; -/** prompt 属性 */ export type PromptProps = { - /** 关闭前的回调,如果返回false,则终止关闭 */ beforeClose?: (scope: { isConfirm: boolean; value: T | undefined; }) => boolean | Promise | undefined; - /** 用于接受用户输入的组件 */ component?: Component; - /** 输入组件的属性 */ componentProps?: Recordable; - /** 输入组件的插槽 */ - componentSlots?: Recordable; - /** 默认值 */ + componentSlots?: + | (() => any) + | Recordable + | VNode + | VNodeArrayChildren; defaultValue?: T; - /** 输入组件的值属性名 */ modelPropName?: string; } & Omit; -/** - * 函数签名 - * alert和confirm的函数签名相同。 - * confirm默认会显示取消按钮,而alert默认只有一个按钮 - * */ export function alert(options: AlertProps): Promise; export function alert( message: string, @@ -141,19 +99,10 @@ export function alert( options?: Partial, ): Promise; -/** - * 弹出输入框的函数签名。 - * beforeClose的参数会传入用户当前输入的值 - * component指定接受用户输入的组件,默认为Input - * componentProps 为输入组件设置的属性数据 - * defaultValue 默认的值 - * modelPropName 输入组件的值属性名称。默认为modelValue - */ export async function prompt( options: Omit & { beforeClose?: ( scope: BeforeCloseScope & { - /** 输入组件的当前值 */ value: T; }, ) => boolean | Promise | undefined; diff --git a/docs/src/components/common-ui/vben-api-component.md b/docs/src/components/common-ui/vben-api-component.md index 2c84e56b5..5465f1516 100644 --- a/docs/src/components/common-ui/vben-api-component.md +++ b/docs/src/components/common-ui/vben-api-component.md @@ -14,7 +14,7 @@ outline: deep ## 基础用法 -通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过`api`获取数据(`beforerFetch`、`afterFetch`将分别在`api`运行前、运行后被调用),使用`resultField`从中提取数组,使用`valueField`、`labelField`等来从数据中提取value和label(如果提供了`childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过`optionsPropName`指定的属性传递给目标组件。 +通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过 `api` 获取数据(`beforeFetch`、`afterFetch` 将分别在 `api` 运行前、运行后被调用),使用 `resultField` 从中提取数组,使用 `valueField`、`labelField` 等来从数据中提取 value 和 label(如果提供了 `childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过 `optionsPropName` 指定的属性传递给目标组件。 ::: details 包装级联选择器,点击下拉时开始加载远程数据 diff --git a/docs/src/components/common-ui/vben-count-to-animator.md b/docs/src/components/common-ui/vben-count-to-animator.md index 5f3ec1881..1a15401a3 100644 --- a/docs/src/components/common-ui/vben-count-to-animator.md +++ b/docs/src/components/common-ui/vben-count-to-animator.md @@ -4,56 +4,54 @@ outline: deep # Vben CountToAnimator 数字动画 -框架提供的数字动画组件,支持数字动画效果。 +`CountToAnimator` 用于展示数字滚动动画效果。 -> 如果文档内没有参数说明,可以尝试在在线示例内寻找 +> 如果文档内没有覆盖到你需要的细节,可以结合在线示例一起查看。 ::: info 写在前面 -如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: +这是一个轻量数字动画组件。如果你需要完全不同的过渡控制方式,也可以直接使用原生动画方案或自行封装。::: ## 基础用法 -通过 `start-val` 和 `end-val`设置数字动画的开始值和结束值, 持续时间`3000`ms。 +通过 `start-val` 和 `end-val` 设置数字动画的起始值和结束值,配合 `duration` 控制动画时长。 -## 自定义前缀及分隔符 +## 自定义前缀与分隔符 -通过 `prefix` 和 `separator` 设置数字动画的前缀和分隔符。 +通过 `prefix`、`suffix`、`separator` 和 `decimal` 可以控制展示格式。 ### Props -| 属性名 | 描述 | 类型 | 默认值 | -| ---------- | -------------- | --------- | -------- | -| startVal | 起始值 | `number` | `0` | -| endVal | 结束值 | `number` | `2021` | -| duration | 动画持续时间 | `number` | `1500` | -| autoplay | 自动执行 | `boolean` | `true` | -| prefix | 前缀 | `string` | - | -| suffix | 后缀 | `string` | - | -| separator | 分隔符 | `string` | `,` | -| color | 字体颜色 | `string` | - | -| useEasing | 是否开启动画 | `boolean` | `true` | -| transition | 动画效果 | `string` | `linear` | -| decimals | 保留小数点位数 | `number` | `0` | +| 属性名 | 描述 | 类型 | 默认值 | +| --- | --- | --- | --- | +| startVal | 起始值 | `number` | `0` | +| endVal | 结束值 | `number` | `2021` | +| duration | 动画持续时间 | `number` | `1500` | +| autoplay | 是否自动播放 | `boolean` | `true` | +| prefix | 前缀 | `string` | `''` | +| suffix | 后缀 | `string` | `''` | +| separator | 千分位分隔符 | `string` | `','` | +| decimal | 小数点分隔符 | `string` | `'.'` | +| color | 文本颜色 | `string` | `''` | +| useEasing | 是否启用过渡预设 | `boolean` | `true` | +| transition | 过渡预设名称 | `keyof typeof TransitionPresets` | `'linear'` | +| decimals | 保留小数位数 | `number` | `0` | ### Events -| 事件名 | 描述 | 类型 | -| -------------- | -------------- | -------------- | -| started | 动画已开始 | `()=>void` | -| finished | 动画已结束 | `()=>void` | -| ~~onStarted~~ | ~~动画已开始~~ | ~~`()=>void`~~ | -| ~~onFinished~~ | ~~动画已结束~~ | ~~`()=>void`~~ | +| 事件名 | 描述 | 类型 | +| -------------- | ----------------------------- | ---------------- | +| started | 动画开始时触发 | `() => void` | +| finished | 动画结束时触发 | `() => void` | +| ~~onStarted~~ | ~~已废弃,请改用 `started`~~ | ~~`() => void`~~ | +| ~~onFinished~~ | ~~已废弃,请改用 `finished`~~ | ~~`() => void`~~ | ### Methods -| 方法名 | 描述 | 类型 | -| ------ | ------------ | ---------- | -| start | 开始执行动画 | `()=>void` | -| reset | 重置 | `()=>void` | +| 方法名 | 描述 | 类型 | +| ------ | -------------------------------- | ------------ | +| reset | 重置为 `startVal` 并重新执行动画 | `() => void` | diff --git a/docs/src/components/common-ui/vben-drawer.md b/docs/src/components/common-ui/vben-drawer.md index 3a28cce79..8214b8554 100644 --- a/docs/src/components/common-ui/vben-drawer.md +++ b/docs/src/components/common-ui/vben-drawer.md @@ -55,7 +55,7 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra - `VbenDrawer` 组件对于参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 - 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenDrawer`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。 - 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。 -- 如果抽屉的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultDrawerProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。 +- 如果抽屉的默认行为不符合你的预期,可以在对应应用的 `apps//src/bootstrap.ts` 中修改 `setDefaultDrawerProps` 的参数来设置默认属性,例如修改默认 `zIndex` 等。 ::: @@ -116,7 +116,7 @@ const [Drawer, drawerApi] = useVbenDrawer({ | 事件名 | 描述 | 类型 | 版本限制 | | --- | --- | --- | --- | -| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | --- | +| onBeforeClose | 关闭前触发,返回 `false` 或 Promise reject 则禁止关闭 | `()=>Promise\|boolean\|undefined` | >5.5.2 支持 Promise | | onCancel | 点击取消按钮触发 | `()=>void` | --- | | onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 | | onConfirm | 点击确认按钮触发 | `()=>void` | --- | @@ -140,7 +140,7 @@ const [Drawer, drawerApi] = useVbenDrawer({ | 方法 | 描述 | 类型 | 版本限制 | | --- | --- | --- | --- | -| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial)\| Partial)=>drawerApi` | +| setState | 动态设置抽屉状态属性 | `(((prev: DrawerState) => Partial)\| Partial)=>drawerApi` | | open | 打开弹窗 | `()=>void` | --- | | close | 关闭弹窗 | `()=>void` | --- | | setData | 设置共享数据 | `(data:T)=>drawerApi` | --- | diff --git a/docs/src/components/common-ui/vben-ellipsis-text.md b/docs/src/components/common-ui/vben-ellipsis-text.md index ce6c0334a..32a8fa58e 100644 --- a/docs/src/components/common-ui/vben-ellipsis-text.md +++ b/docs/src/components/common-ui/vben-ellipsis-text.md @@ -4,31 +4,31 @@ outline: deep # Vben EllipsisText 省略文本 -框架提供的文本展示组件,可配置超长省略、tooltip提示、展开收起等功能。 +`EllipsisText` 用于展示超长文本,支持省略、Tooltip 提示以及点击展开收起。 -> 如果文档内没有参数说明,可以尝试在在线示例内寻找 +> 如果文档内没有覆盖到你需要的细节,可以结合在线示例一起查看。 ## 基础用法 -通过默认插槽设置文本内容,`maxWidth`属性设置最大宽度。 +通过默认插槽提供文本内容,`maxWidth` 用于限制文本区域宽度。 -## 可折叠的文本块 +## 可折叠文本 -通过`line`设置折叠后的行数,`expand`属性设置是否支持展开收起。 +通过 `line` 设置折叠后的最大行数,通过 `expand` 开启点击展开与收起。 ## 自定义提示浮层 -通过名为`tooltip`的插槽定制提示信息。 +通过 `tooltip` 插槽自定义提示内容。 -## 自动显示 tooltip +## 仅在省略时显示 Tooltip -通过`tooltip-when-ellipsis`设置,仅在文本长度超出导致省略号出现时才触发 tooltip。 +通过 `tooltip-when-ellipsis` 控制仅在文本被截断时显示 Tooltip。 @@ -38,27 +38,27 @@ outline: deep | 属性名 | 描述 | 类型 | 默认值 | | --- | --- | --- | --- | -| expand | 支持点击展开或收起 | `boolean` | `false` | -| line | 文本最大行数 | `number` | `1` | +| expand | 是否支持点击展开或收起 | `boolean` | `false` | +| line | 文本最大显示行数 | `number` | `1` | | maxWidth | 文本区域最大宽度 | `number \| string` | `'100%'` | -| placement | 提示浮层的位置 | `'bottom'\|'left'\|'right'\|'top'` | `'top'` | -| tooltip | 启用文本提示 | `boolean` | `true` | -| tooltipWhenEllipsis | 内容超出,自动启用文本提示 | `boolean` | `false` | -| ellipsisThreshold | 设置 tooltipWhenEllipsis 后才生效,文本截断检测的像素差异阈值,越大则判断越严格,如果碰见异常情况可以自己设置阈值 | `number` | `3` | -| tooltipBackgroundColor | 提示文本的背景颜色 | `string` | - | -| tooltipColor | 提示文本的颜色 | `string` | - | -| tooltipFontSize | 提示文本的大小 | `string` | - | -| tooltipMaxWidth | 提示浮层的最大宽度。如不设置则保持与文本宽度一致 | `number` | - | -| tooltipOverlayStyle | 提示框内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` | +| placement | 提示浮层位置 | `'bottom' \| 'left' \| 'right' \| 'top'` | `'top'` | +| tooltip | 是否启用文本提示 | `boolean` | `true` | +| tooltipWhenEllipsis | 是否仅在文本被截断时显示提示 | `boolean` | `false` | +| ellipsisThreshold | 文本截断检测阈值,值越大判定越严格 | `number` | `3` | +| tooltipBackgroundColor | 提示背景色 | `string` | `''` | +| tooltipColor | 提示文字颜色 | `string` | `''` | +| tooltipFontSize | 提示文字大小,单位 `px` | `number` | `14` | +| tooltipMaxWidth | 提示内容最大宽度,单位 `px` | `number` | - | +| tooltipOverlayStyle | 提示内容区域样式 | `CSSProperties` | `{ textAlign: 'justify' }` | ### Events -| 事件名 | 描述 | 类型 | -| ------------ | ------------ | -------------------------- | -| expandChange | 展开状态改变 | `(isExpand:boolean)=>void` | +| 事件名 | 描述 | 类型 | +| ------------ | ------------------ | ----------------------------- | +| expandChange | 展开状态变化时触发 | `(isExpand: boolean) => void` | ### Slots -| 插槽名 | 描述 | -| ------- | -------------------------------- | -| tooltip | 启用文本提示时,用来定制提示内容 | +| 插槽名 | 描述 | +| ------- | ---------------------------------- | +| tooltip | 开启文本提示时,用于自定义提示内容 | diff --git a/docs/src/components/common-ui/vben-form.md b/docs/src/components/common-ui/vben-form.md index 30cbec448..0a5f2edce 100644 --- a/docs/src/components/common-ui/vben-form.md +++ b/docs/src/components/common-ui/vben-form.md @@ -35,10 +35,15 @@ import type { ComponentType } from './component'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { $t } from '@vben/locales'; +import { initComponentAdapter } from './component'; + +initComponentAdapter(); setupVbenForm({ config: { // ant design vue组件库默认都是 v-model:value baseModelPropName: 'value', + // 一些组件库空值为 null,重置表单时需要和实际组件行为保持一致 + emptyStateValue: null, // 一些组件是 v-model:checked 或者 v-model:fileList modelPropNameMap: { Checkbox: 'checked', @@ -87,55 +92,32 @@ import type { BaseFormComponentType } from '@vben/common-ui'; import type { Component, SetupContext } from 'vue'; import { h } from 'vue'; -import { globalShareState, IconPicker } from '@vben/common-ui'; +import { globalShareState } from '@vben/common-ui'; import { $t } from '@vben/locales'; - -const AutoComplete = defineAsyncComponent( - () => import('ant-design-vue/es/auto-complete'), -); -const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); -const Checkbox = defineAsyncComponent( - () => import('ant-design-vue/es/checkbox'), -); -const CheckboxGroup = defineAsyncComponent(() => - import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup), -); -const DatePicker = defineAsyncComponent( - () => import('ant-design-vue/es/date-picker'), -); -const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider')); -const Input = defineAsyncComponent(() => import('ant-design-vue/es/input')); -const InputNumber = defineAsyncComponent( - () => import('ant-design-vue/es/input-number'), -); -const InputPassword = defineAsyncComponent(() => - import('ant-design-vue/es/input').then((res) => res.InputPassword), -); -const Mentions = defineAsyncComponent( - () => import('ant-design-vue/es/mentions'), -); -const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio')); -const RadioGroup = defineAsyncComponent(() => - import('ant-design-vue/es/radio').then((res) => res.RadioGroup), -); -const RangePicker = defineAsyncComponent(() => - import('ant-design-vue/es/date-picker').then((res) => res.RangePicker), -); -const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate')); -const Select = defineAsyncComponent(() => import('ant-design-vue/es/select')); -const Space = defineAsyncComponent(() => import('ant-design-vue/es/space')); -const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch')); -const Textarea = defineAsyncComponent(() => - import('ant-design-vue/es/input').then((res) => res.Textarea), -); -const TimePicker = defineAsyncComponent( - () => import('ant-design-vue/es/time-picker'), -); -const TreeSelect = defineAsyncComponent( - () => import('ant-design-vue/es/tree-select'), -); -const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload')); - +import { + AutoComplete, + Button, + Checkbox, + CheckboxGroup, + DatePicker, + Divider, + Input, + InputNumber, + InputPassword, + Mentions, + notification, + Radio, + RadioGroup, + RangePicker, + Rate, + Select, + Space, + Switch, + Textarea, + TimePicker, + TreeSelect, + Upload, +} from 'ant-design-vue'; const withDefaultPlaceholder = ( component: T, @@ -171,7 +153,6 @@ export type ComponentType = | 'TimePicker' | 'TreeSelect' | 'Upload' - | 'IconPicker'; | BaseFormComponentType; async function initComponentAdapter() { @@ -189,7 +170,6 @@ async function initComponentAdapter() { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, - IconPicker, Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), diff --git a/docs/src/components/common-ui/vben-modal.md b/docs/src/components/common-ui/vben-modal.md index fc714e279..4d99f59f4 100644 --- a/docs/src/components/common-ui/vben-modal.md +++ b/docs/src/components/common-ui/vben-modal.md @@ -69,7 +69,7 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda - `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。 - 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。另外,如果设置了`destroyOnClose`,内部Modal及其子组件会在被关闭后完全销毁。 -- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。 +- 如果弹窗的默认行为不符合你的预期,可以在对应应用的 `apps//src/bootstrap.ts` 中修改 `setDefaultModalProps` 的参数来设置默认属性,例如默认隐藏全屏按钮、修改默认 `zIndex` 等。 ::: diff --git a/docs/src/components/common-ui/vben-vxe-table.md b/docs/src/components/common-ui/vben-vxe-table.md index 344ecae11..8e3088a05 100644 --- a/docs/src/components/common-ui/vben-vxe-table.md +++ b/docs/src/components/common-ui/vben-vxe-table.md @@ -4,25 +4,19 @@ outline: deep # Vben Vxe Table 表格 -框架提供的Table 列表组件基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid),结合`Vben Form 表单`进行了二次封装。 +`Vben Vxe Table` 基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 和 `Vben Form` 做了二次封装,用于构建带搜索表单的列表页面。 -其中,表头的 **表单搜索** 部分采用了`Vben Form表单`,表格主体部分使用了`vxe-grid`组件,支持表格的分页、排序、筛选等功能。 - -> 如果文档内没有参数说明,可以尝试在在线示例或者在 [vxe-grid 官方API 文档](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 内寻找 +> 如果文档内没有覆盖到你需要的细节,可以结合在线示例和 [vxe-grid 官方 API](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 一起查看。 ::: info 写在前面 -如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。 - -::: +如果现有封装不满足你的场景,可以直接使用原生 `vxe-table` 能力,或者在适配层中继续扩展。::: ## 适配器 -表格底层使用 [vxe-table](https://vxetable.cn/#/start/install) 进行实现,所以你可以使用 `vxe-table` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架。 +底层表格基于 `vxe-table`,每个应用都可以在自己的适配层中配置默认行为、自定义渲染器以及与 UI 组件库的集成。 -### 适配器说明 - -每个应用都可以自己配置`vxe-table`的适配器,你可以根据自己的需求。下面是一个简单的配置示例: +### 适配器示例 ::: details vxe-table 表格适配器 @@ -46,7 +40,6 @@ setupVbenVxeTable({ }, minHeight: 180, formConfig: { - // 全局禁用vxe-table的表单配置,使用formOptions enabled: false, }, proxyConfig: { @@ -65,7 +58,6 @@ setupVbenVxeTable({ }, }); - // 表格配置项可以用 cellRender: { name: 'CellImage' }, vxeUI.renderer.add('CellImage', { renderTableDefault(_renderOpts, params) { const { column, row } = params; @@ -73,7 +65,6 @@ setupVbenVxeTable({ }, }); - // 表格配置项可以用 cellRender: { name: 'CellLink' }, vxeUI.renderer.add('CellLink', { renderTableDefault(renderOpts) { const { props } = renderOpts; @@ -84,9 +75,6 @@ setupVbenVxeTable({ ); }, }); - - // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 - // vxeUI.formats.add }, useVbenForm, }); @@ -100,55 +88,50 @@ export type * from '@vben/plugins/vxe-table'; ## 基础表格 -使用 `useVbenVxeGrid` 创建最基础的表格。 +通过 `useVbenVxeGrid` 创建一个基础表格。 ## 远程加载 -通过指定 `proxyConfig.ajax` 的 `query` 方法,可以实现远程加载数据。 +通过配置 `proxyConfig.ajax.query` 实现远程数据加载。 ## 树形表格 -树形表格的数据源为扁平结构,可以指定`treeConfig`配置项,实现树形表格。 +树形表格的数据源通常是扁平结构,可以通过 `treeConfig` 转换为树形展示。 -```typescript +```ts treeConfig: { - transform: true, // 指定表格为树形表格 - parentField: 'parentId', // 父节点字段名 - rowField: 'id', // 行数据字段名 + transform: true, + parentField: 'parentId', + rowField: 'id', }, ``` -## 固定表头/列 +## 固定列 -列固定可选参数: `'left' | 'right' | '' | null` +固定列可选值为 `'left' | 'right' | '' | null`。 ## 自定义单元格 -自定义单元格有两种实现方式 +可以通过插槽或自定义渲染器实现单元格定制。 -- 通过 `slots` 插槽 -- 通过 `customCell` 自定义单元格,但是要先添加渲染器 - -```typescript -// 表格配置项可以用 cellRender: { name: 'CellImage' }, +```ts vxeUI.renderer.add('CellImage', { - renderDefault(_renderOpts, params) { + renderTableDefault(_renderOpts, params) { const { column, row } = params; - return h(Image, { src: row[column.field] } as any); // 注意此处的Image 组件,来源于Antd,需要自行引入,否则会使用js的Image类 + return h(Image, { src: row[column.field] } as any); }, }); -// 表格配置项可以用 cellRender: { name: 'CellLink' }, vxeUI.renderer.add('CellLink', { - renderDefault(renderOpts) { + renderTableDefault(renderOpts) { const { props } = renderOpts; return h( Button, @@ -163,23 +146,20 @@ vxeUI.renderer.add('CellLink', { ## 搜索表单 -**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。 +搜索区域底层使用的是 `Vben Form`。启用搜索表单后,可以通过 `gridOptions.toolbarConfig.search = true` 在工具栏中显示搜索面板开关按钮。 -当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。 +所有以 `form-` 开头的具名插槽都会自动转发到搜索表单。 -### 定制分隔条 +### 自定义分隔条 -当你启用表单搜索时,在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色,并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器(如将其放在一个Card内),默认的表单和表格之间的分隔条可能就显得格格不入了,下面的代码演示了如何定制这个分隔条。 +启用搜索表单时,表单和表格主体之间默认会显示一个分隔条。可以通过 `separator` 调整或关闭它。 ```ts const [Grid] = useVbenVxeGrid({ formOptions: {}, gridOptions: {}, - // 完全移除分隔条 separator: false, - // 你也可以使用下面的代码来移除分隔条 // separator: { show: false }, - // 或者使用下面的代码来改变分隔条的颜色 // separator: { backgroundColor: 'rgba(100,100,0,0.5)' }, }); ``` @@ -188,40 +168,36 @@ const [Grid] = useVbenVxeGrid({ ## 单元格编辑 -通过指定`editConfig.mode`为`cell`,可以实现单元格编辑。 +通过设置 `editConfig.mode = 'cell'` 开启单元格编辑。 ## 行编辑 -通过指定`editConfig.mode`为`row`,可以实现行编辑。 +通过设置 `editConfig.mode = 'row'` 开启整行编辑。 ## 虚拟滚动 -通过 scroll-y.enabled 与 scroll-y.gt 组合开启,其中 enabled 为总开关,gt 是指当总行数大于指定行数时自动开启。 +通过 `scroll-y.enabled` 和 `scroll-y.gt` 组合开启纵向虚拟滚动。 -> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical)。 +> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical) ## API -`useVbenVxeGrid` 返回一个数组,第一个元素是表格组件,第二个元素是表格的方法。 +`useVbenVxeGrid` 返回一个数组,第一个元素是表格组件,第二个元素是表格 API。 ```vue @@ -232,45 +208,42 @@ const [Grid, gridApi] = useVbenVxeGrid({ ### GridApi -useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。 - | 方法名 | 描述 | 类型 | 说明 | | --- | --- | --- | --- | -| setLoading | 设置loading状态 | `(loading)=>void` | - | -| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partialvoid` | - | -| reload | 重载表格,会进行初始化 | `(params:any)=>void` | - | -| query | 重载表格,会保留当前分页 | `(params:any)=>void` | - | -| grid | vxe-table grid实例 | `VxeGridInstance` | - | -| formApi | vbenForm api实例 | `FormApi` | - | -| toggleSearchForm | 设置搜索表单显示状态 | `(show?: boolean)=>boolean` | 当省略参数时,则将表单在显示和隐藏两种状态之间切换 | +| setLoading | 设置 loading 状态 | `(loading: boolean) => void` | - | +| setGridOptions | 更新 `gridOptions` | `(options: Partial) => void` | - | +| reload | 重新加载表格,并重置到初始分页 | `(params?: Record) => void` | - | +| query | 重新查询表格,保留当前分页 | `(params?: Record) => void` | - | +| grid | `vxe-grid` 实例 | `VxeGridInstance` | - | +| formApi | 搜索表单 API 实例 | `FormApi` | - | +| toggleSearchForm | 切换或指定搜索表单显示状态 | `(show?: boolean) => boolean` | 传入参数时强制设置;不传参数时在显示和隐藏之间切换,并返回当前状态 | ## Props -所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。 +所有属性都通过 `useVbenVxeGrid` 的第一个参数传入。 | 属性名 | 描述 | 类型 | 版本要求 | | --- | --- | --- | --- | | tableTitle | 表格标题 | `string` | - | | tableTitleHelp | 表格标题帮助信息 | `string` | - | -| gridClass | grid组件的class | `string` | - | -| gridOptions | grid组件的参数 | `VxeTableGridProps` | - | -| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - | -| formOptions | 表单参数 | `VbenFormProps` | - | +| class | 外层容器的 class | `string` | - | +| gridClass | `vxe-grid` 的 class | `string` | - | +| gridOptions | `vxe-grid` 配置 | `DeepPartial` | - | +| gridEvents | `vxe-grid` 事件 | `DeepPartial` | - | +| formOptions | 搜索表单配置 | `VbenFormProps` | - | | showSearchForm | 是否显示搜索表单 | `boolean` | - | -| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 | +| separator | 搜索表单与表格主体之间的分隔条 | `boolean \| SeparatorOptions` | `>5.5.4` | ## Slots -大部分插槽的说明请参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api),但工具栏部分由于做了一些定制封装,需使用以下插槽定制表格的工具栏: +大部分插槽说明可参考 [vxe-table 官方文档](https://vxetable.cn/v4/#/grid/api),这里列出封装层新增或约定的部分。 -| 插槽名 | 描述 | -| --------------- | -------------------------------------------- | -| toolbar-actions | 工具栏左侧部分(表格标题附近) | -| toolbar-tools | 工具栏右侧部分(vxeTable原生工具按钮的左侧) | -| table-title | 表格标题插槽 | +| 插槽名 | 描述 | +| --------------- | ------------------------------------ | +| toolbar-actions | 工具栏左侧区域,位于标题附近 | +| toolbar-tools | 工具栏右侧区域,位于内置工具按钮左侧 | +| table-title | 自定义表格标题 | -::: info 搜索表单的插槽 +::: info 搜索表单插槽 -对于使用了搜索表单的表格来说,所有以`form-`开头的命名插槽都会传递给表单。 - -::: +当启用了搜索表单时,所有以 `form-` 开头的具名插槽都会被转发给表单。::: diff --git a/docs/src/components/layout-ui/page.md b/docs/src/components/layout-ui/page.md index 29fbdd40f..8f21fae19 100644 --- a/docs/src/components/layout-ui/page.md +++ b/docs/src/components/layout-ui/page.md @@ -4,41 +4,38 @@ outline: deep # Page 常规页面组件 -提供一个常规页面布局的组件,包括头部、内容区域、底部三个部分。 +`Page` 是页面内容区最常用的顶层布局容器,内置了标题区、内容区和底部区三部分结构。 ::: info 写在前面 -本组件是一个基本布局组件。如果有更多的通用页面布局需求(比如双列布局等),可以根据实际需求自行封装。 - -::: +这是一个基础页面容器。如果你的业务页面需要更复杂的布局,例如双列、侧边操作区或自定义滚动区域,建议在 `Page` 之上继续封装。::: ## 基础用法 -将`Page`作为你的业务页面的根组件即可。 +直接将 `Page` 作为业务页面的根组件使用即可。 ### Props | 属性名 | 描述 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | --- | -| title | 页面标题 | `string\|slot` | - | - | -| description | 页面描述(标题下的内容) | `string\|slot` | - | - | -| contentClass | 内容区域的class | `string` | - | - | -| headerClass | 头部区域的class | `string` | - | - | -| footerClass | 底部区域的class | `string` | - | - | -| autoContentHeight | 自动调整内容区域的高度 | `boolean` | `false` | - | +| title | 页面标题 | `string \| slot` | - | - | +| description | 页面描述 | `string \| slot` | - | - | +| contentClass | 内容区域的 class | `string` | - | - | +| headerClass | 头部区域的 class | `string` | - | - | +| footerClass | 底部区域的 class | `string` | - | - | +| autoContentHeight | 根据可视内容高度自动计算内容区高度 | `boolean` | `false` | 开启后内容区会根据布局可视高度自动扣减头部和底部高度 | +| heightOffset | 额外扣减的内容区高度偏移量 | `number` | `0` | 仅在 `autoContentHeight` 开启时生效,单位为 `px` | ::: tip 注意 -如果`title`、`description`、`extra`三者均未提供有效内容(通过`props`或者`slots`均可),则页面头部区域不会渲染。 - -::: +如果 `title`、`description`、`extra` 三者都没有提供有效内容,无论是通过 `props` 还是 `slots`,头部区域都不会渲染。::: ### Slots -| 插槽名称 | 描述 | -| ----------- | ------------ | -| default | 页面内容 | -| title | 页面标题 | -| description | 页面描述 | -| extra | 页面头部右侧 | -| footer | 页面底部 | +| 插槽名称 | 描述 | +| ----------- | ---------------- | +| default | 页面内容 | +| title | 页面标题 | +| description | 页面描述 | +| extra | 页面头部右侧内容 | +| footer | 页面底部内容 | diff --git a/docs/src/demos/vben-drawer/auto-height/drawer.vue b/docs/src/demos/vben-drawer/auto-height/drawer.vue index 1b09723c2..41cb3c46b 100644 --- a/docs/src/demos/vben-drawer/auto-height/drawer.vue +++ b/docs/src/demos/vben-drawer/auto-height/drawer.vue @@ -32,7 +32,7 @@ function handleUpdate(len: number) {
{{ item }}
diff --git a/docs/src/demos/vben-drawer/basic/index.vue b/docs/src/demos/vben-drawer/basic/index.vue index bd7d92750..70b5fd3e7 100644 --- a/docs/src/demos/vben-drawer/basic/index.vue +++ b/docs/src/demos/vben-drawer/basic/index.vue @@ -6,6 +6,6 @@ const [Drawer, drawerApi] = useVbenDrawer(); diff --git a/docs/src/demos/vben-modal/animation-type/index.vue b/docs/src/demos/vben-modal/animation-type/index.vue index 79ba9d33a..f364687b7 100644 --- a/docs/src/demos/vben-modal/animation-type/index.vue +++ b/docs/src/demos/vben-modal/animation-type/index.vue @@ -25,11 +25,11 @@ function openScaleModal() { 缩放动画 - +

这是使用滑动动画的弹窗,从顶部向下滑动进入。

- +

这是使用缩放动画的弹窗,以缩放淡入淡出的方式显示。

diff --git a/docs/src/demos/vben-modal/auto-height/modal.vue b/docs/src/demos/vben-modal/auto-height/modal.vue index e270df1ae..6a23fc87a 100644 --- a/docs/src/demos/vben-modal/auto-height/modal.vue +++ b/docs/src/demos/vben-modal/auto-height/modal.vue @@ -32,7 +32,7 @@ function handleUpdate(len: number) {
{{ item }}
diff --git a/docs/src/demos/vben-modal/basic/index.vue b/docs/src/demos/vben-modal/basic/index.vue index 9f8997085..99f5e0804 100644 --- a/docs/src/demos/vben-modal/basic/index.vue +++ b/docs/src/demos/vben-modal/basic/index.vue @@ -6,6 +6,6 @@ const [Modal, modalApi] = useVbenModal(); diff --git a/docs/src/demos/vben-vxe-table/form/index.vue b/docs/src/demos/vben-vxe-table/form/index.vue index bcf3f5a5d..c17679eb3 100644 --- a/docs/src/demos/vben-vxe-table/form/index.vue +++ b/docs/src/demos/vben-vxe-table/form/index.vue @@ -112,7 +112,7 @@ const gridOptions: VxeGridProps = { }, toolbarConfig: { // 是否显示搜索表单控制按钮 - // @ts-ignore 正式环境时有完整的类型声明 + // @ts-ignore - 正式环境时有完整的类型声明 search: true, }, }; diff --git a/docs/src/demos/vben-vxe-table/tree/index.vue b/docs/src/demos/vben-vxe-table/tree/index.vue index 0024765a8..9dcb764b9 100644 --- a/docs/src/demos/vben-vxe-table/tree/index.vue +++ b/docs/src/demos/vben-vxe-table/tree/index.vue @@ -67,7 +67,7 @@ const collapseAll = () => {