|
|
@@ -0,0 +1,244 @@
|
|
|
+<template>
|
|
|
+ <div class="custom-dropdown" ref="dropdown" :class="[`theme-${theme}`]">
|
|
|
+ <div
|
|
|
+ class="dropdown-trigger"
|
|
|
+ :class="{ 'is-open': isOpen }"
|
|
|
+ @click="toggleDropdown"
|
|
|
+ >
|
|
|
+ <span class="trigger-text">{{ currentLabel }}</span>
|
|
|
+ <i class="arrow-icon"></i>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <transition name="fade">
|
|
|
+ <div class="dropdown-menu" v-show="isOpen">
|
|
|
+ <div class="menu-arrow"></div>
|
|
|
+
|
|
|
+ <div class="menu-list">
|
|
|
+ <div
|
|
|
+ class="menu-item"
|
|
|
+ v-for="item in options"
|
|
|
+ :key="item.value"
|
|
|
+ :class="{ 'is-active': value === item.value }"
|
|
|
+ @click="selectOption(item)"
|
|
|
+ >
|
|
|
+ {{ item.label }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'DropdownSelect',
|
|
|
+ model: {
|
|
|
+ prop: 'value',
|
|
|
+ event: 'change'
|
|
|
+ },
|
|
|
+ props: {
|
|
|
+ value: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ },
|
|
|
+ placeholder: {
|
|
|
+ type: String,
|
|
|
+ default: '请选择'
|
|
|
+ },
|
|
|
+ // 【新增】主题风格:'bordered' (透明边框) | 'solid' (实心深色背景)
|
|
|
+ theme: {
|
|
|
+ type: String,
|
|
|
+ default: 'bordered'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ isOpen: false
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ currentLabel() {
|
|
|
+ const selected = this.options.find(opt => opt.value === this.value);
|
|
|
+ return selected ? selected.label : this.placeholder;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ document.addEventListener('click', this.handleClickOutside);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ document.removeEventListener('click', this.handleClickOutside);
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ toggleDropdown() {
|
|
|
+ this.isOpen = !this.isOpen;
|
|
|
+ },
|
|
|
+ selectOption(item) {
|
|
|
+ if (this.value !== item.value) {
|
|
|
+ this.$emit('change', item.value);
|
|
|
+ this.$emit('select', item);
|
|
|
+ }
|
|
|
+ this.isOpen = false;
|
|
|
+ },
|
|
|
+ handleClickOutside(event) {
|
|
|
+ if (this.$refs.dropdown && !this.$refs.dropdown.contains(event.target)) {
|
|
|
+ this.isOpen = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* 最外层容器 */
|
|
|
+.custom-dropdown {
|
|
|
+ position: relative;
|
|
|
+ display: inline-block;
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+
|
|
|
+/* --- 触发器按钮 (基础样式) --- */
|
|
|
+.dropdown-trigger {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 6px 12px;
|
|
|
+ min-width: 80px;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 14px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+.trigger-text {
|
|
|
+ margin-right: 12px;
|
|
|
+}
|
|
|
+.arrow-icon {
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 4px solid transparent;
|
|
|
+ border-right: 4px solid transparent;
|
|
|
+ border-top: 5px solid #ffffff;
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+}
|
|
|
+.dropdown-trigger.is-open .arrow-icon {
|
|
|
+ transform: rotate(180deg);
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 主题 1:透明边框 (默认原样式) ================== */
|
|
|
+.theme-bordered .dropdown-trigger {
|
|
|
+ background-color: transparent;
|
|
|
+ border: 1px solid rgba(100, 130, 190, 0.6);
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+.theme-bordered .dropdown-trigger:hover,
|
|
|
+.theme-bordered .dropdown-trigger.is-open {
|
|
|
+ border-color: rgba(140, 180, 255, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 主题 2:实心深色 (还原你的最新截图) ================== */
|
|
|
+.theme-solid .dropdown-trigger {
|
|
|
+ background-color: #273444;; /* 截图中的深蓝色底色 */
|
|
|
+ border: 1px solid transparent; /* 占位防止跳动 */
|
|
|
+ border-radius: 4px; /* 稍微圆润的边角 */
|
|
|
+ padding: 5px 8px;
|
|
|
+}
|
|
|
+.theme-solid .dropdown-trigger:hover,
|
|
|
+.theme-solid .dropdown-trigger.is-open {
|
|
|
+ background-color: #385180; /* hover 时稍微提亮 */
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* --- 下拉菜单容器 (基础位置与动画) --- */
|
|
|
+.dropdown-menu {
|
|
|
+ position: absolute;
|
|
|
+ top: calc(100% + 10px);
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ min-width: 100%; /* 至少与触发器同宽 */
|
|
|
+ border-radius: 6px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
|
+ z-index: 1000;
|
|
|
+}
|
|
|
+.menu-arrow {
|
|
|
+ position: absolute;
|
|
|
+ top: -5px;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 6px solid transparent;
|
|
|
+ border-right: 6px solid transparent;
|
|
|
+}
|
|
|
+.menu-list {
|
|
|
+ padding: 6px 0;
|
|
|
+}
|
|
|
+.menu-item {
|
|
|
+ padding: 8px 16px;
|
|
|
+ font-size: 14px;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 下拉菜单配色:主题 1 (白色气泡) ================== */
|
|
|
+.theme-bordered .dropdown-menu {
|
|
|
+ background-color: #ffffff;
|
|
|
+}
|
|
|
+.theme-bordered .menu-arrow {
|
|
|
+ border-bottom: 6px solid #ffffff;
|
|
|
+}
|
|
|
+.theme-bordered .menu-item {
|
|
|
+ color: #333333;
|
|
|
+}
|
|
|
+.theme-bordered .menu-item:hover {
|
|
|
+ background-color: #f0f5ff;
|
|
|
+ color: #4da8ff;
|
|
|
+}
|
|
|
+.theme-bordered .menu-item.is-active {
|
|
|
+ color: #4da8ff;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* ================== 下拉菜单配色:主题 2 (深色科技气泡) ================== */
|
|
|
+.theme-solid .dropdown-menu {
|
|
|
+ background-color: #1e2c4a;
|
|
|
+ border: 1px solid rgba(100, 130, 190, 0.3);
|
|
|
+}
|
|
|
+.theme-solid .menu-arrow {
|
|
|
+ border-bottom: 6px solid #1e2c4a;
|
|
|
+}
|
|
|
+/* 利用伪元素单独画暗色三角形边框,防止悬浮感不足 */
|
|
|
+.theme-solid .menu-arrow::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 1px;
|
|
|
+ left: -6px;
|
|
|
+ border-left: 6px solid transparent;
|
|
|
+ border-right: 6px solid transparent;
|
|
|
+ border-bottom: 6px solid rgba(100, 130, 190, 0.3);
|
|
|
+ z-index: -1;
|
|
|
+}
|
|
|
+.theme-solid .menu-item {
|
|
|
+ color: #c4d7f0;
|
|
|
+}
|
|
|
+.theme-solid .menu-item:hover {
|
|
|
+ background-color: #2b3f66;
|
|
|
+ color: #32F6F8;
|
|
|
+}
|
|
|
+.theme-solid .menu-item.is-active {
|
|
|
+ color: #32F6F8;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+/* Vue 过渡动画 */
|
|
|
+.fade-enter-active, .fade-leave-active {
|
|
|
+ transition: opacity 0.2s, transform 0.2s;
|
|
|
+}
|
|
|
+.fade-enter, .fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translate(-50%, -5px);
|
|
|
+}
|
|
|
+</style>
|