🎈 perf(产业运营平台-电商交易模块的页面中筛选条件存在的问题优化,替换新组件;增加三级tabs切换通用组件;):

This commit is contained in:
郭永超 2025-08-28 15:35:03 +08:00
parent ec47e147dc
commit 30470ee192
5 changed files with 678 additions and 5 deletions

View File

@ -14,6 +14,7 @@ declare module 'vue' {
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default'] CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
copy: typeof import('./src/components/custom-scroll-title copy/index.vue')['default'] copy: typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
CostomImg: typeof import('./src/components/costomImg.vue')['default'] CostomImg: typeof import('./src/components/costomImg.vue')['default']
CostomTabs: typeof import('./src/components/costomTabs.vue')['default']
CustomBack: typeof import('./src/components/customBack.vue')['default'] CustomBack: typeof import('./src/components/customBack.vue')['default']
CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.vue')['default'] CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.vue')['default']
CustomEchartBar: typeof import('./src/components/custom-echart-bar/index.vue')['default'] CustomEchartBar: typeof import('./src/components/custom-echart-bar/index.vue')['default']

View File

@ -0,0 +1,469 @@
<template>
<!-- tabs少数据时用这个多数据时单行会两端有切换箭头 -->
<!-- <div class="coustom-tabs-container" :style="{ fontSize: props.fontSize }">
<el-tabs v-model="active1Current" @tab-change="handle1Click">
<el-tab-pane v-for="item in tabsLivel1Data" :key="item.id" :label="item.name" :name="item.id"> </el-tab-pane>
</el-tabs>
<el-tabs v-show="showLivel >= 2 && tabsLivel2Data.length > 0" v-model="active2Current" @tab-change="handle2Click">
<el-tab-pane v-for="item in tabsLivel2Data" :key="item.id" :label="item.name" :name="item.id"> </el-tab-pane>
</el-tabs>
<el-tabs v-show="showLivel >= 3 && tabsLivel3Data.length > 0" v-model="active3Current" @tab-change="handle3Click">
<el-tab-pane v-for="item in tabsLivel3Data" :key="item.id" :label="item.name" :name="item.id"> </el-tab-pane>
</el-tabs>
</div> -->
<!-- 自定义选项卡样式 -->
<div class="coustom-div-container" :style="{ fontSize: props.fontSize }">
<!-- 第一级选项卡 - 改为使用 div 模拟 -->
<div class="custom-tabs-container">
<div
v-for="item in tabsLivel1Data"
:key="item.id"
class="custom-tab-item"
:class="{
active: active1Current === item.id,
'all-tab': item.isAll,
}"
@click="handleCustomTabClick(1, item)"
>
{{ item.name }}
</div>
</div>
<!-- 第二级选项卡 -->
<div v-if="showLivel >= 2 && tabsLivel2Data.length > 0" class="custom-tabs-container">
<div
v-for="item in tabsLivel2Data"
:key="item.id"
class="custom-tab-item"
:class="{
active: active2Current === item.id,
'all-tab': item.isAll,
}"
@click="handleCustomTabClick(2, item)"
>
{{ item.name }}
</div>
</div>
<!-- 第三级选项卡 -->
<div v-if="showLivel >= 3 && tabsLivel3Data.length > 0" class="custom-tabs-container">
<div
v-for="item in tabsLivel3Data"
:key="item.id"
class="custom-tab-item"
:class="{
active: active3Current === item.id,
'all-tab': item.isAll,
}"
@click="handleCustomTabClick(3, item)"
>
{{ item.name }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed, onMounted, nextTick } from 'vue';
const props = defineProps({
list: {
type: Array,
required: true,
default: () => [
// {
// id: '71',
// level: 2,
// name: '',
// parentId: '60',
// children: [
// {
// id: '7101',
// level: 3,
// name: '',
// parentId: '71',
// },
// {
// id: '7102',
// level: 3,
// name: '',
// parentId: '71',
// },
// {
// id: '7103',
// level: 3,
// name: '',
// parentId: '71',
// },
// ],
// },
// {
// id: '72',
// level: 2,
// name: '',
// parentId: '60',
// children: [
// {
// id: '7201',
// level: 3,
// name: '',
// parentId: '72',
// },
// {
// id: '7202',
// level: 3,
// name: '',
// parentId: '72',
// },
// ],
// },
// {
// id: '73',
// level: 2,
// name: '',
// parentId: '60',
// children: [
// {
// id: '7301',
// level: 3,
// name: '',
// parentId: '73',
// },
// {
// id: '7302',
// level: 3,
// name: '',
// parentId: '73',
// },
// ],
// },
],
},
showLivel: {
type: Number,
default: 3,
validator: (value) => [1, 2, 3].includes(value),
},
fontSize: {
type: String,
default: '18px',
},
});
const emit = defineEmits(['select', 'change']);
// ID
const active1Current = ref('');
const active2Current = ref('');
const active3Current = ref('');
// ""
const createAllOption = (level, parentId = null) => {
return {
id: `all-level-${level}`,
name: '全部',
level: level,
parentId: parentId,
isAll: true, //
};
};
//
const tabsLivel1Data = computed(() => {
const allOption = createAllOption(1);
return [allOption, ...props.list];
});
const tabsLivel2Data = computed(() => {
if (!active1Current.value) return [];
// ""
if (active1Current.value === 'all-level-1') {
const allChildren = props.list.flatMap((item) => item.children || []);
// ""
return allChildren.length > 0 ? [createAllOption(2, active1Current.value), ...allChildren] : [];
}
//
const selectedItem = props.list.find((item) => item.id === active1Current.value);
const children = selectedItem?.children || [];
// ""
return children.length > 0 ? [createAllOption(2, active1Current.value), ...children] : [];
});
const tabsLivel3Data = computed(() => {
if (!active2Current.value) return [];
let children = [];
//
if (active2Current.value === 'all-level-2') {
//
if (active1Current.value === 'all-level-1') {
children = props.list.flatMap((item) => (item.children ? item.children.flatMap((child) => child.children || []) : []));
} else {
const level1Item = props.list.find((item) => item.id === active1Current.value);
children = level1Item?.children ? level1Item.children.flatMap((child) => child.children || []) : [];
}
} else {
//
let parentItem = null;
if (active1Current.value === 'all-level-1') {
for (const level1Item of props.list) {
if (level1Item.children) {
const found = level1Item.children.find((item) => item.id === active2Current.value);
if (found) {
parentItem = found;
break;
}
}
}
} else {
const level1Item = props.list.find((item) => item.id === active1Current.value);
parentItem = level1Item?.children?.find((item) => item.id === active2Current.value);
}
children = parentItem?.children || [];
}
// ""
return children.length > 0 ? [createAllOption(3, active2Current.value), ...children] : [];
});
//
const initializeSelection = () => {
if (tabsLivel1Data.value.length > 0) {
//
active1Current.value = tabsLivel1Data.value[0].id;
if (props.showLivel >= 2 && tabsLivel2Data.value.length > 0) {
//
active2Current.value = tabsLivel2Data.value[0].id;
if (props.showLivel >= 3 && tabsLivel3Data.value.length > 0) {
//
active3Current.value = tabsLivel3Data.value[0].id;
}
}
//
emitSelection();
}
};
//
const getSelectedItems = () => {
const selectedItems = [];
//
if (active1Current.value) {
const level1Item = tabsLivel1Data.value.find((item) => item.id === active1Current.value);
if (level1Item) {
selectedItems.push({
id: level1Item.id,
name: level1Item.name,
isAll: level1Item.isAll || false,
...level1Item,
});
}
}
//
if (active2Current.value && props.showLivel >= 2) {
const level2Item = tabsLivel2Data.value.find((item) => item.id === active2Current.value);
if (level2Item) {
selectedItems.push({
id: level2Item.id,
name: level2Item.name,
isAll: level2Item.isAll || false,
...level2Item,
});
}
}
//
if (active3Current.value && props.showLivel >= 3) {
const level3Item = tabsLivel3Data.value.find((item) => item.id === active3Current.value);
if (level3Item) {
selectedItems.push({
id: level3Item.id,
name: level3Item.name,
isAll: level3Item.isAll || false,
...level3Item,
});
}
}
return selectedItems;
};
//
const emitSelection = () => {
const selectedItems = getSelectedItems();
emit('select', selectedItems);
emit('change', selectedItems);
};
//
const handle1Click = (tabName) => {
active1Current.value = tabName;
//
if (props.showLivel >= 2) {
const allOption = tabsLivel2Data.value.find((item) => item.isAll);
active2Current.value = allOption ? allOption.id : '';
}
if (props.showLivel >= 3) {
const allOption = tabsLivel3Data.value.find((item) => item.isAll);
active3Current.value = allOption ? allOption.id : '';
}
emitSelection();
};
//
const handle2Click = (tabName) => {
active2Current.value = tabName;
//
if (props.showLivel >= 3) {
const allOption = tabsLivel3Data.value.find((item) => item.isAll);
active3Current.value = allOption ? allOption.id : '';
}
emitSelection();
};
//
const handle3Click = (tabName) => {
active3Current.value = tabName;
emitSelection();
};
//
const handleCustomTabClick = (level, item) => {
switch (level) {
case 1:
active1Current.value = item.id;
if (props.showLivel >= 2) {
const allOption = tabsLivel2Data.value.find((item) => item.isAll);
active2Current.value = allOption ? allOption.id : '';
}
if (props.showLivel >= 3) {
const allOption = tabsLivel3Data.value.find((item) => item.isAll);
active3Current.value = allOption ? allOption.id : '';
}
break;
case 2:
active2Current.value = item.id;
if (props.showLivel >= 3) {
const allOption = tabsLivel3Data.value.find((item) => item.isAll);
active3Current.value = allOption ? allOption.id : '';
}
break;
case 3:
active3Current.value = item.id;
break;
}
emitSelection();
};
// props.list
watch(
() => props.list,
(newList) => {
if (newList && newList.length > 0) {
nextTick(() => {
initializeSelection();
});
}
},
{ immediate: true, deep: true }
);
//
onMounted(() => {
if (props.list && props.list.length > 0) {
initializeSelection();
}
});
</script>
<style lang="scss" scoped>
// tabs
.coustom-tabs-container {
width: 100%;
.el-tabs {
margin-bottom: 10px;
:deep(.el-tabs__header) {
margin: 0;
}
:deep(.el-tabs__item) {
padding: 0 16px;
height: 32px;
line-height: 32px;
font-size: v-bind('props.fontSize') !important;
}
//
:deep(.el-tabs__item:first-child) {
color: #67c23a; // 绿
font-weight: 500;
}
:deep(.el-tabs__nav-wrap:after) {
background-color: transparent;
}
}
}
//
.coustom-div-container {
width: 100%;
}
.custom-tabs-container {
display: flex;
flex-wrap: wrap; /* 关键属性:允许换行 */
gap: 12px; /* 选项卡之间的间距 */
background: #fff;
border-radius: 8px;
margin-bottom: 16px;
}
.custom-tab-item {
padding: 0 8px;
cursor: pointer;
transition: all 0.3s ease;
white-space: nowrap;
/* 使用传入的字体大小 */
font-size: v-bind('props.fontSize');
&:hover {
color: $color-main;
}
&.active {
color: $color-main;
}
&.all-tab {
color: #323232;
&.active {
color: $color-main;
}
&:hover {
color: $color-main;
}
}
}
/* 响应式调整 */
@media (max-width: 768px) {
.custom-tab-item {
padding: 6px 12px;
font-size: 14px;
}
}
</style>

View File

@ -51,6 +51,7 @@ const toLink = (index) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.ecommerce-left-menu-warp { .ecommerce-left-menu-warp {
padding: 0 30px 0 10px;
width: 100%; width: 100%;
height: 100%; height: 100%;
.left-menu { .left-menu {

View File

@ -3,7 +3,37 @@
<common current-name="agricultural"> <common current-name="agricultural">
<template #main> <template #main>
<!-- <banner :imglist="bannerList"></banner> --> <!-- <banner :imglist="bannerList"></banner> -->
<filtertop :list="treeList" @select="selected" @search="search"></filtertop> <!-- 原来的顶部筛选组件不好用 -->
<!-- <filtertop :list="treeList" @select="selected" @search="search"></filtertop> -->
<!-- 顶部筛选条件 -->
<div class="filter-container">
<div style="width: 40%; margin-left: 10px; margin-bottom: 10px">
<div class="search-container">
<div class="search-box">
<input v-model="searchName" type="text" placeholder="请输入农资商品名称" class="search-input" />
<button class="search-button" @click.stop="onQuery()">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
</div>
</div>
<costomTabs :list="treeList" @change="changeTabs"></costomTabs>
</div>
<div class="goods-list-warp"> <div class="goods-list-warp">
<div class="goods-list"> <div class="goods-list">
<template v-for="(n, index) in list" :key="n.id"> <template v-for="(n, index) in list" :key="n.id">
@ -32,6 +62,7 @@ import common from './components/common.vue';
import banner from './components/banner.vue'; import banner from './components/banner.vue';
import filtertop from './components/filtertop.vue'; import filtertop from './components/filtertop.vue';
import goodsItem from './components/goodsItem.vue'; import goodsItem from './components/goodsItem.vue';
import costomTabs from '@/components/costomTabs.vue';
import { ref, reactive, onMounted, onBeforeMount, onUnmounted, watch, computed } from 'vue'; import { ref, reactive, onMounted, onBeforeMount, onUnmounted, watch, computed } from 'vue';
import { transaction, agriculturalList } from '@/apis/agricultural'; import { transaction, agriculturalList } from '@/apis/agricultural';
let treeList = reactive([ let treeList = reactive([
@ -71,7 +102,24 @@ onBeforeMount(() => {
getTree(); getTree();
getList(); getList();
}); });
const changeTabs = (paramsArr) => {
console.log('tabs选项', paramsArr);
let level1Id = 0;
let level2Id = 0;
let level3Id = 0;
if (paramsArr.length >= 1) {
level1Id = paramsArr[0]?.name == '全部' ? 0 : paramsArr[0]?.id || 0;
}
if (paramsArr.length >= 2) {
level2Id = paramsArr[1]?.name == '全部' ? 0 : paramsArr[1]?.id || 0;
}
if (paramsArr.length >= 3) {
level3Id = paramsArr[2]?.name == '全部' ? 0 : paramsArr[2]?.id || 0;
}
console.log('处理后的ID:', level1Id, level2Id, level3Id);
};
// //
const getTree = () => { const getTree = () => {
transaction().then((res) => { transaction().then((res) => {
@ -84,6 +132,7 @@ const getTree = () => {
}; };
// //
let searchName = ref('');
let list = reactive([]); let list = reactive([]);
let params = reactive({ let params = reactive({
current: 1, current: 1,
@ -119,9 +168,11 @@ const getList = (data) => {
}; };
// let bannerList = reactive(['images/ecommerce/' + 'banner.png', 'images/ecommerce/' + 'banner1.png']); // let bannerList = reactive(['images/ecommerce/' + 'banner.png', 'images/ecommerce/' + 'banner1.png']);
const onQuery = () => {
getList({ searchName: searchName.value });
};
const search = (data) => { const search = (data) => {
getList(data); getList();
}; };
const selected = (data) => { const selected = (data) => {
@ -161,11 +212,60 @@ const selected = (data) => {
background: $color-fff; background: $color-fff;
} }
} }
/* 添加伪元素占位 */ /* 添加伪元素占位 */
.goods-list::after { .goods-list::after {
content: ''; content: '';
flex: auto; flex: auto;
} }
} }
//
.filter-container {
padding: 16px 16px 6px 16px;
width: 100%;
border-radius: 16px;
text-align: left;
background: $color-fff;
//
.search-container {
display: flex;
justify-content: center;
padding: 0 16px 0 0;
.search-box {
position: relative;
width: 100%;
max-width: 600px;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid black;
border-radius: 14px;
font-size: 14px;
color: #333;
outline: none;
transition: border-color 0.3s;
}
.search-input:focus {
border-color: #409eff;
}
.search-input::placeholder {
color: #999;
}
.search-button {
position: absolute;
right: -10px;
top: 55%;
transform: translateY(-50%);
background: transparent;
border: none;
cursor: pointer;
padding: 4px;
color: #000;
}
.search-button:hover {
color: #409eff;
}
}
}
</style> </style>

View File

@ -3,7 +3,37 @@
<common current-name="supplier"> <common current-name="supplier">
<template #main> <template #main>
<!-- <banner name="supplier" :imglist="bannerList"></banner> --> <!-- <banner name="supplier" :imglist="bannerList"></banner> -->
<filtertop :list="treeList" @select="selected" @search="search"></filtertop> <!-- 原来的顶部筛选组件不好用 -->
<!-- <filtertop :list="treeList" @select="selected" @search="search"></filtertop> -->
<!-- 顶部筛选条件 -->
<div class="filter-container">
<div style="width: 40%; margin-left: 10px; margin-bottom: 10px">
<div class="search-container">
<div class="search-box">
<input v-model="searchName" type="text" placeholder="请输入农资商品名称" class="search-input" />
<button class="search-button" @click.stop="onQuery()">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
</div>
</div>
<costomTabs :list="treeList" @change="changeTabs"></costomTabs>
</div>
<div class="goods-list-warp"> <div class="goods-list-warp">
<div class="goods-list"> <div class="goods-list">
<template v-for="(n, index) in list" :key="n.id"> <template v-for="(n, index) in list" :key="n.id">
@ -53,6 +83,7 @@ let list = reactive([
// goodPrice: '25', // goodPrice: '25',
// }, // },
]); ]);
let searchName = ref('');
let params = reactive({ let params = reactive({
current: 1, current: 1,
size: 10, size: 10,
@ -65,6 +96,24 @@ let pagination = reactive({
total: 4, total: 4,
}); });
let bannerList = reactive(['images/ecommerce/' + 'banner1.png', 'images/ecommerce/' + 'banner1.png']); let bannerList = reactive(['images/ecommerce/' + 'banner1.png', 'images/ecommerce/' + 'banner1.png']);
const changeTabs = (paramsArr) => {
console.log('tabs选项', paramsArr);
let level1Id = 0;
let level2Id = 0;
let level3Id = 0;
if (paramsArr.length >= 1) {
level1Id = paramsArr[0]?.name == '全部' ? 0 : paramsArr[0]?.id || 0;
}
if (paramsArr.length >= 2) {
level2Id = paramsArr[1]?.name == '全部' ? 0 : paramsArr[1]?.id || 0;
}
if (paramsArr.length >= 3) {
level3Id = paramsArr[2]?.name == '全部' ? 0 : paramsArr[2]?.id || 0;
}
console.log('处理后的ID:', level1Id, level2Id, level3Id);
};
const getList = (data) => { const getList = (data) => {
if (data) { if (data) {
params.goodName = data.searchName; params.goodName = data.searchName;
@ -84,6 +133,9 @@ onMounted(() => {
getTree(); getTree();
}); });
const onQuery = () => {
getList({ searchName: searchName.value });
};
const search = (data) => { const search = (data) => {
getList(data); getList(data);
}; };
@ -151,4 +203,54 @@ const selected = (data) => {
flex: auto; flex: auto;
} }
} }
//
.filter-container {
padding: 16px 16px 6px 16px;
width: 100%;
border-radius: 16px;
text-align: left;
background: $color-fff;
//
.search-container {
display: flex;
justify-content: center;
padding: 0 16px 0 0;
.search-box {
position: relative;
width: 100%;
max-width: 600px;
}
.search-input {
width: 100%;
padding: 10px;
border: 1px solid black;
border-radius: 14px;
font-size: 14px;
color: #333;
outline: none;
transition: border-color 0.3s;
}
.search-input:focus {
border-color: #409eff;
}
.search-input::placeholder {
color: #999;
}
.search-button {
position: absolute;
right: -10px;
top: 55%;
transform: translateY(-50%);
background: transparent;
border: none;
cursor: pointer;
padding: 4px;
color: #000;
}
.search-button:hover {
color: #409eff;
}
}
}
</style> </style>