From a41277c35bbe927f346d0d88266bb53dcc0a359b Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Sun, 23 Feb 2025 16:06:53 +0800 Subject: [PATCH 01/15] feat: dialogCard --- src/dialog/dialog-card-props.ts | 57 +++++++++++ src/dialog/dialog-card.tsx | 163 ++++++++++++++++++++++++++++++++ src/dialog/dialog.md | 6 ++ src/dialog/dialog.tsx | 88 ++++++----------- src/dialog/index.ts | 3 + src/dialog/type.ts | 5 +- 6 files changed, 261 insertions(+), 61 deletions(-) create mode 100644 src/dialog/dialog-card-props.ts create mode 100644 src/dialog/dialog-card.tsx diff --git a/src/dialog/dialog-card-props.ts b/src/dialog/dialog-card-props.ts new file mode 100644 index 000000000..801e96c23 --- /dev/null +++ b/src/dialog/dialog-card-props.ts @@ -0,0 +1,57 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TdDialogCardProps } from './type'; +import { PropType } from 'vue'; + +export default { + /** 对话框内容 */ + body: { + type: [String, Function] as PropType, + }, + /** 取消按钮,可自定义。值为 null 则不显示取消按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制取消事件 */ + cancelBtn: { + type: [String, Object, Function] as PropType, + }, + /** 关闭按钮,可以自定义。值为 true 显示默认关闭按钮,值为 false 不显示关闭按钮。值类型为 string 则直接显示值,如:“关闭”。值类型为 TNode,则表示呈现自定义按钮示例 */ + closeBtn: { + type: [String, Boolean, Function] as PropType, + default: true as TdDialogCardProps['closeBtn'], + }, + /** 确认按钮。值为 null 则不显示确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。使用 TNode 自定义按钮时,需自行控制确认事件 */ + confirmBtn: { + type: [String, Object, Function] as PropType, + }, + /** 确认按钮加载状态 */ + confirmLoading: { + type: Boolean, + default: undefined, + }, + /** 底部操作栏,默认会有“确认”和“取消”两个按钮。值为 true 显示默认操作按钮,值为 false 不显示任何内容,值类型为 Function 表示自定义底部内容 */ + footer: { + type: [Boolean, Function] as PropType, + }, + /** 头部内容。值为 true 显示空白头部,值为 false 不显示任何内容,值类型为 string 则直接显示值,值类型为 Function 表示自定义头部内容 */ + header: { + type: [String, Boolean, Function] as PropType, + default: true as TdDialogCardProps['header'], + }, + /** 对话框风格 */ + theme: { + type: String as PropType, + default: 'default' as TdDialogCardProps['theme'], + validator(val: TdDialogCardProps['theme']): boolean { + if (!val) return true; + return ['default', 'info', 'warning', 'danger', 'success'].includes(val); + }, + }, + /** 如果“取消”按钮存在,则点击“取消”按钮时触发,同时触发关闭事件 */ + onCancel: Function as PropType, + /** 点击右上角关闭按钮时触发 */ + onCloseBtnClick: Function as PropType, + /** 如果“确认”按钮存在,则点击“确认”按钮时触发,或者键盘按下回车键时触发 */ + onConfirm: Function as PropType, +}; diff --git a/src/dialog/dialog-card.tsx b/src/dialog/dialog-card.tsx new file mode 100644 index 000000000..7ce4ce4ca --- /dev/null +++ b/src/dialog/dialog-card.tsx @@ -0,0 +1,163 @@ +import Vue from 'vue'; +import { + CloseIcon as TdCloseIcon, + InfoCircleFilledIcon as TdInfoCircleFilledIcon, + CheckCircleFilledIcon as TdCheckCircleFilledIcon, + ErrorCircleFilledIcon as TdErrorCircleFilledIcon, +} from 'tdesign-icons-vue'; + +import TButton from '../button'; +import { DialogCloseContext, TdDialogProps } from './type'; +import dialogProps from './props'; +import dialogCardProps from './dialog-card-props'; +import { renderTNodeJSX } from '../utils/render-tnode'; +import mixins from '../utils/mixins'; +import getConfigReceiverMixins, { + DialogConfig, + getGlobalIconMixins, + getAttachConfigMixins, +} from '../config-provider/config-receiver'; +import { emitEvent } from '../utils/event'; + +export default mixins( + getConfigReceiverMixins('dialog'), + getGlobalIconMixins(), + getAttachConfigMixins('dialog'), +).extend({ + name: 'TDialogCard', + + components: { + TButton, + }, + + props: { + ...dialogProps, + ...dialogCardProps, + instanceGlobal: Object, + }, + + computed: { + isModal(): boolean { + return this.mode === 'modal'; + }, + isModeLess(): boolean { + return this.mode === 'modeless'; + }, + isFullScreen(): boolean { + return this.mode === 'full-screen'; + }, + }, + + methods: { + closeBtnAction(e: MouseEvent) { + emitEvent>(this, 'close-btn-click', { e }); + this.emitCloseEvent({ + trigger: 'close-btn', + e, + }); + }, + + cancelBtnAction(e: MouseEvent) { + emitEvent>(this, 'cancel', { e }); + this.emitCloseEvent({ + trigger: 'cancel', + e, + }); + }, + + confirmBtnAction(e: MouseEvent) { + emitEvent>(this, 'confirm', { e }); + }, + + emitCloseEvent(context: DialogCloseContext) { + emitEvent>(this, 'close', context); + this.$emit('update:visible', false); + }, + + onStopDown(e: MouseEvent) { + if (this.isModeLess && this.draggable) e.stopPropagation(); + }, + + renderHeader() { + const headerClassName = this.isFullScreen + ? [`${this.componentName}__header`, `${this.componentName}__header--fullscreen`] + : `${this.componentName}__header`; + const closeClassName = this.isFullScreen + ? [`${this.componentName}__close`, `${this.componentName}__close--fullscreen`] + : `${this.componentName}__close`; + const { CloseIcon } = this.useGlobalIcon({ + CloseIcon: TdCloseIcon, + }); + const defaultHeader =
; + const defaultCloseBtn = ; + const getIcon = () => { + const { InfoCircleFilledIcon, CheckCircleFilledIcon, ErrorCircleFilledIcon } = this.useGlobalIcon({ + InfoCircleFilledIcon: TdInfoCircleFilledIcon, + CheckCircleFilledIcon: TdCheckCircleFilledIcon, + ErrorCircleFilledIcon: TdErrorCircleFilledIcon, + }); + const icon = { + info: , + warning: , + danger: , + success: , + }; + return icon[this.theme]; + }; + return ( + (this.header || this?.closeBtn) && ( +
+
+ {getIcon()} + {this.header} +
+ {this.closeBtn ? ( + + {renderTNodeJSX(this, 'closeBtn', defaultCloseBtn)} + + ) : null} +
+ ) + ); + }, + + renderBody() { + const bodyClassName = this.theme === 'default' + ? [`${this.componentName}__body`] + : [`${this.componentName}__body`, `${this.componentName}__body__icon`]; + + if (this.isFullScreen && !!this.footer) { + bodyClassName.push(`${this.componentName}__body--fullscreen`); + } else if (this.isFullScreen) { + bodyClassName.push(`${this.componentName}__body--fullscreen--without-footer`); + } + return ( +
+ {this.body} +
+ ); + }, + + renderFooter() { + const footerClassName = this.isFullScreen + ? [`${this.componentName}__footer`, `${this.componentName}__footer--fullscreen`] + : `${this.componentName}__footer`; + + return ( +
+ {this.footer} +
+ ); + }, + }, + + render() { + return ( +
+ {this.renderHeader()} + {this.renderBody()} + {!!this.footer && this.renderFooter()} +
+ ); + }, +}); diff --git a/src/dialog/dialog.md b/src/dialog/dialog.md index 2758b062d..98dde7405 100644 --- a/src/dialog/dialog.md +++ b/src/dialog/dialog.md @@ -35,6 +35,12 @@ {{ plugin }} ## API +### DialogCard Props + +名称 | 类型 | 默认值 | 描述 | 必传 +-- | -- | -- | -- | -- +`Pick` | String / Slot / Function | - | 继承 `Pick` 中的全部属性。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N + ### Dialog Props 名称 | 类型 | 默认值 | 描述 | 必传 diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index dc4b71d7c..abd59ccb4 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -24,6 +24,7 @@ import { AttachNode, ClassName, Styles } from '../common'; import { updateElement } from '../hooks/useDestroyOnClose'; import stack from './stack'; import { getScrollbarWidth } from '../_common/js/utils/getScrollbarWidth'; +import TDialogCard from './dialog-card'; function getCSSValue(v: string | number) { return isNaN(Number(v)) ? v : `${Number(v)}px`; @@ -87,10 +88,6 @@ export default mixins( isModeLess(): boolean { return this.mode === 'modeless'; }, - // 是否普通对话框,没有脱离文档流的对话框 - isNormal(): boolean { - return this.mode === 'normal'; - }, isFullScreen(): boolean { return this.mode === 'full-screen'; }, @@ -111,17 +108,18 @@ export default mixins( return dialogClass; }, positionClass(): ClassName { - if (this.isNormal) return []; if (this.isFullScreen) return [`${this.componentName}__position_fullscreen`]; - const dialogClass = [ - `${this.componentName}__position`, - !!this.top && `${this.componentName}--top`, - `${this.placement && !this.top ? `${this.componentName}--${this.placement}` : ''}`, - ]; - return dialogClass; + if (this.isModal || this.isModeLess) { + return [ + `${this.componentName}__position`, + !!this.top && `${this.componentName}--top`, + `${this.placement && !this.top ? `${this.componentName}--${this.placement}` : ''}`, + ]; + } + return []; }, wrapClass(): ClassName { - return [!this.isNormal && `${this.componentName}__wrap`]; + return [(this.isModal || this.isModeLess || this.isFullScreen) && `${this.componentName}__wrap`]; }, ctxClass(): ClassName { // dialog__ctx--fixed 绝对定位 @@ -153,7 +151,7 @@ export default mixins( return !this.isFullScreen ? { width: getCSSValue(this.width), ...this.dialogStyle } : { ...this.dialogStyle }; // width全屏模式不生效; }, computedAttach(): AttachNode { - return this.showInAttachedElement || this.isNormal ? undefined : this.attach || this.globalAttach(); + return this.showInAttachedElement ? undefined : this.attach || this.globalAttach(); }, }, @@ -431,13 +429,10 @@ export default mixins( if (this.isModeLess && this.draggable) e.stopPropagation(); }, renderDialog() { - const { CloseIcon } = this.useGlobalIcon({ - CloseIcon: TdCloseIcon, - }); // header 值为 true 显示空白头部 const defaultHeader =
; - const defaultCloseBtn = ; - const body = renderContent(this, 'default', 'body'); + const headerContent = renderTNodeJSX(this, 'header', defaultHeader); + const bodyContent = renderContent(this, 'default', 'body'); // this.getConfirmBtn is a function of ActionMixin // this.getCancelBtn is a function of ActionMixin const defaultFooter = ( @@ -457,54 +452,29 @@ export default mixins( })} ); - const headerClassName = this.isFullScreen - ? [`${this.componentName}__header`, `${this.componentName}__header--fullscreen`] - : `${this.componentName}__header`; - const closeClassName = this.isFullScreen - ? [`${this.componentName}__close`, `${this.componentName}__close--fullscreen`] - : `${this.componentName}__close`; - const bodyClassName = this.theme === 'default' - ? [`${this.componentName}__body`] - : [`${this.componentName}__body`, `${this.componentName}__body__icon`]; - const footerContent = renderTNodeJSX(this, 'footer', defaultFooter); - - if (this.isFullScreen && footerContent) { - bodyClassName.push(`${this.componentName}__body--fullscreen`); - } else if (this.isFullScreen) { - bodyClassName.push(`${this.componentName}__body--fullscreen--without-footer`); - } - const footerClassName = this.isFullScreen - ? [`${this.componentName}__footer`, `${this.componentName}__footer--fullscreen`] - : `${this.componentName}__footer`; - - const footer = this.footer ? ( -
- {footerContent} -
- ) : null; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { + body, header, footer, dialogClassName, theme, onConfirm, onCancel, onCloseBtnClick, ...otherProps + } = this; // 此处获取定位方式 top 优先级较高 存在时 默认使用top定位 return ( // 非模态形态下draggable为true才允许拖拽
-
-
- {this.getIcon()} - {renderTNodeJSX(this, 'header', defaultHeader)} -
- {this.closeBtn ? ( - - {renderTNodeJSX(this, 'closeBtn', defaultCloseBtn)} - - ) : null} -
- -
- {body} -
- {footer} +
diff --git a/src/dialog/index.ts b/src/dialog/index.ts index 5c2a194fa..05088052d 100644 --- a/src/dialog/index.ts +++ b/src/dialog/index.ts @@ -1,4 +1,5 @@ import _Dialog from './dialog'; +import _DialogCard from './dialog-card'; import withInstall from '../utils/withInstall'; import { TdDialogProps } from './type'; @@ -8,5 +9,7 @@ export * from './type'; export type DialogProps = TdDialogProps; export const Dialog = withInstall(_Dialog); +export const DialogCard = withInstall(_DialogCard); + export { default as DialogPlugin } from './plugin'; export default Dialog; diff --git a/src/dialog/type.ts b/src/dialog/type.ts index a74f1d6a4..099df97b8 100644 --- a/src/dialog/type.ts +++ b/src/dialog/type.ts @@ -80,10 +80,10 @@ export interface TdDialogProps { */ header?: string | boolean | TNode; /** - * 对话框类型,有 4 种:模态对话框、非模态对话框、普通对话框、全屏对话框。弹出「模态对话框」时,只能操作对话框里面的内容,不能操作其他内容。弹出「非模态对话框」时,则可以操作页面内所有内容。「普通对话框」是指没有脱离文档流的对话框,可以在这个基础上开发更多的插件 + * 对话框类型,有 3 种:模态对话框、非模态对话框、全屏对话框。弹出「模态对话框」时,只能操作对话框里面的内容,不能操作其他内容。弹出「非模态对话框」时,则可以操作页面内所有内容。 * @default modal */ - mode?: 'modal' | 'modeless' | 'normal' | 'full-screen'; + mode?: 'modal' | 'modeless' | 'full-screen'; /** * 对话框位置,内置两种:垂直水平居中显示 和 靠近顶部(top:20%)显示。默认情况,为避免贴顶或贴底,顶部和底部距离最小为 `48px`,可通过调整 `top` 覆盖默认大小 * @default top @@ -180,6 +180,7 @@ export interface TdDialogCardProps | 'onCancel' | 'onCloseBtnClick' | 'onConfirm' + | 'confirmLoading' > {} export interface DialogOptions extends Omit { From f1a4c49c557b198691d381f1d1fb4a203a29f554 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Sun, 23 Feb 2025 16:08:04 +0800 Subject: [PATCH 02/15] feat: dialogCard --- src/dialog/dialog-card.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/dialog/dialog-card.tsx b/src/dialog/dialog-card.tsx index 7ce4ce4ca..eafa20432 100644 --- a/src/dialog/dialog-card.tsx +++ b/src/dialog/dialog-card.tsx @@ -57,18 +57,6 @@ export default mixins( }); }, - cancelBtnAction(e: MouseEvent) { - emitEvent>(this, 'cancel', { e }); - this.emitCloseEvent({ - trigger: 'cancel', - e, - }); - }, - - confirmBtnAction(e: MouseEvent) { - emitEvent>(this, 'confirm', { e }); - }, - emitCloseEvent(context: DialogCloseContext) { emitEvent>(this, 'close', context); this.$emit('update:visible', false); From fd5a34098a6ee4e12413d19c23f3ca1043c6cf36 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Sun, 23 Feb 2025 16:20:18 +0800 Subject: [PATCH 03/15] feat: dialogCard Attach Problem --- src/dialog/dialog.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index abd59ccb4..576d68539 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -151,7 +151,9 @@ export default mixins( return !this.isFullScreen ? { width: getCSSValue(this.width), ...this.dialogStyle } : { ...this.dialogStyle }; // width全屏模式不生效; }, computedAttach(): AttachNode { - return this.showInAttachedElement ? undefined : this.attach || this.globalAttach(); + return this.showInAttachedElement || !this.isModal || !this.isModeLess || !this.isFullScreen + ? undefined + : this.attach || this.globalAttach(); }, }, From da33f38cc39f9a0834d322bdd304270e01746bc0 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Sun, 23 Feb 2025 21:42:34 +0800 Subject: [PATCH 04/15] chore: remove var --- src/dialog/dialog-card.tsx | 1 - src/dialog/dialog.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/dialog/dialog-card.tsx b/src/dialog/dialog-card.tsx index eafa20432..04da51a95 100644 --- a/src/dialog/dialog-card.tsx +++ b/src/dialog/dialog-card.tsx @@ -76,7 +76,6 @@ export default mixins( const { CloseIcon } = this.useGlobalIcon({ CloseIcon: TdCloseIcon, }); - const defaultHeader =
; const defaultCloseBtn = ; const getIcon = () => { const { InfoCircleFilledIcon, CheckCircleFilledIcon, ErrorCircleFilledIcon } = this.useGlobalIcon({ diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index 576d68539..f1fefa2a8 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -1,7 +1,6 @@ import Vue from 'vue'; import { isNumber, throttle } from 'lodash-es'; import { - CloseIcon as TdCloseIcon, InfoCircleFilledIcon as TdInfoCircleFilledIcon, CheckCircleFilledIcon as TdCheckCircleFilledIcon, ErrorCircleFilledIcon as TdErrorCircleFilledIcon, From eb0fb4b4361ed0344ea8292815d403148d531015 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Tue, 25 Feb 2025 00:22:24 +0800 Subject: [PATCH 05/15] feat: dialogCard --- src/dialog/dialog-card.tsx | 28 ++++++++++++++--- src/dialog/dialog.tsx | 61 +++++++++++--------------------------- 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/src/dialog/dialog-card.tsx b/src/dialog/dialog-card.tsx index 04da51a95..c34ef052e 100644 --- a/src/dialog/dialog-card.tsx +++ b/src/dialog/dialog-card.tsx @@ -5,7 +5,7 @@ import { CheckCircleFilledIcon as TdCheckCircleFilledIcon, ErrorCircleFilledIcon as TdErrorCircleFilledIcon, } from 'tdesign-icons-vue'; - +import ActionMixin from './actions'; import TButton from '../button'; import { DialogCloseContext, TdDialogProps } from './type'; import dialogProps from './props'; @@ -20,6 +20,7 @@ import getConfigReceiverMixins, { import { emitEvent } from '../utils/event'; export default mixins( + ActionMixin, getConfigReceiverMixins('dialog'), getGlobalIconMixins(), getAttachConfigMixins('dialog'), @@ -129,10 +130,29 @@ export default mixins( const footerClassName = this.isFullScreen ? [`${this.componentName}__footer`, `${this.componentName}__footer--fullscreen`] : `${this.componentName}__footer`; - + // this.getConfirmBtn is a function of ActionMixin + // this.getCancelBtn is a function of ActionMixin + const defaultFooter = ( +
+ {this.getCancelBtn({ + cancelBtn: this.cancelBtn, + globalCancel: this.instanceGlobal?.cancel || this.global.cancel, + className: `${this.componentName}__cancel`, + })} + {this.getConfirmBtn({ + theme: this.theme, + confirmBtn: this.confirmBtn, + confirmLoading: this.confirmLoading, + globalConfirm: this.instanceGlobal?.confirm || this.global.confirm, + globalConfirmBtnTheme: this.instanceGlobal?.confirmBtnTheme || this.global.confirmBtnTheme, + className: `${this.componentName}__confirm`, + })} +
+ ); + const footerContent = renderTNodeJSX(this, 'footer', defaultFooter); return (
- {this.footer} + {footerContent}
); }, @@ -143,7 +163,7 @@ export default mixins(
{this.renderHeader()} {this.renderBody()} - {!!this.footer && this.renderFooter()} + {this.renderFooter()}
); }, diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index f1fefa2a8..f79ad8ac5 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -1,13 +1,6 @@ import Vue from 'vue'; import { isNumber, throttle } from 'lodash-es'; -import { - InfoCircleFilledIcon as TdInfoCircleFilledIcon, - CheckCircleFilledIcon as TdCheckCircleFilledIcon, - ErrorCircleFilledIcon as TdErrorCircleFilledIcon, -} from 'tdesign-icons-vue'; - import TButton from '../button'; -import ActionMixin from './actions'; import { DialogCloseContext, TdDialogProps } from './type'; import props from './props'; import { renderTNodeJSX, renderContent } from '../utils/render-tnode'; @@ -47,7 +40,6 @@ if (typeof window !== 'undefined' && window.document && window.document.document let key = 1; export default mixins( - ActionMixin, getConfigReceiverMixins('dialog'), getGlobalIconMixins(), getAttachConfigMixins('dialog'), @@ -355,20 +347,6 @@ export default mixins( return !!eventFuncs?.length; }, - getIcon() { - const { InfoCircleFilledIcon, CheckCircleFilledIcon, ErrorCircleFilledIcon } = this.useGlobalIcon({ - InfoCircleFilledIcon: TdInfoCircleFilledIcon, - CheckCircleFilledIcon: TdCheckCircleFilledIcon, - ErrorCircleFilledIcon: TdErrorCircleFilledIcon, - }); - const icon = { - info: , - warning: , - danger: , - success: , - }; - return icon[this.theme]; - }, mousedownHandler(targetEvent: MouseEvent) { const target = this.$refs.dialog as HTMLElement; // 算出鼠标相对元素的位置 @@ -434,29 +412,20 @@ export default mixins( const defaultHeader =
; const headerContent = renderTNodeJSX(this, 'header', defaultHeader); const bodyContent = renderContent(this, 'default', 'body'); - // this.getConfirmBtn is a function of ActionMixin - // this.getCancelBtn is a function of ActionMixin - const defaultFooter = ( -
- {this.getCancelBtn({ - cancelBtn: this.cancelBtn, - globalCancel: this.instanceGlobal?.cancel || this.global.cancel, - className: `${this.componentName}__cancel`, - })} - {this.getConfirmBtn({ - theme: this.theme, - confirmBtn: this.confirmBtn, - confirmLoading: this.confirmLoading, - globalConfirm: this.instanceGlobal?.confirm || this.global.confirm, - globalConfirmBtnTheme: this.instanceGlobal?.confirmBtnTheme || this.global.confirmBtnTheme, - className: `${this.componentName}__confirm`, - })} -
- ); - const footerContent = renderTNodeJSX(this, 'footer', defaultFooter); // eslint-disable-next-line @typescript-eslint/no-unused-vars const { - body, header, footer, dialogClassName, theme, onConfirm, onCancel, onCloseBtnClick, ...otherProps + body, + header, + footer, + confirmBtn, + cancelBtn, + confirmLoading, + dialogClassName, + theme, + onConfirm, + onCancel, + onCloseBtnClick, + ...otherProps } = this; // 此处获取定位方式 top 优先级较高 存在时 默认使用top定位 return ( @@ -470,8 +439,12 @@ export default mixins( {...otherProps} header={headerContent} body={bodyContent} - footer={footerContent} + footer={footer} class={dialogClassName} + confirmBtn={confirmBtn} + cancelBtn={cancelBtn} + confirmLoading={confirmLoading} + instanceGlobal={this.instanceGlobal} onConfirm={this.confirmBtnAction} onCancel={this.cancelBtnAction} onCloseBtnClick={this.closeBtnAction} From 337b02b36fc5ce7dd2dece1820ff2edd7ca80795 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Tue, 25 Feb 2025 00:27:27 +0800 Subject: [PATCH 06/15] refactor: f --- src/dialog/dialog-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialog/dialog-card.tsx b/src/dialog/dialog-card.tsx index c34ef052e..29dc5a19e 100644 --- a/src/dialog/dialog-card.tsx +++ b/src/dialog/dialog-card.tsx @@ -163,7 +163,7 @@ export default mixins(
{this.renderHeader()} {this.renderBody()} - {this.renderFooter()} + {!!this.footer && this.renderFooter()}
); }, From 2f7bb0b9896a9c8d35b4ec70db95ff8bac168660 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Sun, 2 Mar 2025 14:38:52 +0800 Subject: [PATCH 07/15] chore: move out-DOM into dialogCardEl --- src/dialog/dialog-card.tsx | 51 +++++++++++++++++--- src/dialog/dialog.tsx | 95 +++++++++++++++----------------------- 2 files changed, 81 insertions(+), 65 deletions(-) diff --git a/src/dialog/dialog-card.tsx b/src/dialog/dialog-card.tsx index 29dc5a19e..02794a8f8 100644 --- a/src/dialog/dialog-card.tsx +++ b/src/dialog/dialog-card.tsx @@ -10,7 +10,7 @@ import TButton from '../button'; import { DialogCloseContext, TdDialogProps } from './type'; import dialogProps from './props'; import dialogCardProps from './dialog-card-props'; -import { renderTNodeJSX } from '../utils/render-tnode'; +import { renderContent, renderTNodeJSX } from '../utils/render-tnode'; import mixins from '../utils/mixins'; import getConfigReceiverMixins, { DialogConfig, @@ -18,6 +18,11 @@ import getConfigReceiverMixins, { getAttachConfigMixins, } from '../config-provider/config-receiver'; import { emitEvent } from '../utils/event'; +import { ClassName, Styles } from '../common'; + +function getCSSValue(v: string | number) { + return isNaN(Number(v)) ? v : `${Number(v)}px`; +} export default mixins( ActionMixin, @@ -47,6 +52,22 @@ export default mixins( isFullScreen(): boolean { return this.mode === 'full-screen'; }, + dialogClass(): ClassName { + const dialogClass = [ + `${this.componentName}`, + `${this.componentName}__modal-${this.theme}`, + this.isModeLess && this.draggable && `${this.componentName}--draggable`, + ]; + if (this.isFullScreen) { + dialogClass.push(`${this.componentName}__fullscreen`); + } else { + dialogClass.push(`${this.componentName}--default`); + } + return dialogClass; + }, + computedDialogStyle(): Styles { + return !this.isFullScreen ? { width: getCSSValue(this.width), ...this.dialogStyle } : { ...this.dialogStyle }; // width全屏模式不生效; + }, }, methods: { @@ -60,7 +81,20 @@ export default mixins( emitCloseEvent(context: DialogCloseContext) { emitEvent>(this, 'close', context); - this.$emit('update:visible', false); + }, + + // used in mixins of ActionMixin + cancelBtnAction(e: MouseEvent) { + emitEvent>(this, 'cancel', { e }); + this.emitCloseEvent({ + trigger: 'cancel', + e, + }); + }, + + // used in mixins of ActionMixin + confirmBtnAction(e: MouseEvent) { + emitEvent>(this, 'confirm', { e }); }, onStopDown(e: MouseEvent) { @@ -68,6 +102,9 @@ export default mixins( }, renderHeader() { + // header 值为 true 显示空白头部 + const defaultHeader =
; + const header = renderTNodeJSX(this, 'header', defaultHeader); const headerClassName = this.isFullScreen ? [`${this.componentName}__header`, `${this.componentName}__header--fullscreen`] : `${this.componentName}__header`; @@ -85,6 +122,7 @@ export default mixins( ErrorCircleFilledIcon: TdErrorCircleFilledIcon, }); const icon = { + default: null as null, info: , warning: , danger: , @@ -93,11 +131,11 @@ export default mixins( return icon[this.theme]; }; return ( - (this.header || this?.closeBtn) && ( + (header || this?.closeBtn) && (
{getIcon()} - {this.header} + {header}
{this.closeBtn ? ( @@ -110,6 +148,7 @@ export default mixins( }, renderBody() { + const body = renderContent(this, 'default', 'body'); const bodyClassName = this.theme === 'default' ? [`${this.componentName}__body`] : [`${this.componentName}__body`, `${this.componentName}__body__icon`]; @@ -121,7 +160,7 @@ export default mixins( } return (
- {this.body} + {body}
); }, @@ -160,7 +199,7 @@ export default mixins( render() { return ( -
+
{this.renderHeader()} {this.renderBody()} {!!this.footer && this.renderFooter()} diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index f79ad8ac5..475c40eb6 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -3,7 +3,6 @@ import { isNumber, throttle } from 'lodash-es'; import TButton from '../button'; import { DialogCloseContext, TdDialogProps } from './type'; import props from './props'; -import { renderTNodeJSX, renderContent } from '../utils/render-tnode'; import mixins from '../utils/mixins'; import getConfigReceiverMixins, { DialogConfig, @@ -18,10 +17,6 @@ import stack from './stack'; import { getScrollbarWidth } from '../_common/js/utils/getScrollbarWidth'; import TDialogCard from './dialog-card'; -function getCSSValue(v: string | number) { - return isNaN(Number(v)) ? v : `${Number(v)}px`; -} - let mousePosition: { x: number; y: number } | null; const getClickPosition = (e: MouseEvent) => { mousePosition = { @@ -85,19 +80,6 @@ export default mixins( maskClass(): ClassName { return [`${this.componentName}__mask`, !this.showOverlay && `${this.classPrefix}-is-hidden`]; }, - dialogClass(): ClassName { - const dialogClass = [ - `${this.componentName}`, - `${this.componentName}__modal-${this.theme}`, - this.isModeLess && this.draggable && `${this.componentName}--draggable`, - ]; - if (this.isFullScreen) { - dialogClass.push(`${this.componentName}__fullscreen`); - } else { - dialogClass.push(`${this.componentName}--default`); - } - return dialogClass; - }, positionClass(): ClassName { if (this.isFullScreen) return [`${this.componentName}__position_fullscreen`]; if (this.isModal || this.isModeLess) { @@ -138,9 +120,6 @@ export default mixins( } return topStyle; }, - computedDialogStyle(): Styles { - return !this.isFullScreen ? { width: getCSSValue(this.width), ...this.dialogStyle } : { ...this.dialogStyle }; // width全屏模式不生效; - }, computedAttach(): AttachNode { return this.showInAttachedElement || !this.isModal || !this.isModeLess || !this.isFullScreen ? undefined @@ -162,7 +141,7 @@ export default mixins( } this.$nextTick(() => { - const target = this.$refs.dialog as HTMLElement; + const target = (this.$refs.dialog as Vue).$el as HTMLElement; if (mousePosition && target) { target.style.transformOrigin = `${mousePosition.x - target.offsetLeft}px ${ mousePosition.y - target.offsetTop @@ -320,7 +299,7 @@ export default mixins( // 关闭弹窗动画结束时事件 afterLeave() { if (this.isModeLess && this.draggable) { - const target = this.$refs.dialog as HTMLElement; + const target = (this.$refs.dialog as Vue).$el as HTMLElement; if (!target) return; // 关闭弹窗 清空拖拽设置的相关css target.style.position = 'relative'; @@ -337,18 +316,18 @@ export default mixins( this.$emit('update:visible', false); }, - // Vue在引入阶段对事件的处理还做了哪些初始化操作。Vue在实例上用一个_events属性存贮管理事件的派发和更新, - // 暴露出$on, $once, $off, $emit方法给外部管理事件和派发执行事件 - // 所以通过判断_events某个事件下监听函数数组是否超过一个,可以判断出组件是否监听了当前事件 - hasEventOn(name: string) { - // _events 因没有被暴露在vue实例接口中,只能把这个规则注释掉 - /* eslint-disable dot-notation */ - const eventFuncs = this['_events']?.[name]; - return !!eventFuncs?.length; - }, + // // Vue在引入阶段对事件的处理还做了哪些初始化操作。Vue在实例上用一个_events属性存贮管理事件的派发和更新, + // // 暴露出$on, $once, $off, $emit方法给外部管理事件和派发执行事件 + // // 所以通过判断_events某个事件下监听函数数组是否超过一个,可以判断出组件是否监听了当前事件 + // hasEventOn(name: string) { + // // _events 因没有被暴露在vue实例接口中,只能把这个规则注释掉 + // /* eslint-disable dot-notation */ + // const eventFuncs = this['_events']?.[name]; + // return !!eventFuncs?.length; + // }, mousedownHandler(targetEvent: MouseEvent) { - const target = this.$refs.dialog as HTMLElement; + const target = (this.$refs.dialog as Vue).$el as HTMLElement; // 算出鼠标相对元素的位置 this.disX = targetEvent.clientX - target.offsetLeft; this.disY = targetEvent.clientY - target.offsetTop; @@ -366,7 +345,7 @@ export default mixins( document.addEventListener('dragend', this.mouseUpHandler); }, mouseMoverHandler(documentEvent: MouseEvent) { - const target = this.$refs.dialog as HTMLElement; + const target = (this.$refs.dialog as Vue).$el as HTMLElement; // 用鼠标的位置减去鼠标相对元素的位置,得到元素的位置 let left = documentEvent.clientX - this.disX; let top = documentEvent.clientY - this.disY; @@ -387,7 +366,7 @@ export default mixins( document.removeEventListener('dragend', this.mouseUpHandler); }, initDragEvent(status: boolean) { - const target = this.$refs.dialog as HTMLElement; + const target = (this.$refs.dialog as Vue).$el as HTMLElement; if (status) { target.addEventListener('mousedown', this.mousedownHandler); } else { @@ -399,7 +378,7 @@ export default mixins( */ resizeAdjustPosition() { if (this.visible) { - const target = this.$refs.dialog as HTMLElement; + const target = (this.$refs.dialog as Vue).$el as HTMLElement; target.style.left = `${this.dLeft * (window.innerWidth / this.windowInnerWidth)}px`; target.style.top = `${this.dTop * (window.innerHeight / this.windowInnerHeight)}px`; } @@ -408,10 +387,6 @@ export default mixins( if (this.isModeLess && this.draggable) e.stopPropagation(); }, renderDialog() { - // header 值为 true 显示空白头部 - const defaultHeader =
; - const headerContent = renderTNodeJSX(this, 'header', defaultHeader); - const bodyContent = renderContent(this, 'default', 'body'); // eslint-disable-next-line @typescript-eslint/no-unused-vars const { body, @@ -426,30 +401,32 @@ export default mixins( onCancel, onCloseBtnClick, ...otherProps - } = this; + } = this.$props; // 此处获取定位方式 top 优先级较高 存在时 默认使用top定位 return ( // 非模态形态下draggable为true才允许拖拽
-
- -
+ + {this.$slots.header} + {this.$slots.default ? this.$slots.default : this.$slots.body} +
); From 77f87e06ba061f2127abe9f0b4d5f9f5f89f651a Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Sun, 2 Mar 2025 14:41:29 +0800 Subject: [PATCH 08/15] fix: tree hook typo --- src/tree/hooks/useDragHandle.ts | 12 ++++++------ src/tree/tree-types.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tree/hooks/useDragHandle.ts b/src/tree/hooks/useDragHandle.ts index 2948ba354..2e57724b7 100644 --- a/src/tree/hooks/useDragHandle.ts +++ b/src/tree/hooks/useDragHandle.ts @@ -1,6 +1,6 @@ import { TreeNode } from '../adapt'; import { - TreeProps, TypDragEventState, TypeTreeState, TypeDragHandle, + TreeProps, TypeDragEventState, TypeTreeState, TypeDragHandle, } from '../tree-types'; import { DragPosition } from './useDraggable'; import { emitEvent } from '../util'; @@ -11,7 +11,7 @@ export default function useDragHandle(state: TypeTreeState) { } = state; let dragNode: TreeNode = null; - const handleDragStart = (state: TypDragEventState) => { + const handleDragStart = (state: TypeDragEventState) => { const { dragEvent, node } = state; dragNode = node; @@ -22,7 +22,7 @@ export default function useDragHandle(state: TypeTreeState) { emitEvent>(props, context, 'drag-start', ctx); }; - const handleDragEnd = (state: TypDragEventState) => { + const handleDragEnd = (state: TypeDragEventState) => { const { dragEvent, node } = state; dragNode = node; @@ -33,7 +33,7 @@ export default function useDragHandle(state: TypeTreeState) { emitEvent>(props, context, 'drag-end', ctx); }; - const handleDragOver = (state: TypDragEventState) => { + const handleDragOver = (state: TypeDragEventState) => { const { dragEvent, node } = state; const ctx = { node: node.getModel(), @@ -42,7 +42,7 @@ export default function useDragHandle(state: TypeTreeState) { emitEvent>(props, context, 'drag-over', ctx); }; - const handleDragLeave = (state: TypDragEventState) => { + const handleDragLeave = (state: TypeDragEventState) => { const { dragEvent, node } = state; const ctx = { node: node.getModel(), @@ -51,7 +51,7 @@ export default function useDragHandle(state: TypeTreeState) { emitEvent>(props, context, 'drag-leave', ctx); }; - const handleDrop = (state: TypDragEventState) => { + const handleDrop = (state: TypeDragEventState) => { const { dragEvent, node, dropPosition } = state; if (node.value === dragNode.value || node.getParents().some((_node) => _node.value === dragNode.value)) return; diff --git a/src/tree/tree-types.ts b/src/tree/tree-types.ts index 4c9d8fe03..083d8bc5f 100644 --- a/src/tree/tree-types.ts +++ b/src/tree/tree-types.ts @@ -55,7 +55,7 @@ export interface TypeEventState extends TypeTreeEventState { dropPosition?: number; } -export interface TypDragEventState extends TypeEventState { +export interface TypeDragEventState extends TypeEventState { dragEvent?: DragEvent; dropPosition?: number; } @@ -91,11 +91,11 @@ export interface TypeScopedSlots { } export interface TypeDragHandle { - handleDragStart: (state: TypDragEventState) => void; - handleDragEnd: (state: TypDragEventState) => void; - handleDragOver: (state: TypDragEventState) => void; - handleDragLeave: (state: TypDragEventState) => void; - handleDrop: (state: TypDragEventState) => void; + handleDragStart: (state: TypeDragEventState) => void; + handleDragEnd: (state: TypeDragEventState) => void; + handleDragOver: (state: TypeDragEventState) => void; + handleDragLeave: (state: TypeDragEventState) => void; + handleDrop: (state: TypeDragEventState) => void; } export interface TypeTreeScope { From 706198c0bf3c16db58c10239c3d6d10f51649fb3 Mon Sep 17 00:00:00 2001 From: Wesley <69622989+Wesley-0808@users.noreply.github.com> Date: Mon, 3 Mar 2025 11:11:22 +0800 Subject: [PATCH 09/15] =?UTF-8?q?fix=EF=BC=9A=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dialog/dialog.tsx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index 475c40eb6..579b86fc5 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -388,20 +388,7 @@ export default mixins( }, renderDialog() { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { - body, - header, - footer, - confirmBtn, - cancelBtn, - confirmLoading, - dialogClassName, - theme, - onConfirm, - onCancel, - onCloseBtnClick, - ...otherProps - } = this.$props; + const { body, header, footer, confirmBtn, cancelBtn, confirmLoading, dialogClassName, theme, onConfirm, onCancel, onCloseBtnClick, ...otherProps } = this.$props; // 此处获取定位方式 top 优先级较高 存在时 默认使用top定位 return ( // 非模态形态下draggable为true才允许拖拽 From 85b7cfe51c1b0bf14361f77d7d6c5953c2fae139 Mon Sep 17 00:00:00 2001 From: Wesley <69622989+Wesley-0808@users.noreply.github.com> Date: Mon, 3 Mar 2025 11:25:59 +0800 Subject: [PATCH 10/15] fix: lint --- src/dialog/dialog.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index 579b86fc5..0b8aa9cbd 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -387,8 +387,22 @@ export default mixins( if (this.isModeLess && this.draggable) e.stopPropagation(); }, renderDialog() { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { body, header, footer, confirmBtn, cancelBtn, confirmLoading, dialogClassName, theme, onConfirm, onCancel, onCloseBtnClick, ...otherProps } = this.$props; + /* eslint-disable @typescript-eslint/no-unused-vars */ + const { + body, + header, + footer, + confirmBtn, + cancelBtn, + confirmLoading, + dialogClassName, + theme, + onConfirm, + onCancel, + onCloseBtnClick, + ...otherProps + } = this.$props; + /* eslint-enable @typescript-eslint/no-unused-vars */ // 此处获取定位方式 top 优先级较高 存在时 默认使用top定位 return ( // 非模态形态下draggable为true才允许拖拽 From 578a4c568b909cd9e9d3bf0a1234eebf22676248 Mon Sep 17 00:00:00 2001 From: Wesley <69622989+Wesley-0808@users.noreply.github.com> Date: Mon, 3 Mar 2025 11:30:21 +0800 Subject: [PATCH 11/15] fix: lint --- src/dialog/dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialog/dialog.tsx b/src/dialog/dialog.tsx index 0b8aa9cbd..a0751ea70 100644 --- a/src/dialog/dialog.tsx +++ b/src/dialog/dialog.tsx @@ -401,7 +401,7 @@ export default mixins( onCancel, onCloseBtnClick, ...otherProps - } = this.$props; + } = this.$props; /* eslint-enable @typescript-eslint/no-unused-vars */ // 此处获取定位方式 top 优先级较高 存在时 默认使用top定位 return ( From d994f460c8feb5f7cbd54d5aaf45cdb0fce25aa2 Mon Sep 17 00:00:00 2001 From: Wesley <985189328@qq.com> Date: Sat, 15 Mar 2025 00:18:28 +0800 Subject: [PATCH 12/15] chore: update snapshot --- test/snap/__snapshots__/csr.test.js.snap | 3817 +++++++++++++++++----- 1 file changed, 3051 insertions(+), 766 deletions(-) diff --git a/test/snap/__snapshots__/csr.test.js.snap b/test/snap/__snapshots__/csr.test.js.snap index 803b1e03a..22d0382c7 100644 --- a/test/snap/__snapshots__/csr.test.js.snap +++ b/test/snap/__snapshots__/csr.test.js.snap @@ -44576,7 +44576,84 @@ exports[`csr snapshot test > csr test ./src/dialog/_example/async.vue 1`] = ` + > + `; @@ -44655,7 +44732,92 @@ exports[`csr snapshot test > csr test ./src/dialog/_example/attach.vue 1`] = ` data-v-d38b880a="" duration="300" name="t-dialog-zoom__vue" - /> + > +
- - `; @@ -44963,255 +45126,238 @@ exports[`csr snapshot test > csr test ./src/dialog/_example/base.vue 1`] = ` - -
-`; - -exports[`csr snapshot test > csr test ./src/dialog/_example/custom.vue 1`] = ` -
-

- 弹窗内容自定义 -

-
-
+ >
- -
+ class="t-dialog__mask" + />
- +
+
+ 对话框标题 +
+ + + + + +
+
+
+
+ +
+
+
+
+ 共 30 条数据 +
+
+
+
+
+
+ + + 10 条/页 + + + + + + +
+
+
+
+
+ + + +
+
    +
  • + 1 +
  • +
  • + 2 +
  • +
  • + 3 +
  • +
+
+ + + +
+
+
+
+
+ +
+
+
+ + + -
-
-
-

- 操作按钮自定义 -

-
-

- 底部按钮有两个控制属性:confirmBtn 和 cancelBtn。属性类型有多种:string | ButtonProps | TNode。也可以通过 footer 来自定义控制 -

-
-
-
- -
-
- -
-
- -
-
- -
-
- - - - -
-`; - -exports[`csr snapshot test > csr test ./src/dialog/_example/drag.vue 1`] = ` -
-
-
- -
-
- -
-
- -
-
-
- - - -
-`; - -exports[`csr snapshot test > csr test ./src/dialog/_example/icon.vue 1`] = ` -
- -
- - - + + +
- - - - - + `; -exports[`csr snapshot test > csr test ./src/dialog/_example/warning.vue 1`] = ` +exports[`csr snapshot test > csr test ./src/dialog/_example/drag.vue 1`] = `
csr test ./src/dialog/_example/warning.vue 1`] = ` - 提示反馈 + 非模态对话框-可拖拽
@@ -45929,7 +46133,7 @@ exports[`csr snapshot test > csr test ./src/dialog/_example/warning.vue 1`] = ` - 成功反馈 + 非模态对话框-不可拖拽
@@ -45943,13 +46147,1708 @@ exports[`csr snapshot test > csr test ./src/dialog/_example/warning.vue 1`] = ` - 警示反馈 + 模态对话框-不支持拖拽 -
+
+
+ + +`; + +exports[`csr snapshot test > csr test ./src/dialog/_example/icon.vue 1`] = ` +
+ + + +
+ +
+
+
+
+
+
+ + + + 温馨提示 +
+ + + + + +
+
+ 系统重启后会短暂影响页面访问,确认重启吗? +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + 推送失败 +
+ + + + + +
+
+ 请检查推送数据是否符合要求 +
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ + + + 操作成功 +
+ + + + + +
+
+ 是否前往查看订单列表 +
+ +
+
+
+
+
+
+ + + + +
+`; + +exports[`csr snapshot test > csr test ./src/dialog/_example/modal.vue 1`] = ` +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +`; + +exports[`csr snapshot test > csr test ./src/dialog/_example/plugin.vue 1`] = ` +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+`; + +exports[`csr snapshot test > csr test ./src/dialog/_example/position.vue 1`] = ` +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +