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.
 
 
 
 

773 lines
22 KiB

<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>