npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

fdialogsmz

v1.0.14

Published

函数式弹窗、抽屉,无内容版

Downloads

104

Readme

概述

这是一个基于 Vue 3 + TypeScript + Element Plus 构建的函数式弹窗组件,支持动态传入任意Vue组件作为内容,提供丰富的配置选项和灵活的API。

1.0.6: 优化

| 维度 | 优化前 | 优化后 | | ----- | ------------------ | ----------------- | | 多实例 | 后者冲掉前者 | 支持任意数量共存 | | 内存泄漏 | DOM/事件未清理 | 统一 unmount & 删除节点 | | 类型安全 | 大量 any | 全部严格类型推导 | | 态竞 | 全局 buttonLoading | 事件级 Set |

安装和引入

  1. 引入组件
import FDialog from 'fdialog'
import 'fdialog/dist/main.css'
app.use(FDialog);
  1. 引入弹窗组件
import { createModal, ModalHelper } from 'fdialog'

使用

  1. 创建简单弹窗
const BasicComponent = {
  setup() {
    return () => (
      <div style="padding: 20px;">
        <p>这是一个简单的弹窗内容</p>
      </div>
    )
  }
}

const showModal = async () => {
  const result = await createModal({
    component: BasicComponent,
    modalProps: {
      title: '基础弹窗',
      width: '500px'
    }
  })
  
  console.log('弹窗结果:', result)
}
  1. 使用ModalHelper工具类
// 确认对话框
const result = await ModalHelper.confirm('确定要执行此操作吗?')

// 成功提示
await ModalHelper.success('操作成功完成!')

// 警告提示  
await ModalHelper.warning('请注意操作风险')

// 错误提示
await ModalHelper.error('系统发生错误')

配置选项

| 参数 | 类型 | 默认值 | 说明 | |------|------|------------|-----------| |visible| boolean| false | 是否显示弹窗 | |title| string| '提示' | 弹窗标题 | |titleIcon| IconType| - | 标题图标类型 |
|customIconUrl| string| - | 自定义图标URL | |width| string| '50%' | 弹窗宽度 | |showFooter| boolean| true | 是否显示底部按钮 | |footerButtons| ModalButton[]| 取消/确定 | 底部按钮配置 | |footerPosition| FooterPosition | 'center' | 按钮位置 | |closeOnClickModal| boolean | true | 点击遮罩关闭 | |showClose| boolean| true | 显示关闭按钮 | |headerBackground| string | '#f2f2f2' | 头部背景色 | |headerIconSize| string | '24px' | 图标大小 | |headerSlotPosition| HeaderPosition | 'left' | 头部插槽位置 |

IconType 类型

type IconType = 'add' | 'dialog' | 'success' | 'warning' | 'error' | 'info' | 'custom' | string

FooterPosition 类型

type FooterPosition = 'left' | 'center' | 'right' | 'stretch'

HeaderPosition 类型

type HeaderPosition = 'left' | 'center' | 'right' | 'stretch'

高级用法

  1. 自定义内容组件
<template>
  <div class="user-form">
    <h3>用户信息</h3>
    <el-input v-model="name" placeholder="请输入姓名" />
    <el-input v-model="email" placeholder="请输入邮箱" />
    <div class="actions">
      <el-button @click="handleCancel">取消</el-button>
      <el-button type="primary" @click="handleSubmit">提交</el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const props = defineProps<{
  onEvent?: (event: string, data?: any) => void
  onConfirm?: (data: any) => void
  onCancel?: () => void
}>()

const name = ref('')
const email = ref('')

const handleSubmit = () => {
  if (!name.value) {
    props.onEvent?.('validation-error', { message: '姓名不能为空' })
    return
  }
  
  props.onConfirm?.({
    name: name.value,
    email: email.value
  })
}

const handleCancel = () => {
  props.onCancel?.()
}
</script>
  1. 复杂弹窗配置
const headerButton = {
    template: `<el-button type="primary" size="small">操作</el-button>`
}

const showComplexModal = async () => {
  const result = await createModal({
    component: UserForm,
    componentProps: {
      initialData: { name: '张三', email: '' }
    },
    modalProps: {
      title: '用户信息编辑',
      titleIcon: 'user',
      width: '600px',
      headerBackground: '#f0f9eb',
      headerIconSize: '28px',
      footerPosition: 'right',
      footerButtons: [
        { text: '取消', type: '', event: 'cancel', icon: 'info' },
        { text: '保存草稿', type: 'info', event: 'draft', icon: 'info' },
        { text: '确认提交', type: 'primary', event: 'submit', icon: 'success' }
      ]
    }, 
    headerSlot: headerButton,
    onComponentEvent: (event, data) => {
      console.log('收到事件:', event, data)
      // 处理自定义事件
    }
  })
}
  1. 事件处理
// 在内容组件中发射事件
props.onEvent?.('custom-event', { data: '自定义数据' })

// 在外部监听事件
onComponentEvent: (event, data) => {
  switch (event) {
    case 'custom-event':
      console.log('收到自定义事件:', data)
      break
    case 'validation-error':
      ElMessage.warning(data.message)
      break
  }
}
  1. 内容组件事件传递
const props = defineProps<{
    onEvent?: (event: string, data?: any) => void;
    emit?: (event: string, data?: any) => void;
    onClose?: (result?: any) => void;
    onConfirm?: (data?: any) => void;
    onCancel?: () => void;
}>();
// 方法1:使用props传递的回调
const handleSave = () => {
    props.onEvent?.("validation-error", { field: "name", message: "姓名不能为空" });
    return;
};

// 方法2:发送自定义事件
const handleCustomEvent = () => {
    props.onEvent?.("custom-action", {
        action: "refresh",
        timestamp: new Date().toISOString()
    });
};

// 方法3:使用emit别名(更符合Vue习惯)
const handleEmitExample = () => {
    props.emit?.("data-updated", "这是emit事件传递参数");
};

// 暴露给弹窗组件的方法
const getData = async () => {
    if (!formRef.value) {
        throw new Error('表单引用未初始化')
    }

    // 验证表单
    const isValid = await formRef.value.validate()
    if (!isValid) {
        throw new Error('表单验证失败')
    }

    return {
        ...form.value,
    }
}

// 获取原始数据(不验证)
const getRawData = () => {
    return form.value
}

// 暴露方法给弹窗组件调用
defineExpose({
    getData,
    validate,
    getRawData,
    formData: form.value
})
  1. 表单验证
  • 内容组件:

<script setup lang="ts">
import { reactive, ref } from "vue";

import type { FormInstance, FormRules } from "element-plus";

interface RuleForm {
  name: string;
  region: string;
  count: string;
  date1: string;
  date2: string;
  delivery: boolean;
  location: string;
  type: string[];
  resource: string;
  desc: string;
}

const ruleFormRef = ref<FormInstance>();
const ruleForm = reactive<RuleForm>({
  name: "Hello",
  region: "",
  count: "",
  date1: "",
  date2: "",
  delivery: false,
  location: "",
  type: [],
  resource: "",
  desc: ""
});

const locationOptions = ["Home", "Company", "School"];

const rules = reactive<FormRules<RuleForm>>({
  name: [
    { required: true, message: "Please input Activity name", trigger: "blur" },
    { min: 3, max: 5, message: "Length should be 3 to 5", trigger: "blur" }
  ],
  region: [
    {
      required: true,
      message: "Please select Activity zone",
      trigger: "change"
    }
  ],
  count: [
    {
      required: true,
      message: "Please select Activity count",
      trigger: "change"
    }
  ],
  date1: [
    {
      type: "date",
      required: true,
      message: "Please pick a date",
      trigger: "change"
    }
  ],
  date2: [
    {
      type: "date",
      required: true,
      message: "Please pick a time",
      trigger: "change"
    }
  ],
  location: [
    {
      required: true,
      message: "Please select a location",
      trigger: "change"
    }
  ],
  type: [
    {
      type: "array",
      required: true,
      message: "Please select at least one activity type",
      trigger: "change"
    }
  ],
  resource: [
    {
      required: true,
      message: "Please select activity resource",
      trigger: "change"
    }
  ],
  desc: [{ required: true, message: "Please input activity form", trigger: "blur" }]
});
const options = Array.from({ length: 10000 }).map((_, idx) => ({
  value: `${idx + 1}`,
  label: `${idx + 1}`
}));

// 暴露验证方法
const validate = async () => {
  await ruleFormRef.value?.validate();
};

defineExpose({
  validate
});
</script>

<template>
  <el-button type="primary" @click="handleSave">保存</el-button>
  <el-button @click="handleCustomEvent">发送自定义事件</el-button>
  <el-button @click="handleEmitExample">发送别名事件</el-button>
  <el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" :rules="rules" label-width="auto">
    <el-form-item label="Activity name" prop="name">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item label="Activity zone" prop="region">
      <el-select v-model="ruleForm.region" placeholder="Activity zone">
        <el-option label="Zone one" value="shanghai" />
        <el-option label="Zone two" value="beijing" />
      </el-select>
    </el-form-item>
    <el-form-item label="Activity count" prop="count">
      <el-select-v2 v-model="ruleForm.count" placeholder="Activity count" :options="options" />
    </el-form-item>
    <el-form-item label="Activity time" required>
      <el-col :span="11">
        <el-form-item prop="date1">
          <el-date-picker
            v-model="ruleForm.date1"
            type="date"
            aria-label="Pick a date"
            placeholder="Pick a date"
            style="width: 100%"
          />
        </el-form-item>
      </el-col>
      <el-col class="text-center" :span="2">
        <span class="text-gray-500">-</span>
      </el-col>
      <el-col :span="11">
        <el-form-item prop="date2">
          <el-time-picker v-model="ruleForm.date2" aria-label="Pick a time" placeholder="Pick a time" style="width: 100%" />
        </el-form-item>
      </el-col>
    </el-form-item>
    <el-form-item label="Instant delivery" prop="delivery">
      <el-switch v-model="ruleForm.delivery" />
    </el-form-item>
    <el-form-item label="Activity location" prop="location">
      <el-segmented v-model="ruleForm.location" :options="locationOptions" />
    </el-form-item>
    <el-form-item label="Activity type" prop="type">
      <el-checkbox-group v-model="ruleForm.type">
        <el-checkbox value="Online activities" name="type"> Online activities </el-checkbox>
        <el-checkbox value="Promotion activities" name="type"> Promotion activities </el-checkbox>
        <el-checkbox value="Offline activities" name="type"> Offline activities </el-checkbox>
        <el-checkbox value="Simple brand exposure" name="type"> Simple brand exposure </el-checkbox>
      </el-checkbox-group>
    </el-form-item>
    <el-form-item label="Resources" prop="resource">
      <el-radio-group v-model="ruleForm.resource">
        <el-radio value="Sponsorship">Sponsorship</el-radio>
        <el-radio value="Venue">Venue</el-radio>
      </el-radio-group>
    </el-form-item>
    <el-form-item label="Activity form" prop="desc">
      <el-input v-model="ruleForm.desc" type="textarea" />
    </el-form-item>
  </el-form>
</template>

<style scoped>
.read-the-docs {
  color: #888888;
}
</style>
  • 弹窗组件:
<script setup lang="ts">
import { ref } from "vue";
import Example from "./components/example.vue";


const lastResult = ref<any>(null);
const headerButton = {
  template: `<el-button type="primary" size="small">操作按钮</el-button>`
};

const showBasicModal = async () => {
  const result = await createModal({
    component: Example,
    componentProps: {
      /* 传递给组件的props */
    },
    modalProps: {
      title: "弹窗标题",
      // titleIcon: "gdsTitle", // 使用内部图标
      width: "900px",
      footerButtons: [
        { text: "取消", event: "cancel" },
        { text: "确定", event: "confirm", type: "primary" }
      ]
    },
    headerSlot: headerButton,
    onComponentEvent: (event, data) => {
      // 处理特定事件
      switch (event) {
        case "confirm":
          // 自动调用验证 ”confirm“
      }
    }
  });
  lastResult.value = result;
};
</script>

<template>
  <div>
    <div>
      <el-card>
        <el-button type="primary" @click="showBasicModal">高度自定义弹窗</el-button>
      </el-card>
    </div>
  </div>
</template>

<style scoped lang="scss">
:deep(.el-card__body) {
  .md {
    height: 650px;
    overflow-y: auto;
  }
}
</style>

API 参考

createModal 函数

function createModal(options: ModalOptions): Promise<any>

参数:

  • options.component: Vue组件

  • options.componentProps: 传递给组件的props

  • options.modalProps: 弹窗配置

  • options.onComponentEvent: 事件回调函数

返回值: Promise,解析为弹窗关闭时的结果

ModalHelper 工具类

// 确认对话框
ModalHelper.confirm(message: string, title?: string): Promise<any>

// 成功提示
ModalHelper.success(message: string, title?: string): Promise<any>

// 警告提示
ModalHelper.warning(message: string, title?: string): Promise<any>

// 错误提示  
ModalHelper.error(message: string, title?: string): Promise<any>

// 信息提示
ModalHelper.info(message: string, title?: string): Promise<any>

// 关闭弹窗
ModalHelper.closeModal(result?: any);

// 获取当前弹窗实例
ModalHelper.getCurrentModal();

// 更新当前模态框属性
ModalHelper.updateModalProps(modalProps: ModalProps);

样式定制

  1. 使用内置样式类
// 弹窗尺寸
.el-dialog-small     // 392px
.el-dialog-default   // 850px  
.el-dialog-large     // 1168px
.el-dialog-largest   // 1500px
  1. 自定义样式
<template>
  <ElDialog class="custom-dialog my-custom-style">
    <!-- 内容 -->
  </ElDialog>
</template>

<style scoped>
  .my-custom-style {
    :deep(.el-dialog__header) {
      background: linear-gradient(90deg, #409EFF, #64b5ff);

      .el-dialog__title {
        color: white;

        &::before {
          filter: brightness(0) invert(1);
        }
      }
    }
  }
</style>

最佳实践

  1. 组件设计原则
// 好的实践:组件只关注UI,事件通过props传递
const GoodComponent = {
  props: ['onEvent', 'onConfirm'],
  setup(props) {
    const handleAction = () => {
      props.onEvent?.('action-taken', { data: 'example' })
    }
    
    return { handleAction }
  }
}

// 不好的实践:组件直接操作外部状态
const BadComponent = {
  setup() {
    const store = useStore() // 避免直接使用store
    // ...
  }
}
  1. 错误处理
const showModalSafely = async () => {
  try {
    const result = await createModal({
      component: MyComponent,
      modalProps: { title: '弹窗' }
    })
    // 处理结果
  } catch (error) {
    console.error('弹窗打开失败:', error)
    ElMessage.error('弹窗打开失败')
  }
}
  1. 性能优化
// 懒加载组件
const LazyComponent = defineAsyncComponent(() =>
  import('./LazyComponent.vue')
)

const showLazyModal = async () => {
  await createModal({
    component: LazyComponent,
    modalProps: { title: '懒加载弹窗' }
  })
}

常见问题

  1. 类型错误处理
// 确保组件有正确的props定义
defineProps<{
  onEvent?: (event: string, data?: any) => void
  onConfirm?: (data: any) => void
  onCancel?: () => void
}>()