UNPKG

jobsys-newbie

Version:

Enhanced component based on ant-design-vue

234 lines (213 loc) 6.24 kB
import { computed, defineComponent, ref, watch, watchEffect } from "vue" import { useDraggable } from "@vueuse/core" import { Drawer, Modal } from "ant-design-vue" import { CloseOutlined, CompressOutlined, ExpandOutlined } from "@ant-design/icons-vue" import "./index.less" import { isFunction, isObject } from "lodash-es" import { genPixel } from "../../utils/style" import { useT } from "../../hooks/index.js" /** * 弹窗组件 * 可以选择使用 Modal 或者是 Drawer * Modal 支持全屏以及拖动 * * @version 1.0.0 */ export default defineComponent({ name: "NewbieModal", props: { /** * 弹窗类型 * * @values modal, drawer */ type: { type: String, default: "modal" }, /** * 可见性控制 * `v-model:visible` */ visible: { type: Boolean, default: false }, /** * 宽度 */ width: { type: [Number, String], default: 800 }, /** * 高度,仅当Type为modal时有效 */ height: { type: [Number, String], default: 500 }, /** * 标题 */ title: { type: String, default: "" }, /** * 为 `Function` 时返回 Promise, resolve(true) 正常关闭,resolve(false) 可阻止关闭; * 为 `Object` 时参考 [下方配置 beforeclose](#beforeclose) */ beforeClose: { type: [Function, Object], default: null }, /** * 原生 [Modal](https://www.antdv.com/components/modal-cn#api) 或者是 [Drawer](https://www.antdv.com/components/drawer-cn#api) 的配置 */ modalProps: { type: Object, default: () => ({}) }, }, emits: [ /** * @event update:visible * @param {boolean} visible 是否可见 */ "update:visible", /** * 关闭弹窗时回调 * * @event close */ "close", ], setup(props, { emit, slots }) { const modalTitleRef = ref(null) const isFullModal = ref(false) const isVisible = ref(false) watch( () => props.visible, (visible) => { isVisible.value = visible }, { immediate: true }, // 添加 immediate 选项 ) const onCloseModal = async () => { if (props.beforeClose && isFunction(props.beforeClose) && !(await props.beforeClose())) { return } if (props.beforeClose && isObject(props.beforeClose)) { const { title, content, okText, cancelText, trigger } = props.beforeClose let showConfirm = isFunction(trigger) ? trigger() : trigger if (showConfirm) { Modal.confirm({ title: title || useT("common.action-prompt"), content: content || useT("common.close-confirm"), okText: okText || useT("common.confirm"), cancelText: cancelText || useT("common.cancel"), onOk: () => { isFullModal.value = false emit("update:visible", false) emit("close") }, }) return } } isFullModal.value = false emit("update:visible", false) emit("close") } // 以下代码用于拖动弹窗,来自官方文档, https://www.antdv.com/components/modal-cn#components-modal-demo-modal-render const { x, y, isDragging } = useDraggable(modalTitleRef) const startX = ref(0) const startY = ref(0) const startedDrag = ref(false) const transformX = ref(0) const transformY = ref(0) const preTransformX = ref(0) const preTransformY = ref(0) const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0, }) watch([x, y], () => { if (!startedDrag.value) { startX.value = x.value startY.value = y.value const bodyRect = document.body.getBoundingClientRect() const titleRect = modalTitleRef.value.getBoundingClientRect() dragRect.value.right = bodyRect.width - titleRect.width dragRect.value.bottom = Math.max(bodyRect.height - titleRect.height, 800) preTransformX.value = transformX.value preTransformY.value = transformY.value } startedDrag.value = true }) watch(isDragging, () => { if (!isDragging) { startedDrag.value = false } }) watchEffect(() => { if (startedDrag.value) { transformX.value = preTransformX.value + Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) - startX.value transformY.value = preTransformY.value + Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) - startY.value } }) const transformStyle = computed(() => { return { transform: `translate(${transformX.value}px, ${transformY.value}px)`, } }) const modalTitle = () => ( <div class="newbie-modal-header"> <span class="newbie-modal-title" ref={modalTitleRef}> {props.title} </span> <div class="newbie-modal-actions"> {isFullModal.value ? ( <a onClick={() => (isFullModal.value = false)}> <CompressOutlined style={{ fontSize: "16px" }} /> </a> ) : ( <a onClick={() => (isFullModal.value = true)}> <ExpandOutlined style={{ fontSize: "16px" }} /> </a> )} <a onClick={onCloseModal}> <CloseOutlined style={{ fontSize: "20px", marginTop: "2px" }} /> </a> </div> </div> ) const elemModal = () => ( <Modal footer={null} wrapStyle={{ overflow: "hidden" }} wrapClassName={`newbie-modal ${isFullModal.value ? "full-modal" : ""}`} width={isFullModal.value ? "100%" : genPixel(props.width)} v-model:open={isVisible.value} bodyStyle={{ maxHeight: props.height ? genPixel(props.height) : "600px", overflow: "auto" }} closable={false} destroyOnClose maskClosable={false} onCancel={onCloseModal} {...props.modalProps} > {{ /** * @slot 弹窗内容 */ default: () => slots.default?.(), title: () => modalTitle(), modalRender: ({ originVNode }) => <div style={transformStyle.value}>{originVNode}</div>, }} </Modal> ) const elemDrawer = () => ( <Drawer title={props.title} v-model:open={isVisible.value} closable={false} destroyOnClose width={genPixel(props.width)} maskClosable={false} onClose={onCloseModal} {...props.modalProps} > {{ /** * @slot 弹窗内容 */ default: () => slots.default?.(), extra: () => <CloseOutlined onClick={onCloseModal} />, }} </Drawer> ) return () => <div class="newbie-modal">{props.type === "modal" ? elemModal() : elemDrawer()}</div> }, })