|
|
<template> <div class="productContent"> <div class="selectBox" @click="handelMenu" :class="{ 'active': isOpen }"> <span class="placeholder" :style="{ color: selectedItem ? '#333' : '#A8ABB2' }"> {{ selectedItem || placeholder }} </span> <span class="arrow"> <el-icon> <ArrowDown /> </el-icon> </span> </div>
<div class="menu" v-show="isOpen"> <div class="coinselect" @click="coinhandelMenu" :class="{ 'active': coinisOpen, 'disabled-menu': restrictType === 'software' }"> <div class="cointxt"> {{ t('cash.goldProduct') }} </div> <span class="coin-arrow"> <el-icon> <ArrowDown /> </el-icon> </span> </div> <div class="coinoption" v-show="coinisOpen"> <el-radio v-model="selectedValue" :label="t('cash.coinRecharge')" size="large" /> </div> <div class="product"> <div class="coinselect" @click="producthandelMenu" :class="{ 'active': productisOpen, 'disabled-menu': restrictType === 'gold' }"> <div class="cointxt"> {{ t('cash.softwareProduct') }} </div> <span class="coin-arrow"> <el-icon> <ArrowDown /> </el-icon> </span> </div> <div class="productOption" v-show="productisOpen"> <hr class="line"> <div class="checktxt">{{ t('cash.software') }}</div> <div class="marketprodut"> <div class="fistlevel" v-for="(menu, index) in menuData" :key="menu.name" @click="clickmenu(index)" :class="{ 'selected': menu.options.includes(selectedValue) }"> <div class="label"> {{ menu.name }} <el-icon :class="{ 'rotate': activeIndex === index }"> <ArrowDown /> </el-icon> </div> <div v-show="activeIndex == index" class="selectoption" @click.stop> <el-radio-group v-model="selectedValue"> <div class="option" v-for="product in menu.options" :key="product"> <el-radio :label="product"> {{ product }} </el-radio> </div> </el-radio-group> </div> </div> </div> <hr class="line"> <div class="ai"> <div class="checktxt">{{ t('cash.aiService.aiDetectionTool') }}</div> <hr class="line"> <el-radio-group v-model="selectedValue"> <el-radio v-for="ai in AIProduct" :key="ai" :label="ai" :value="ai"> {{ ai }} </el-radio> </el-radio-group> </div> <div class="ai"> <div class="checktxt">{{ t('cash.aiService.superDetectionTool') }}</div> <hr class="line"> <el-radio-group v-model="selectedValue"> <el-radio v-for="ai in superProduct" :key="ai" :label="ai" :value="ai"> {{ ai }} </el-radio> </el-radio-group> </div> <div class="ai"> <div class="checktxt">{{ t('cash.other') }}</div> <hr class="line"> <!-- Native implementation replacing el-radio-group --> <div class="native-radio-group"> <div v-for="ai in InfoFee" :key="ai" class="radio-wrapper native-wrapper"> <div class="native-radio-item" @click="handleClick(ai)"> <span class="native-radio-input" :class="{ 'is-checked': selectedValue === ai }"> <span class="native-radio-inner"></span> </span> <span class="native-radio-label" :class="{ 'is-checked': selectedValue === ai }">{{ ai }}</span> </div> </div> <div class="native-radio-item" @click=" showPanel = !showPanel"> <span class="native-radio-input" :class="{ 'is-checked': ifHcInfo }"> <span class="native-radio-inner"></span> </span> <span class="native-radio-label" :class="{ 'is-checked': ifHcInfo }">{{ t('cash.HC') }}</span> <div v-if="showPanel" class="native-cascader-panel" @click.stop> <div class="cascader-menu"> <div v-for="opt in cascaderOptions" :key="opt.value" class="cascader-node" :class="{ 'is-active': activeCascaderOption?.value === opt.value }" @mouseenter="activeCascaderOption = opt"> <span class="cascader-label">{{ opt.label }}</span> <el-icon class="cascader-icon"> <ArrowRight /> </el-icon> </div> </div> <div class="cascader-menu sub-menu" v-if="activeCascaderOption"> <div v-for="child in activeCascaderOption.children" :key="child.value" class="cascader-node" @click="handlePanelChange([activeCascaderOption.value, child.value])"> <span class="cascader-label">{{ child.label }}</span> </div> </div> </div> </div>
</div> </div> </div> </div> <hr> <div class="selected-value">{{ t('common.selectedValue') }}:{{ selectedValue ? `(${selectedValue})` : t('common.none') }}</div> <div class="confirm"> <el-button type="info" @click="cancelSelection">{{ t('common.cancel') }}</el-button> <el-button type="primary" @click="confirmSelection">{{ t('common.confirm') }}</el-button> </div> </div> </div></template><script setup>import { ref, watch, onMounted, computed, onUnmounted, nextTick } from 'vue';import { ArrowDown, ArrowRight } from '@element-plus/icons-vue';import { useFormItem } from 'element-plus';// 国际化
import { useI18n } from 'vue-i18n'
const { t } = useI18n()const { formItem } = useFormItem()
const searchData = ref('')const isOpen = ref(false)const coinisOpen = ref(false)const productisOpen = ref(false)const selectedItem = ref('')const dropdownRef = ref(null)const placeholder = ref(t('common_add.productNamePlaceholder'))
const handelMenu = () => { isOpen.value = !isOpen.value ifselectAndOpen()}const coinhandelMenu = () => { if (props.restrictType === 'software') return; coinisOpen.value = !coinisOpen.value}const producthandelMenu = () => { if (props.restrictType === 'gold') return; productisOpen.value = !productisOpen.value}
// 接收父组件通过 v-model 传入的值
const props = defineProps({ modelValue: { type: String, default: '' }, restrictType: { type: String, default: 'all' // 'all', 'gold', 'software'
}})
const emit = defineEmits(['update:modelValue', 'change'])
const selectedValue = ref('')
const confirmSelection = () => { emit('update:modelValue', selectedValue.value) emit('change', selectedValue.value) selectedItem.value = selectedValue.value formItem?.validate('change').catch(() => { }) isOpen.value = false}
const cancelSelection = () => { selectedValue.value = props.modelValue selectedItem.value = props.modelValue || '' isOpen.value = false showPanel.value = false}
const AIProduct = [ t('cash.aiService.aiTracking'), t('cash.aiService.aiAttack'), t('cash.aiService.aiFunds'), t('cash.aiService.aiActivity'), t('cash.aiService.aiDetectionTool')]const superProduct = [ t('cash.aiService.superPerspective'), t('cash.aiService.superAmbush'), t('cash.aiService.superHunting'), t('cash.aiService.superPulse'), t('cash.aiService.superCompass'), t('cash.aiService.superDetectionTool')]const InfoFee = [ t('cash.staticInfoFee'), t('cash.BGmember'),]const showPanel = ref(false)const activeCascaderOption = ref(null)
const handleClick = (ai) => { selectedValue.value = ai;}const cascaderValue = ref([])const handlePanelChange = (val) => { selectedValue.value = val[val.length - 1] showPanel.value = false // 选择后关闭面板
console.log('selectedValue.value', selectedValue.value);}const ifHcInfo = ref(false)const checkIfHcINFO = () => { ifHcInfo.value = cascaderOptions.some(group => group.children && group.children.some(child => child.value === selectedValue.value) ) console.log('ifHcInfo.value', ifHcInfo.value);}// 级联选择器数据源
const cascaderOptions = [ { value: 'realTime', label: t('cash.aiService.realTime'), children: [ { value: t('cash.aiService.HCInfoFeeRealTimeUS'), label: t('cash.softwareMenu.usStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeHK'), label: t('cash.softwareMenu.hkStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeMalaysiaStock'), label: t('cash.softwareMenu.malaysiaStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeAStock'), label: t('cash.softwareMenu.aStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeSingaporeStock'), label: t('cash.softwareMenu.singaporeStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeJapanStock'), label: t('cash.softwareMenu.japanStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeThailandStock'), label: t('cash.softwareMenu.thailandStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeVietnamStock'), label: t('cash.softwareMenu.vietnamStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeIndonesiaStock'), label: t('cash.softwareMenu.indonesiaStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeKoreaStock'), label: t('cash.softwareMenu.koreaStock') }, { value: t('cash.aiService.HCInfoFeeRealTimeTaiwanStock'), label: t('cash.softwareMenu.taiwanStock') } ] }, { value: 'delayed', label: t('cash.aiService.delayed'), children: [ { value: t('cash.aiService.HCInfoFeeDelayUS'), label: t('cash.softwareMenu.usStock') }, { value: t('cash.aiService.HCInfoFeeDelayHK'), label: t('cash.softwareMenu.hkStock') }, { value: t('cash.aiService.HCInfoFeeDelayMalaysiaStock'), label: t('cash.softwareMenu.malaysiaStock') }, { value: t('cash.aiService.HCInfoFeeDelayAStock'), label: t('cash.softwareMenu.aStock') }, { value: t('cash.aiService.HCInfoFeeDelaySingaporeStock'), label: t('cash.softwareMenu.singaporeStock') }, { value: t('cash.aiService.HCInfoFeeDelayJapanStock'), label: t('cash.softwareMenu.japanStock') }, { value: t('cash.aiService.HCInfoFeeDelayThailandStock'), label: t('cash.softwareMenu.thailandStock') }, { value: t('cash.aiService.HCInfoFeeDelayVietnamStock'), label: t('cash.softwareMenu.vietnamStock') }, { value: t('cash.aiService.HCInfoFeeDelayIndonesiaStock'), label: t('cash.softwareMenu.indonesiaStock') }, { value: t('cash.aiService.HCInfoFeeDelayKoreaStock'), label: t('cash.softwareMenu.koreaStock') }, { value: t('cash.aiService.HCInfoFeeDelayTaiwanStock'), label: t('cash.softwareMenu.taiwanStock') } ] }]
watch(selectedValue, () => { checkIfHcINFO();}, { immediate: true });
// 使用多语言字段重新构建menuData
const menuData = [ { name: t('cash.softwareMenu.usStock'), options: [ t('cash.softwareMenu.usStockSoftware'), t('cash.softwareMenu.usStockGoldCard'), t('cash.softwareMenu.usStockPackage') ] }, { name: t('cash.softwareMenu.hkStock'), options: [ t('cash.softwareMenu.hkStockSoftware'), t('cash.softwareMenu.hkStockGoldCard'), t('cash.softwareMenu.hkStockPackage') ] }, { name: t('cash.softwareMenu.aStock'), options: [ t('cash.softwareMenu.aStockSoftware'), t('cash.softwareMenu.aStockGoldCard'), t('cash.softwareMenu.aStockPackage') ] }, { name: t('cash.softwareMenu.singaporeStock'), options: [ t('cash.softwareMenu.singaporeStockSoftware'), t('cash.softwareMenu.singaporeStockGoldCard'), t('cash.softwareMenu.singaporeStockPackage') ] }, { name: t('cash.softwareMenu.malaysiaStock'), options: [ t('cash.softwareMenu.malaysiaStockSoftware'), t('cash.softwareMenu.malaysiaStockGoldCard'), t('cash.softwareMenu.malaysiaStockPackage') ] }, { name: t('cash.softwareMenu.japanStock'), options: [ t('cash.softwareMenu.japanStockSoftware'), t('cash.softwareMenu.japanStockGoldCard'), t('cash.softwareMenu.japanStockPackage') ] }, { name: t('cash.softwareMenu.thailandStock'), options: [ t('cash.softwareMenu.thailandStockSoftware'), t('cash.softwareMenu.thailandStockGoldCard'), t('cash.softwareMenu.thailandStockPackage') ] }, { name: t('cash.softwareMenu.vietnamStock'), options: [ t('cash.softwareMenu.vietnamStockSoftware'), t('cash.softwareMenu.vietnamStockGoldCard'), t('cash.softwareMenu.vietnamStockPackage') ] }, { name: t('cash.softwareMenu.indonesiaStock'), options: [ t('cash.softwareMenu.indonesiaStockSoftware'), t('cash.softwareMenu.indonesiaStockGoldCard'), t('cash.softwareMenu.indonesiaStockPackage') ] }, { name: t('cash.softwareMenu.koreaStock'), options: [ t('cash.softwareMenu.koreaStockSoftware'), t('cash.softwareMenu.koreaStockGoldCard'), t('cash.softwareMenu.koreaStockPackage') ] }, { name: t('cash.softwareMenu.taiwanStock'), options: [ t('cash.softwareMenu.taiwanStockSoftware'), t('cash.softwareMenu.taiwanStockGoldCard'), t('cash.softwareMenu.taiwanStockPackage') ] }];//全局事件实现点击外部关闭选项
const closeSoftwareSubmenu = () => { activeIndex.value = -1; // 将展开的二级菜单索引重置为 -1,实现关闭
};
const closeWholeDropdown = () => { isOpen.value = false; // 关闭整个弹窗
// 可选:同时关闭弹窗内的子面板(如金币产品、软件产品展开面板)
coinisOpen.value = false; productisOpen.value = false; closeSoftwareSubmenu(); // 同时关闭软件二级选项框
};
const handleGlobalClick = (e) => { // --- 原有:处理软件二级选项框外部关闭逻辑 ---
if (productisOpen.value) { const submenuContainers = document.querySelectorAll('.selectoption'); const firstLevelContainers = document.querySelectorAll('.fistlevel'); let isClickInsideSubmenu = false; let isClickInsideFirstLevel = false;
submenuContainers.forEach(container => container.contains(e.target) && (isClickInsideSubmenu = true)); firstLevelContainers.forEach(container => container.contains(e.target) && (isClickInsideFirstLevel = true));
if (!isClickInsideSubmenu && !isClickInsideFirstLevel) { closeSoftwareSubmenu(); } }
// --- 新增:处理整个弹窗外部关闭逻辑 ---
if (isOpen.value) { // 仅当弹窗处于打开状态时判断
// 获取整个下拉弹窗的 DOM 容器
const dropdownContainer = document.querySelector('.menu'); // 获取弹窗触发按钮(类名 .selectBox),点击按钮需正常切换弹窗,不触发关闭
const triggerButton = document.querySelector('.selectBox');
// 点击位置不在弹窗内,且不在触发按钮内 → 关闭整个弹窗
if (!dropdownContainer?.contains(e.target) && !triggerButton?.contains(e.target)) { closeWholeDropdown(); } }};
const ifselectAndOpen = async () => { await nextTick(); if (selectedValue.value == t('cash.coinRecharge')) { coinisOpen.value = true } else if (selectedValue.value) { productisOpen.value = true } else { //不做处理
}}
//软件相关
const activeIndex = ref(-1)const clickmenu = (index) => { activeIndex.value = activeIndex.value === index ? -1 : index;}
const resetSelect = () => { selectedValue.value = ''; // 重置选中值
selectedItem.value = ''; // 重置显示文本
isOpen.value = false; // 关闭下拉菜单
coinisOpen.value = false; // 关闭金币产品子菜单
productisOpen.value = false; // 关闭软件产品子菜单
activeIndex.value = -1; // 关闭二级菜单
};
watch(() => props.modelValue, (newVal) => { selectedItem.value = newVal; selectedValue.value = newVal; checkIfHcINFO();}, { immediate: true });onMounted(async () => { console.log('打开组件', props.modelValue); selectedValue.value = props.modelValue; selectedItem.value = selectedValue.value || ''; document.addEventListener('click', handleGlobalClick); // 绑定全局事件
});onUnmounted(() => { selectedValue.value = ''; document.removeEventListener('click', handleGlobalClick); // 解绑全局事件
});defineExpose({ resetSelect });</script><style scoped lang="scss">.productContent { position: relative; width: 450px; font-family: 'Arial', sans-serif;}
.selectBox { border: 1px solid #e5e7eb; padding: 4px 12px; height: 23px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; background-color: #fff; border-radius: 6px; transition: all 0.3s ease;
.placeholder { flex: 1; font-size: 14px; line-height: 18px; color: #A8ABB2; }}
.arrow { margin-left: 8px; color: #999; transition: transform 0.3s ease;}
.selectBox.active .arrow { transform: rotate(180deg);}
.menu { position: absolute; top: 100%; left: 0; width: 200%; min-height: 200px; display: flex; padding: 10px; flex-direction: column; align-items: flex-start; gap: 10px; flex-shrink: 0; border-radius: 8px; background: #E4F0FC; box-shadow: 0 0 4px 0 #00000040; z-index: 3000;
.coinselect { width: 126px; height: 20px; border: 1px solid #175BE5; padding: 5px 0 5px 12px; display: flex; border-radius: 5px;
&.disabled-menu { cursor: not-allowed; opacity: 0.5; background-color: #f5f7fa; }
.cointxt { width: 100px; height: 100%; display: flex; justify-content: center; align-items: center; color: #175be5; text-align: center; font-family: "PingFang SC"; font-size: 14px; font-style: normal; font-weight: 700; line-height: 20px; } }
.coin-arrow { flex: 1; display: flex; justify-content: center; align-items: center; color: #175BE5; }
.coinselect.active .coin-arrow { transform: rotate(-90deg); }
.product { width: 100%;
.line { display: flex; height: 1px; padding: 0 16px; align-items: flex-start; align-content: flex-start; gap: 8px 60px; flex-shrink: 0; align-self: stretch; flex-wrap: wrap; border-top: 1px solid #7E91FF; }
.checktxt { color: #5870ff; font-family: "PingFang SC"; font-size: 13px; font-style: normal; font-weight: 700; line-height: 22px; margin: 10px 20px; }
.productOption { width: 100%;
.ai { width: 100%;
.radio-wrapper { position: relative; display: inline-block; margin-right: 32px; } }
.marketprodut { width: 100%; display: flex; flex-wrap: wrap; gap: 10px;
.fistlevel { position: relative; width: 130px;
.label .el-icon { margin-left: 5px; transition: transform 0.3s ease; }
.label .rotate { transform: rotate(-90deg); // 箭头旋转效果
}
.label { margin-left: 10px; }
.selectoption { width: 100px; background-color: #fff; padding: 5px 20px; position: absolute; left: 60px; top: 0; z-index: 999; border: 1px solid #175BE5; border-radius: 6px;
:deep(.el-checkbox__label) { color: #333333; font-family: "PingFang SC"; font-size: 13px; font-style: normal; font-weight: 400; line-height: 22px; } } }
.fistlevel.selected .label { color: #175BE5; } } } }
.selected-value { width: 100%; color: #5870ff; font-size: 12px; text-align: center; border-top: 1px solid #7E91FF; padding-top: 10px; }
.confirm { width: 100%; padding: 10px 0; display: flex; justify-content: center; gap: 50px; }}
/* Native Radio Styles */.native-radio-group { display: inline-block;}
.native-radio-item { position: relative; display: inline-flex; align-items: center; cursor: pointer; margin-right: 0;}
.native-radio-input { white-space: nowrap; cursor: pointer; outline: none; display: inline-flex; position: relative; vertical-align: middle; width: 14px; height: 14px; background-color: #fff; border: 1px solid #dcdfe6; border-radius: 50%; box-sizing: border-box; margin-right: 8px;
&.is-checked { border-color: #409eff; background: #409eff;
.native-radio-inner { width: 4px; height: 4px; border-radius: 50%; background-color: #fff; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } }}
.native-radio-label { font-size: 14px; color: #606266;
&.is-checked { color: #409eff; }}
/* Native Cascader Styles */.native-cascader-panel { position: absolute; top: -36px; left: 100px; /* Adjust as needed */ z-index: 1000; background: #E4F0FC; border: 1px solid #e4e7ed; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); border-radius: 4px; display: flex; max-height: 100px;}
.cascader-menu { min-width: 150px; padding: 6px 0; background: #E4F0FC;
border-right: 1px solid #e4e7ed;
&:last-child { border-right: none; }
&.sub-menu { /* Slightly different bg for submenu */ overflow-y: auto; height: 150px; }}
.cascader-node { padding: 8px 15px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; font-size: 14px; color: #606266;
&:hover, &.is-active { background-color: #f5f7fa; color: #409eff; }
.cascader-icon { font-size: 12px; margin-left: 10px; color: #c0c4cc; }}</style>
|