You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
6.9 KiB

  1. <template>
  2. <div class="dropdown" ref="dropdownRef">
  3. <!-- 下拉框触发器 -->
  4. <div class="dropdown-toggle" @click="toggleMenu" :class="{ 'active': isOpen }">
  5. <span class="placeholder" :style="{ color: selectedItem ? '#333' : '#A8ABB2' }">
  6. {{ selectedItem || placeholder }}
  7. </span>
  8. <span class="arrow">
  9. <el-icon>
  10. <ArrowDown />
  11. </el-icon>
  12. </span>
  13. </div>
  14. <!-- 下拉菜单 -->
  15. <div class="dropdown-menu" v-if="isOpen">
  16. <!-- 搜索框 -->
  17. <div class="search">
  18. <input type="text" v-model="searchData" class="search-input" placeholder="查询" @focus="handleSearchFocus"
  19. @blur="handleSearchBlur">
  20. <el-icon class="search-icon">
  21. <Search />
  22. </el-icon>
  23. <el-icon class="clear-icon" v-if="searchData" @click="clearSearch">
  24. <CircleClose />
  25. </el-icon>
  26. </div>
  27. <!-- 选项区域按钮样式 -->
  28. <div class="menuContent">
  29. <button class="dropdown-item" v-for="(item, index) in filteredItems" :key="index" @click="handleSelect(item)"
  30. :class="{ 'selected': selectedItem === item }">
  31. {{ item }}
  32. </button>
  33. </div>
  34. </div>
  35. </div>
  36. </template>
  37. <script setup>
  38. import { ref, computed, watchEffect, onMounted } from 'vue';
  39. const searchData = ref('')
  40. const isOpen = ref(false)
  41. const selectedItem = ref('')
  42. const dropdownRef = ref(null)
  43. const props = defineProps({
  44. items: {
  45. type: Array,
  46. required: true,
  47. default: () => []
  48. },
  49. placeholder: {
  50. type: String,
  51. default: '请选择支付方式'
  52. },
  53. modelValue: {
  54. type: String,
  55. default: ''
  56. }
  57. })
  58. const emit = defineEmits(['update:modelValue', 'change'])
  59. // 切换下拉菜单
  60. const toggleMenu = () => {
  61. isOpen.value = !isOpen.value
  62. }
  63. // 清除搜索
  64. const clearSearch = () => {
  65. searchData.value = ''
  66. }
  67. // 选择选项
  68. const handleSelect = (item) => {
  69. selectedItem.value = item
  70. isOpen.value = false
  71. emit('update:modelValue', item)
  72. emit('change', item)
  73. }
  74. // 点击外部关闭菜单
  75. const handleClickOutside = (event) => {
  76. if (dropdownRef.value && !dropdownRef.value.contains(event.target)) {
  77. isOpen.value = false
  78. }
  79. }
  80. // 监听搜索框的焦点和失焦事件
  81. const handleSearchFocus = () => {
  82. // 可以在这里添加额外的逻辑
  83. }
  84. const handleSearchBlur = () => {
  85. // 可以在这里添加额外的逻辑
  86. }
  87. // 搜索过滤
  88. const filteredItems = computed(() => {
  89. if (!searchData.value) return props.items
  90. return props.items.filter(item =>
  91. item.toLowerCase().includes(searchData.value.toLowerCase())
  92. )
  93. })
  94. // 挂载/卸载事件
  95. onMounted(() => {
  96. document.addEventListener('click', handleClickOutside)
  97. return () => {
  98. document.removeEventListener('click', handleClickOutside)
  99. }
  100. })
  101. // 监听外部值变化
  102. watchEffect(() => {
  103. selectedItem.value = props.modelValue
  104. })
  105. </script>
  106. <style scoped lang="scss">
  107. // 下拉容器
  108. .dropdown {
  109. position: relative;
  110. width: 268px;
  111. font-family: 'Arial', sans-serif;
  112. }
  113. // 触发器:控制展开/收起
  114. .dropdown-toggle {
  115. border: 1px solid #e5e7eb;
  116. padding: 4px 12px;
  117. /* 调整内边距以匹配按钮高度 */
  118. height: 23px;
  119. /* 调整高度以匹配按钮 */
  120. cursor: pointer;
  121. display: flex;
  122. justify-content: space-between;
  123. align-items: center;
  124. background-color: #fff;
  125. border-radius: 6px;
  126. transition: all 0.3s ease;
  127. .placeholder {
  128. flex: 1;
  129. font-size: 14px;
  130. line-height: 18px;
  131. color: #A8ABB2;
  132. }
  133. }
  134. // 展开状态:边框+阴影高亮
  135. .dropdown-toggle.active {
  136. border-color: #678BFF;
  137. box-shadow: 0 0 0 2px rgba(103, 139, 255, 0.1);
  138. }
  139. // 箭头图标:展开时旋转
  140. .arrow {
  141. margin-left: 8px;
  142. color: #999;
  143. transition: transform 0.3s ease;
  144. }
  145. .dropdown-toggle.active .arrow {
  146. transform: rotate(180deg);
  147. }
  148. // 下拉菜单主体
  149. .dropdown-menu {
  150. position: absolute;
  151. top: 100%; // 紧贴触发器下方
  152. left: 0;
  153. width: 100%;
  154. border: 1px solid #678BFF;
  155. max-height: 300px;
  156. background-color: #fff;
  157. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  158. border-radius: 0 0 8px 8px; // 仅底部两侧圆角
  159. z-index: 1000;
  160. margin-top: 15px; // 关键:不要有上外边距,尖角要紧贴触发器
  161. overflow: visible;
  162. &::before {
  163. content: "";
  164. position: absolute;
  165. top: -8px;
  166. left: 50%;
  167. transform: translateX(-50%) scaleY(0.5);
  168. width: 30px;
  169. height: 16px;
  170. background: #fff;
  171. clip-path: polygon(0 100%, 100% 100%, 50% 0);
  172. z-index: 1001;
  173. border: none; /* 移除原来的边框 */
  174. }
  175. &::after {
  176. content: "";
  177. position: absolute;
  178. top: -9px; /* 比 ::before 往下一点,制造边框效果 */
  179. left: 50%;
  180. transform: translateX(-50%) scaleY(0.5);
  181. width: 30px;
  182. height: 16px;
  183. background: #678BFF;
  184. clip-path: polygon(0 100%, 100% 100%, 50% 0);
  185. z-index: 1000;
  186. }
  187. }
  188. // 搜索框区域
  189. .search {
  190. position: sticky;
  191. top: 0;
  192. background-color: #FFFFFF;
  193. z-index: 1002;
  194. padding: 10px 14px 0px 10px;
  195. }
  196. // 搜索输入框:浅灰背景 + 图标定位
  197. .search-input {
  198. width: 100%;
  199. height: 25px;
  200. padding: 0 12px 0 5px;
  201. /* 左侧留出图标空间 */
  202. border: 1px solid #dcdfe6;
  203. border-radius: 8px;
  204. box-sizing: border-box;
  205. background-color: #f8f9fa;
  206. /* 浅灰背景匹配参考图 */
  207. outline: none;
  208. font-size: 12px;
  209. transition: border-color 0.3s ease;
  210. &::placeholder {
  211. color: #909399;
  212. }
  213. &:hover {
  214. border-color: #c0c4cc;
  215. }
  216. &:focus {
  217. border-color: #678BFF;
  218. }
  219. }
  220. // 搜索图标:左侧定位
  221. .search-icon {
  222. position: absolute;
  223. top: 62%;
  224. left: 50px;
  225. transform: translateY(-50%);
  226. color: #909399;
  227. z-index: 1003;
  228. }
  229. // 清除图标:右侧定位 + hover效果
  230. .clear-icon {
  231. position: absolute;
  232. top: 50%;
  233. right: 20px;
  234. transform: translateY(-50%);
  235. color: #909399;
  236. cursor: pointer;
  237. z-index: 1003;
  238. &:hover {
  239. color: #606266;
  240. }
  241. }
  242. // 选项容器:调整滚动区域高度
  243. .menuContent {
  244. max-height: 200px;
  245. /* 减去搜索框高度和尖角高度 */
  246. overflow-y: auto;
  247. padding: 8px;
  248. padding: 10px 14px 12px 10px;
  249. }
  250. // 选项按钮:无边框 + hover/选中效果
  251. .dropdown-item {
  252. width: 100%;
  253. height: 25px;
  254. padding: 5px 12px 5px 5px;
  255. text-align: center;
  256. cursor: pointer;
  257. border: none;
  258. border-radius: 8px;
  259. background-color: #fff;
  260. font-size: 12px;
  261. margin: 5px 0;
  262. color: #040A2D;
  263. transition: all 0.2s ease;
  264. &:hover {
  265. background-color: #F3FAFE;
  266. /* hover浅灰 */
  267. }
  268. &.selected {
  269. background-color: #E5EBFE;
  270. /* 选中浅蓝 */
  271. color: #2741DE;
  272. /* 选中文字蓝色 */
  273. }
  274. }
  275. // 可选:滚动条美化(与原风格一致)
  276. .menuContent::-webkit-scrollbar {
  277. width: 6px;
  278. }
  279. .menuContent::-webkit-scrollbar-track {
  280. background: #f1f1f1;
  281. border-radius: 3px;
  282. }
  283. .menuContent::-webkit-scrollbar-thumb {
  284. background: #c0c4cc;
  285. border-radius: 3px;
  286. &:hover {
  287. background: #909399;
  288. }
  289. }
  290. </style>