更新记录

0.0.2(2025-11-06) 下载此版本

移除对于uview的依赖

0.0.1(2025-11-06) 下载此版本

基于uview的三级联动加末级节点多选


平台兼容性

uni-app(4.84)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - - - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 快应用-华为 快应用-联盟
- - - - - - - - - -

MedicalProjectModal 医疗项目选择器

一个基于 uni-app 的三级联动选择器组件,支持多选功能,适用于医疗美容项目的选择场景。

✨ 特点

  • 🎯 纯原生实现:不依赖任何第三方 UI 组件库
  • 🎨 优雅动画:流畅的弹出和收起动画效果
  • 📱 完美适配:支持各种屏幕尺寸,响应式设计
  • 性能优化:使用 Vue 3 Composition API,性能更优

版本支持

  • Vue 3: 完全支持(使用 Composition API + <script setup> 语法)
  • Vue 2: 不支持(组件使用了 Vue 3 特有的 <script setup> 语法)

依赖

  • uni-app 框架(Vue 3 版本)
  • 无需任何第三方 UI 组件库 ✅

引入方式

1. 页面中直接引入(推荐)

<script setup>
import MedicalProjectModal from '@/components/MedicalProjectModal/MedicalProjectModal.vue';
import { ref } from 'vue';

const showModal = ref(false);
const projectData = ref([
  {
    id: 1,
    name: '皮肤美容',
    children: [
      {
        id: 11,
        name: '祛斑美白',
        children: [
          { id: 111, name: '激光祛斑' },
          { id: 112, name: '光子嫩肤' }
        ]
      }
    ]
  }
]);

const handleConfirm = (selectedItems) => {
  console.log('选中的项目:', selectedItems);
};
</script>

<template>
  <view>
    <button @click="showModal = true">选择医疗项目</button>

    <MedicalProjectModal 
      v-model:show="showModal"
      :data="projectData"
      @confirm="handleConfirm"
    />
  </view>
</template>

2. 全局注册(可选)

// main.js
import { createSSRApp } from 'vue';
import MedicalProjectModal from '@/components/MedicalProjectModal/MedicalProjectModal.vue';

export function createApp() {
  const app = createSSRApp(App);

  // 全局注册
  app.component('MedicalProjectModal', MedicalProjectModal);

  return { app };
}

属性 Props

属性名 类型 默认值 必填 说明
show Boolean false 控制弹窗显示/隐藏,支持 v-model
data Array [] 三级联动数据源

data 数据结构

interface ProjectItem {
  id: number | string;      // 唯一标识
  name: string;            // 显示名称
  children?: ProjectItem[]; // 子级数据
}

type ProjectData = ProjectItem[];

data 示例

const projectData = [
  {
    id: 1,
    name: '皮肤美容',
    children: [
      {
        id: 11,
        name: '祛斑美白',
        children: [
          { id: 111, name: '激光祛斑' },
          { id: 112, name: '光子嫩肤' },
          { id: 113, name: '水光针' }
        ]
      },
      {
        id: 12,
        name: '抗衰老',
        children: [
          { id: 121, name: '热玛吉' },
          { id: 122, name: '超声刀' }
        ]
      }
    ]
  },
  {
    id: 2,
    name: '注射美容',
    children: [
      {
        id: 21,
        name: '玻尿酸',
        children: [
          { id: 211, name: '苹果肌填充' },
          { id: 212, name: '隆鼻' }
        ]
      }
    ]
  }
];

事件 Events

事件名 参数 说明
update:show (value: Boolean) 弹窗显示状态改变时触发,支持 v-model
confirm (selectedItems: Array) 点击确定按钮时触发,返回选中的项目数组

confirm 事件返回值

// selectedItems 结构
[
  { id: 111, name: '激光祛斑' },
  { id: 122, name: '超声刀' }
]

完整使用示例

<script setup>
import { ref } from 'vue';
import MedicalProjectModal from '@/components/MedicalProjectModal/MedicalProjectModal.vue';

// 控制弹窗显示
const showModal = ref(false);

// 已选择的项目
const selectedProjects = ref([]);

// 三级联动数据
const projectData = ref([
  {
    id: 1,
    name: '皮肤美容',
    children: [
      {
        id: 11,
        name: '祛斑美白',
        children: [
          { id: 111, name: '激光祛斑' },
          { id: 112, name: '光子嫩肤' },
          { id: 113, name: '水光针' }
        ]
      },
      {
        id: 12,
        name: '抗衰老',
        children: [
          { id: 121, name: '热玛吉' },
          { id: 122, name: '超声刀' },
          { id: 123, name: 'Botox' }
        ]
      }
    ]
  },
  {
    id: 2,
    name: '注射美容',
    children: [
      {
        id: 21,
        name: '玻尿酸',
        children: [
          { id: 211, name: '苹果肌填充' },
          { id: 212, name: '隆鼻' },
          { id: 213, name: '丰唇' }
        ]
      },
      {
        id: 22,
        name: '肉毒素',
        children: [
          { id: 221, name: '瘦脸针' },
          { id: 222, name: '除皱针' }
        ]
      }
    ]
  },
  {
    id: 3,
    name: '身体塑形',
    children: [
      {
        id: 31,
        name: '减脂塑形',
        children: [
          { id: 311, name: '吸脂手术' },
          { id: 312, name: '冷冻溶脂' }
        ]
      }
    ]
  }
]);

// 打开弹窗
const openModal = () => {
  showModal.value = true;
};

// 确认选择
const handleConfirm = (items) => {
  selectedProjects.value = items;
  console.log('选中的项目:', items);

  // 提取项目名称
  const names = items.map(item => item.name).join('、');

  uni.showToast({
    title: `已选择:${names}`,
    icon: 'none',
    duration: 2000
  });
};
</script>

<template>
  <view class="page">
    <!-- 触发按钮 -->
    <button @click="openModal" class="select-btn">
      选择医疗项目
    </button>

    <!-- 显示已选择的项目 -->
    <view v-if="selectedProjects.length > 0" class="selected-list">
      <text class="label">已选项目:</text>
      <view 
        v-for="item in selectedProjects" 
        :key="item.id"
        class="project-tag"
      >
        {{ item.name }}
      </view>
    </view>

    <!-- 医疗项目选择器 -->
    <MedicalProjectModal 
      v-model:show="showModal"
      :data="projectData"
      @confirm="handleConfirm"
    />
  </view>
</template>

<style lang="scss" scoped>
.page {
  padding: 32rpx;
}

.select-btn {
  width: 100%;
  height: 88rpx;
  background: #FF7575;
  color: #fff;
  border-radius: 44rpx;
  font-size: 32rpx;
}

.selected-list {
  margin-top: 32rpx;

  .label {
    font-size: 28rpx;
    color: #666;
    margin-bottom: 16rpx;
  }

  .project-tag {
    display: inline-block;
    padding: 8rpx 24rpx;
    margin: 8rpx 16rpx 8rpx 0;
    background: #FFF5F5;
    color: #FF7575;
    border-radius: 32rpx;
    font-size: 26rpx;
  }
}
</style>

功能特性

1. 三级联动

  • 第一级:选择大分类(单选)
  • 第二级:选择子分类(单选)
  • 第三级:选择具体项目(多选)

2. 多选支持

  • 第三级支持多选,可以跨分类选择
  • 选中后会显示勾选图标(✓)
  • 底部展示已选择的项目标签

3. 已选项目管理

  • 底部显示所有已选择的项目
  • 点击标签可以快速移除(×图标)
  • 确认前必须至少选择一个项目

4. 交互优化

  • 自动选中第一级第一项
  • 切换第一级时不清空已选项目,支持跨分类选择
  • 支持点击遮罩层关闭弹窗
  • 支持取消和确定按钮
  • 流畅的弹出/收起动画效果

5. 视觉设计

  • 粉红色主题(#FF7575),可自定义
  • 选中状态带左侧指示条
  • 按压反馈效果
  • 圆角卡片设计

注意事项

  1. 数据格式:确保传入的 data 符合三级结构,每级都有 idnamechildren 字段
  2. 唯一 ID:每个项目的 id 必须唯一,用于多选判断
  3. 至少选择一项:确认时如果未选择任何项目,会提示用户
  4. Vue 3 专用:此组件使用 Vue 3 Composition API,不兼容 Vue 2
  5. 纯原生组件:不依赖任何第三方 UI 库,开箱即用

样式定制

组件内部使用了 scoped 样式,如需修改样式,可以:

方法 1:使用深度选择器(推荐)

<style lang="scss">
// 修改标题颜色
:deep(.medical-project-modal .modal-header .title) {
  color: #333;
}

// 修改主题色(确定按钮、选中状态等)
:deep(.medical-project-modal .confirm-btn) {
  color: #007aff;
}

:deep(.picker-item.active) {
  color: #007aff;
  background: #E6F2FF;
}

// 修改遮罩层透明度
:deep(.modal-mask) {
  background: rgba(0, 0, 0, 0.7);
}
</style>

方法 2:直接修改组件源码

修改 MedicalProjectModal.vue 文件中的 <style> 部分:

主要样式变量位置:

  • 主题色:#FF7575(搜索替换为你的品牌色)
  • 圆角:border-radius: 20rpx
  • 弹窗高度:height: 600rpx(.picker-container)
  • 动画时长:transition: 0.3s

主题色自定义示例

如果要将主题色从粉红色改为蓝色:

// 在组件源码中搜索并替换
#FF7575 → #007aff  // 主题色
#FFF5F5 → #E6F2FF  // 浅色背景

实现细节

组件结构

MedicalProjectModal
├── modal-mask(遮罩层)
│   └── modal-wrapper(弹窗包装器)
│       └── medical-project-modal
│           ├── modal-header(顶部操作栏)
│           ├── picker-container(选择器容器)
│           │   └── picker-columns(三列布局)
│           │       ├── picker-column(第一级)
│           │       ├── picker-column(第二级)
│           │       └── picker-column(第三级,多选)
│           └── selected-area(已选择区域)

核心逻辑

  1. 弹窗动画:使用 CSS transformopacity 实现流畅的弹出效果
  2. 多选实现:使用数组存储选中项,通过 findIndex 判断是否已选
  3. 三级联动:使用 computed 计算属性响应式更新子级数据
  4. 图标实现:使用原生文本符号(✓ 和 ×),无需图标库

图标说明

组件使用纯文本符号作为图标:

  • 勾选图标: (Unicode: U+2713)
  • 关闭图标:× (Unicode: U+00D7)

如需更换图标样式,可以:

  • 使用其他 Unicode 符号
  • 使用 emoji 表情
  • 使用自定义字体图标

常见问题

Q1: 如何获取选中项目的完整路径?

A: 当前组件只返回第三级选中的项目。如需完整路径,可以在 handleConfirm 中自行处理:

const handleConfirm = (items) => {
  // 方案 1:修改组件,在选中时记录完整路径
  // 方案 2:根据 id 从原始数据中递归查找完整路径
  const findPath = (data, targetId, path = []) => {
    for (const item of data) {
      const currentPath = [...path, item];
      if (item.id === targetId) return currentPath;
      if (item.children) {
        const result = findPath(item.children, targetId, currentPath);
        if (result) return result;
      }
    }
    return null;
  };
};

Q2: 如何设置默认选中?

A: 可以在组件中添加 defaultSelected prop 和初始化逻辑:

// 在组件 props 中添加
const props = defineProps({
  // ...其他 props
  defaultSelected: {
    type: Array,
    default: () => []
  }
});

// 在 watch 中初始化
watch(() => props.show, (newVal) => {
  if (newVal) {
    if (props.defaultSelected.length > 0) {
      selectedThirdItems.value = [...props.defaultSelected];
    }
  }
});

Q3: 如何限制最多选择数量?

A: 可以在 toggleThird 方法中添加判断:

// 添加 prop
const props = defineProps({
  // ...
  maxCount: {
    type: Number,
    default: 10 // 最多选择 10 个
  }
});

// 修改 toggleThird 方法
const toggleThird = (item) => {
  const index = selectedThirdItems.value.findIndex(i => i.id === item.id);
  if (index > -1) {
    selectedThirdItems.value.splice(index, 1);
  } else {
    if (selectedThirdItems.value.length >= props.maxCount) {
      uni.showToast({ 
        title: `最多选择${props.maxCount}个项目`, 
        icon: 'none' 
      });
      return;
    }
    selectedThirdItems.value.push(item);
  }
};

Q4: 为什么不用图标库?

A: 为了减少依赖和提升性能,组件使用原生 Unicode 符号作为图标。这样做的好处:

  • ✅ 零依赖,开箱即用
  • ✅ 体积更小,加载更快
  • ✅ 兼容性更好
  • ✅ 易于自定义

如果你需要更精美的图标,可以轻松替换为:

  • uni-app 内置图标
  • 自定义 SVG 图标
  • 第三方图标库(如 uview、uni-icons 等)

Q5: 如何清空已选择的项目?

A: 组件内部提供了清空方法,你可以通过 ref 调用:

<script setup>
import { ref } from 'vue';

const modalRef = ref(null);

// 清空选择
const clearSelection = () => {
  if (modalRef.value) {
    modalRef.value.selectedThirdItems = [];
  }
};
</script>

<template>
  <MedicalProjectModal 
    ref="modalRef"
    v-model:show="showModal"
    :data="projectData"
  />
</template>

更新日志

v2.0.0 (2025-11-06)

  • 🎉 移除 uview 组件库依赖,改用纯原生实现
  • ✨ 优化弹窗动画效果
  • 🎨 使用 Unicode 符号替代图标库
  • 📝 完善文档说明

v1.0.0

  • 🎉 初始版本发布
  • ✨ 支持三级联动选择
  • ✨ 支持第三级多选
  • ✨ 已选项目管理功能

License

MIT


反馈与贡献

如有问题或建议,欢迎提出 Issue 或 Pull Request。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。