346 lines
9.1 KiB
Vue
346 lines
9.1 KiB
Vue
<template>
|
||
<uni-popup
|
||
ref="popup"
|
||
type="bottom"
|
||
borderRadius="10px 10px 0 0"
|
||
background-color="#FFFFFF"
|
||
@maskClick="maskClickFn"
|
||
:mask-click="maskClick"
|
||
class="popup-fix"
|
||
>
|
||
<view class="popup-content">
|
||
<view class="popup-header">
|
||
<view class="btn-cancel" @click="cancel">取消</view>
|
||
<view class="title">
|
||
<text>{{ title }}</text>
|
||
</view>
|
||
<view class="btn-confirm"></view>
|
||
</view>
|
||
<view class="popup-list">
|
||
<view class="content-wrapper">
|
||
<scroll-view class="filter-nav" scroll-y>
|
||
<view
|
||
v-for="(item, index) in filterOptions"
|
||
:key="index"
|
||
class="nav-item button-click"
|
||
:class="{ active: activeTab === item.key }"
|
||
@click="scrollTo(item.key)"
|
||
>
|
||
{{ item.label }}
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<scroll-view class="filter-content" :scroll-into-view="activeTab" scroll-y>
|
||
<template v-for="(item, index) in filterOptions" :key="index">
|
||
<view class="content-item">
|
||
<view class="item-title" :id="item.key">{{ item.label }}</view>
|
||
<view class="check-content">
|
||
<view
|
||
v-for="option in item.options"
|
||
:key="option.value"
|
||
class="checkbox-item button-click"
|
||
:class="{
|
||
checkedstyle: activeValue === option.value,
|
||
}"
|
||
@click="handleItemClick(option)"
|
||
>
|
||
<text class="option-label">{{ option.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</uni-popup>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, nextTick, onBeforeMount } from 'vue';
|
||
import useDictStore from '@/stores/useDictStore';
|
||
const { getTransformChildren } = useDictStore();
|
||
|
||
const area = ref(true);
|
||
const maskClick = ref(false);
|
||
const maskClickFn = ref(null);
|
||
|
||
const title = ref('标题');
|
||
const confirmCallback = ref(null);
|
||
const cancelCallback = ref(null);
|
||
const changeCallback = ref(null);
|
||
const popup = ref(null);
|
||
// MODIFIED: 新增 ref,用于存储当前激活的选项值
|
||
const activeValue = ref(null);
|
||
|
||
const activeTab = ref('');
|
||
const filterOptions = ref([]);
|
||
const listData = ref([]);
|
||
|
||
// MODIFIED: open 方法增加一个 currentValue 参数
|
||
const open = (newConfig = {}) => {
|
||
const {
|
||
title: configTitle,
|
||
success,
|
||
cancel,
|
||
change,
|
||
data,
|
||
maskClick: configMaskClick = false,
|
||
currentValue, // MODIFIED: 接收父组件传入的当前值
|
||
} = newConfig;
|
||
|
||
// reset();
|
||
|
||
if (configTitle) title.value = configTitle;
|
||
if (typeof success === 'function') confirmCallback.value = success;
|
||
if (typeof cancel === 'function') cancelCallback.value = cancel;
|
||
if (typeof change === 'function') changeCallback.value = change;
|
||
if (Array.isArray(data)) listData.value = data;
|
||
|
||
// MODIFIED: 将父组件传入的值
|
||
activeValue.value = currentValue;
|
||
|
||
if (configMaskClick) {
|
||
maskClick.value = configMaskClick;
|
||
maskClickFn.value = cancel;
|
||
}
|
||
|
||
getoptions();
|
||
|
||
nextTick(() => {
|
||
popup.value?.open();
|
||
});
|
||
};
|
||
|
||
const close = () => {
|
||
popup.value?.close();
|
||
};
|
||
|
||
const cancel = () => {
|
||
handleClick(cancelCallback.value, null);
|
||
};
|
||
|
||
const handleClick = async (callback, selectedItem) => {
|
||
if (typeof callback !== 'function') {
|
||
close();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const result = await callback(selectedItem);
|
||
if (result !== false) close();
|
||
} catch (error) {
|
||
console.error('Callback execution error:', error);
|
||
}
|
||
};
|
||
|
||
const handleItemClick = (option) => {
|
||
// MODIFIED: 点击时,更新本地的 activeValue
|
||
activeValue.value = option.value;
|
||
// 立即调用回调并传递所选的完整 option
|
||
handleClick(confirmCallback.value, option);
|
||
};
|
||
|
||
const scrollTo = (key) => {
|
||
activeTab.value = key;
|
||
};
|
||
|
||
function getoptions() {
|
||
filterOptions.value = transformRegionalData(listData.value);
|
||
activeTab.value = listData.value[0].key;
|
||
}
|
||
|
||
const reset = () => {
|
||
maskClick.value = false;
|
||
confirmCallback.value = null;
|
||
cancelCallback.value = null;
|
||
changeCallback.value = null;
|
||
// MODIFIED: 重置时也清空 activeValue
|
||
activeValue.value = null;
|
||
};
|
||
|
||
function transformRegionalData(sourceData) {
|
||
const options = sourceData.map((region, index) => {
|
||
const ls = region.areaList.map((commercial) => ({
|
||
...commercial,
|
||
text: commercial.commercialAreaName,
|
||
label: commercial.commercialAreaName,
|
||
value: commercial.commercialAreaId,
|
||
key: commercial.commercialAreaId,
|
||
listClass: 'default',
|
||
status: 'default',
|
||
}));
|
||
return {
|
||
label: region.regionalName,
|
||
key: 'lx' + region.regionalId,
|
||
options: ls,
|
||
};
|
||
});
|
||
|
||
return options;
|
||
}
|
||
|
||
// 暴露方法给父组件
|
||
defineExpose({
|
||
open,
|
||
close,
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
/* 样式表无变化,保持原样即可 */
|
||
.popup-fix {
|
||
position: fixed !important;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
top: 0;
|
||
height: 100vh;
|
||
z-index: 9999;
|
||
}
|
||
.popup-content {
|
||
color: #000000;
|
||
height: 80vh;
|
||
}
|
||
.popup-bottom {
|
||
display: none;
|
||
}
|
||
.popup-list {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: nowrap;
|
||
align-items: center;
|
||
justify-content: space-evenly;
|
||
height: calc(80vh - 100rpx);
|
||
overflow: hidden;
|
||
.picker-view {
|
||
width: 100%;
|
||
height: 500rpx;
|
||
margin-top: 20rpx;
|
||
.uni-picker-view-mask {
|
||
background: rgba(0, 0, 0, 0);
|
||
}
|
||
.item {
|
||
line-height: 84rpx;
|
||
height: 84rpx;
|
||
text-align: center;
|
||
font-weight: 400;
|
||
font-size: 32rpx;
|
||
color: #cccccc;
|
||
}
|
||
.item-active {
|
||
color: #333333;
|
||
}
|
||
.uni-picker-view-indicator:after {
|
||
border-color: #e3e3e3;
|
||
}
|
||
.uni-picker-view-indicator:before {
|
||
border-color: #e3e3e3;
|
||
}
|
||
}
|
||
}
|
||
.popup-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 40rpx 40rpx 10rpx 40rpx;
|
||
.title {
|
||
font-weight: 500;
|
||
font-size: 36rpx;
|
||
color: #333333;
|
||
text-align: center;
|
||
}
|
||
.btn-cancel {
|
||
font-weight: 400;
|
||
font-size: 32rpx;
|
||
color: #666d7f;
|
||
line-height: 38rpx;
|
||
}
|
||
.btn-confirm {
|
||
font-weight: 400;
|
||
font-size: 32rpx;
|
||
color: #256bfa;
|
||
min-width: 60rpx;
|
||
}
|
||
}
|
||
.content-wrapper {
|
||
flex: 1;
|
||
display: flex;
|
||
overflow: hidden;
|
||
height: 100%;
|
||
}
|
||
|
||
.filter-nav {
|
||
width: 200rpx;
|
||
background-color: #ffffff;
|
||
|
||
.nav-item {
|
||
height: 100rpx;
|
||
line-height: 100rpx;
|
||
text-align: center;
|
||
font-weight: 400;
|
||
font-size: 28rpx;
|
||
color: #666d7f;
|
||
&.active {
|
||
font-weight: 500;
|
||
font-size: 28rpx;
|
||
color: #256bfa;
|
||
}
|
||
}
|
||
}
|
||
|
||
.filter-content {
|
||
flex: 1;
|
||
padding: 20rpx;
|
||
background-color: #f6f6f6;
|
||
|
||
.content-item {
|
||
margin-top: 30rpx;
|
||
.item-title {
|
||
font-weight: 400;
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
}
|
||
.content-item:first-child {
|
||
margin-top: 0rpx;
|
||
}
|
||
|
||
.check-content {
|
||
display: grid;
|
||
gap: 16rpx;
|
||
grid-template-columns: repeat(auto-fill, minmax(180rpx, 1fr));
|
||
place-items: stretch;
|
||
|
||
.checkbox-item {
|
||
display: flex;
|
||
align-items: center;
|
||
text-align: center;
|
||
background-color: #d9d9d9;
|
||
|
||
min-width: 0;
|
||
padding: 0 10rpx;
|
||
height: 80rpx;
|
||
background: #e8eaee;
|
||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||
|
||
.option-label {
|
||
font-size: 28rpx;
|
||
width: 100%;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
}
|
||
}
|
||
/* 这个样式现在会根据 activeValue 动态应用 */
|
||
.checkedstyle {
|
||
height: 76rpx;
|
||
background: rgba(37, 107, 250, 0.06);
|
||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||
border: 2rpx solid #256bfa;
|
||
color: #256bfa;
|
||
}
|
||
}
|
||
}
|
||
</style>
|