Commit a7784bac by Sweet Zhang

产品对比

parent e477db49
...@@ -424,4 +424,21 @@ export default { ...@@ -424,4 +424,21 @@ export default {
withdrawalList(params) { withdrawalList(params) {
return request(`${scrmUrl}/scrm-api/agCffpUserFortune/withdrawal/list`, 'POST', params) return request(`${scrmUrl}/scrm-api/agCffpUserFortune/withdrawal/list`, 'POST', params)
}, },
// 产品查询
productSearch(params){
return request(`${apiURL}/insurance_product/getAppProductList`, 'POST', params)
},
// 产品PK信息查询
getProductPKInfo(params){
return request(`${apiURL}/insurance_product/getBasicProductPKInfo`, 'POST', params)
},
// 保司查询
getInsuranceCompanyList(params){
return request(`${apiURL}/insurance_product/getInsuranceCompanyList`, 'POST', params)
},
// 数据字典查询
metaQuery(params){
return request(`${apiURL}/metadata/dropOptionsQuery`,'POST',params)
}
} }
<!-- components/ComparisonButton.vue -->
<template>
<view
class="comparison-btn"
@click="showComparisonPanel = !showComparisonPanel"
v-if="selectedProducts.length > 0"
>
<text class="count">{{ selectedProducts.length }}</text>
<uni-icons type="list" size="24" color="#fff"></uni-icons>
<!-- 已选产品面板 -->
<view class="comparison-panel" v-if="showComparisonPanel">
<view class="panel-header">
<text class="panel-title">已选对比产品</text>
<button class="clear-btn" @click="clearComparison">清空</button>
</view>
<view class="product-list">
<view class="product-item" v-for="product in selectedProducts" :key="product.id">
<image
class="product-logo"
:src="product.companyLogo"
:alt="product.companyName"
></image>
<text class="product-name">{{ product.name }}</text>
<button
class="remove-btn"
@click.stop="removeFromComparison(product.id)"
>
<uni-icons type="close" size="18" color="#fff"></uni-icons>
</button>
</view>
</view>
<button
class="compare-btn"
@click="goToComparisonResult"
:disabled="selectedProducts.length < 2"
>
去对比
</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { useProductComparison } from '@/util/useProductComparison';
// 引入对比逻辑
const {
selectedProducts,
removeFromComparison,
clearComparison,
goToComparisonResult
} = useProductComparison();
// 控制面板显示/隐藏
const showComparisonPanel = ref(false);
</script>
<style scoped>
.comparison-btn {
position: fixed;
right: 30rpx;
bottom: 150rpx;
width: 80rpx;
height: 80rpx;
background-color: #007aff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2);
z-index: 999;
}
.count {
position: absolute;
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background-color: #ff3b30;
color: #fff;
font-size: 22rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.comparison-panel {
position: absolute;
right: 0;
bottom: 90rpx;
width: 500rpx;
background-color: #fff;
border-radius: 15rpx;
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.1);
padding: 15rpx;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 15rpx;
border-bottom: 1px solid #eee;
margin-bottom: 15rpx;
}
.panel-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
.clear-btn {
font-size: 24rpx;
color: #007aff;
background-color: transparent;
padding: 0;
}
.product-list {
max-height: 300rpx;
overflow-y: auto;
margin-bottom: 20rpx;
}
.product-item {
display: flex;
align-items: center;
padding: 10rpx 0;
border-bottom: 1px solid #f5f5f5;
}
.product-logo {
width: 50rpx;
height: 50rpx;
border-radius: 5rpx;
margin-right: 10rpx;
}
.product-name {
flex: 1;
font-size: 26rpx;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.remove-btn {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background-color: #ff3b30;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.compare-btn {
width: 100%;
background-color: #007aff;
color: #fff;
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
border-radius: 10rpx;
}
.compare-btn:disabled {
background-color: #ccc;
}
</style>
\ No newline at end of file
...@@ -49,7 +49,7 @@ const config = { ...@@ -49,7 +49,7 @@ const config = {
stage, stage,
prod prod
} }
let env = 'dev'; let env = 'prod';
let baseURL = config[env].base_url; let baseURL = config[env].base_url;
let apiURL = config[env].api_url; let apiURL = config[env].api_url;
......
<template>
<view class="compare-result-page">
<view class="page-header">
<text class="page-title">产品对比</text>
<button class="toggle-btn" @click="showOnlyDiff = !showOnlyDiff">
<text>{{ showOnlyDiff ? '显示全部' : '只看不同' }}</text>
</button>
</view>
<!-- 已选产品 -->
<view class="selected-products">
<view class="product-item" v-for="(product, index) in productPKInfoList" :key="product.planBizId">
<view class="product-info">
<view class="product-name">{{ product.name }} </view>
<view class="company-name">{{ product.companyName }}</view>
<view><uni-tag :text="product.categoryName" size="small" type="primary"></uni-tag></view>
</view>
<view class="change-product" @click="navigateToPKPage">
<uni-icons type="loop" size="16" color="#007aff"></uni-icons>
<view
v-if="productPKInfoList.length > 1">更换</view>
</view>
</view>
</view>
<!-- 对比内容(按group分组展示) -->
<!-- 对比内容:从 productPKInfoList.basicInfos 提取分组渲染 -->
<view class="compare-content" v-if="productPKInfoList.length > 0">
<!-- 核心:从第一个产品的 basicInfos 中遍历分组(A/B/C组) -->
<view
class="compare-section"
v-for="group in getFirstProductGroups()"
:key="group.groupCode"
>
<view class="section-header">
<uni-icons
:type="getGroupIcon(group.groupCode)"
size="22"
:color="getGroupColor(group.groupCode)"
></uni-icons>
<text class="section-title">{{ group.groupName }}</text>
</view>
<view class="section-content">
<!-- 遍历当前分组下的所有属性(factor) -->
<view
class="compare-item"
:class="{ 'hidden-item': shouldHide(group.groupCode, factor.type) }"
v-for="factor in group.factors"
:key="factor.type"
>
<text class="item-label">{{ factor.typeName }}</text>
<view class="item-values">
<!-- 遍历所有产品,匹配当前分组+当前属性的内容 -->
<view class="item-value" v-for="product in productPKInfoList" :key="product.planBizId">
{{ getProductFactorValue(product, group.groupCode, factor.type) || '-' }}
</view>
</view>
</view>
</view>
</view>
<!-- 产品彩页分组(单独处理,从 planFiles 提取) -->
<view class="compare-section" id="product-file-group">
<view class="section-header">
<uni-icons type="image" size="22" color="#cc66ff"></uni-icons>
<text class="section-title">产品彩页</text>
</view>
<view class="section-content">
<view class="compare-item" >
<text class="item-label">产品文件</text>
<view class="item-values">
<view class="product-file-list" v-for="product in productPKInfoList" :key="product.planBizId">
<view class="no-file" v-if="!product.planFiles || product.planFiles.length === 0">
-
</view>
<view
class="file-item"
v-for="(file, idx) in (product.planFiles || [])"
:key="idx"
@click="getUrl(file.fileUrl)"
>
<uni-icons type="file-pdf" size="18" color="#cc66ff"></uni-icons>
<text class="file-name">{{ file.fileName }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 无产品时的空状态 -->
<view class="empty-state" v-else>
<uni-icons type="empty" size="60" color="#ccc"></uni-icons>
<text class="empty-text">暂无对比产品数据</text>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import common from '@/common/common';
import api from '@/api/api';
// 路由实例
const router = useRouter();
const route = useRoute();
// 后端返回的原始数据
const resultData = ref({});
// 配置分组列表(基本信息、适合人群等)
const configList = ref([]);
// 产品对比信息列表
const productPKInfoList = ref([]);
// 是否只看不同
const showOnlyDiff = ref(false);
// 查看文件的URL
const viewFileUrl = ref()
// 根据分组编码获取对应的图标
const getGroupIcon = (groupCode) => {
const iconMap = {
'A': 'info', // 基本信息
'B': 'auth', // 适合人群
'C': 'star', // 产品特色
'D': 'image' // 产品彩页
};
return iconMap[groupCode] || 'help';
};
// 根据分组编码获取对应的颜色
const getGroupColor = (groupCode) => {
const colorMap = {
'A': '#007aff', // 基本信息-蓝色
'B': '#00cc66', // 适合人群-绿色
'C': '#ff9900', // 产品特色-橙色
'D': '#cc66ff' // 产品彩页-紫色
};
return colorMap[groupCode] || '#999';
};
import {hshare} from '@/util/fiveshare';
const wxShare = (productIds,categoryId)=>{
// H5 自定义分享
const shareLink = `${window.location.origin}/myPackageA/compare-result/compare-result?categoryId=${categoryId}&productIds=${productIds}`;
// 2. 分享标题(简洁明了,包含产品数量)
const shareTitle = `多款产品对比结果`;
// 3. 分享描述(突出对比价值)
const shareDesc = `包含核心参数对比,快速了解差异`;
// 4. 分享图标(建议用公司LOGO或产品相关图标,尺寸200x200px,HTTPS地址)
const shareIcon = `${window.location.origin}/static/mypoint_pic.png`;
let data = {
title: shareTitle,
desc:shareDesc,
link: shareLink, //分享链接
imgUrl: shareIcon, //图片c
}
//安卓机型获取当前页面路径
let url = window.location.href.split('#')[0];
//ios机型获取当前页面路径
let ua = navigator.userAgent.toLowerCase();
let isWeixin = ua.indexOf('micromessenger') !== -1;
if (isWeixin) {
let isiOS = /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent); //ios终端
if (isiOS && window.sessionStorage.getItem('firstEntryUrl')) {
url = window.sessionStorage.getItem('firstEntryUrl').split('#')[0];
}
}
hshare(data, url)
}
const getUrl = (fileUrl) => {
if (!fileUrl) {
uni.showToast({ title: '暂无文档', icon: 'none' });
return;
}
// 区分环境:H5 端直接打开 URL,其他端用原下载逻辑
if (uni.getSystemInfoSync().uniPlatform === 'web') {
// H5 方案:用浏览器新窗口打开 PDF(依赖浏览器原生支持)
const opened = window.open(fileUrl, '_blank');
} else {
// 非 H5 端(小程序/APP):保留原下载+打开逻辑
uni.downloadFile({
url: fileUrl,
success: (res) => {
if (res.statusCode === 200) {
uni.openDocument({
filePath: res.tempFilePath,
showMenu: true,
success: () => console.log('打开文档成功'),
fail: (err) => {
uni.showToast({ title: '打开失败,请重试', icon: 'none' });
console.error('openDocument 失败:', err);
}
});
} else {
uni.showToast({ title: '下载失败', icon: 'none' });
}
},
fail: (err) => {
uni.showToast({ title: '下载失败,请检查网络', icon: 'none' });
console.error('downloadFile 失败:', err);
}
});
}
};
// 【核心1:获取第一个产品的分组列表(A/B/C组)】
// 所有产品的分组结构一致,取第一个产品的 basicInfos 作为分组源
const getFirstProductGroups = () => {
if (productPKInfoList.value.length === 0) return [];
return productPKInfoList.value[0].basicInfos || [];
};
// 【核心2:获取指定产品、指定分组、指定属性的内容】
const getProductFactorValue = (product, groupCode, factorType) => {
// 1. 找到产品中当前分组(如A组-基本信息)
const targetGroup = product.basicInfos.find(item => item.groupCode === groupCode);
if (!targetGroup) return '';
// 2. 找到分组中当前属性(如type=5-保障期限)
const targetFactor = targetGroup.factors.find(item => item.type === factorType);
return targetFactor ? targetFactor.content : '';
};
// 【核心3:判断属性是否所有产品都相同(“只看不同”逻辑)】
const shouldHide = (groupCode, factorType) => {
// 未开启“只看不同”或只有1个产品,不隐藏
if (!showOnlyDiff.value || productPKInfoList.value.length <= 1) return false;
// 取第一个产品的属性值作为基准
const firstProduct = productPKInfoList.value[0];
const baseValue = getProductFactorValue(firstProduct, groupCode, factorType);
// 归一化空值(避免“/”“空字符串”视为不同)
const normalize = (val) => val === '' || val === '/' ? '无数据' : val;
const baseNormalized = normalize(baseValue);
// 检查所有产品的当前属性值是否与基准一致
const allSame = productPKInfoList.value.every(product => {
const currentValue = getProductFactorValue(product, groupCode, factorType);
return normalize(currentValue) === baseNormalized;
});
// 所有产品相同则隐藏,否则显示
return allSame;
};
// 前往PK选择页
const navigateToPKPage = () => {
uni.navigateBack()
};
// 初始化数据
onMounted(() => {
const { productIds, categoryId} = route.query;
if (!productIds || !categoryId) {
uni.showToast({ title: '缺少对比参数', icon: 'none' });
return;
}
const params = {
category: categoryId,
planBizIdList: productIds.split(',')
};
let ua = navigator.userAgent.toLowerCase();
let isWeixin = ua.indexOf('micromessenger') !== -1;
api.getProductPKInfo(params).then(res => {
if (res.success && res.data?.productPKInfoList) {
productPKInfoList.value = res.data.productPKInfoList;
// 微信环境初始化分享(如有)
if (isWeixin) {
wxShare(productIds,categoryId)
}
} else {
common.errorDialog(1, res.message || '获取对比数据失败');
}
});
})
</script>
<style scoped>
/* 基础样式与之前保持一致,重点适配数据展示 */
.compare-result-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx; /* 预留底部空间 */
}
/* 页面头部 */
.page-header {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx;
background-color: #fff;
border-bottom: 1px solid #eee;
position: relative;
}
.page-title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.toggle-btn {
position: absolute;
right: 20rpx;
background: transparent;
color: #007aff;
font-size: 26rpx;
padding: 0 10rpx;
}
/* 已选产品 */
.selected-products {
background-color: #fff;
padding: 15rpx;
border-bottom: 1px solid #eee;
overflow-x: auto;
white-space: nowrap;
}
.product-item {
display: inline-flex;
align-items: flex-end;
padding: 10rpx 15rpx;
border-radius: 10rpx;
background-color: #f5f7fa;
margin-right: 15rpx;
}
.product-name {
font-size: 26rpx;
font-weight: 500;
color: #333;
margin-right: 10rpx;
max-width: 260rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.company-name {
font-size: 22rpx;
color: #666;
margin-right: 10rpx;
}
.change-product{
display: flex;
align-items: center;
font-size: 24rpx;
color: #007aff;
}
/* 对比内容 */
.compare-content {
padding: 15rpx;
}
.compare-section {
background-color: #fff;
border-radius: 15rpx;
margin-bottom: 15rpx;
overflow: hidden;
}
.section-header {
display: flex;
align-items: center;
padding: 20rpx;
background-color: #f5f7fa;
border-bottom: 1px solid #eee;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-left: 10rpx;
}
.section-content {
padding: 10rpx 0;
}
.compare-item {
display: flex;
padding: 20rpx;
border-bottom: 1px solid #eee;
}
.compare-item:last-child {
border-bottom: none;
}
.hidden-item {
display: none; /* "只看不同"时隐藏相同项 */
}
.item-label {
width: 200rpx;
font-size: 26rpx;
color: #666;
font-weight: 500;
}
.item-values {
flex: 1;
display: flex;
justify-content: space-between;
gap: 10rpx;
}
.item-value {
flex: 1;
font-size: 26rpx;
color: #333;
padding: 0 10rpx;
word-break: break-word;
line-height: 1.5; /* 多行文本时更美观 */
}
/* 产品彩页按钮样式 */
.item-value:has(.brochure-btn) {
display: flex;
justify-content: center;
align-items: center;
}
.brochure-btn {
background-color: #007aff;
color: #fff;
font-size: 24rpx;
padding: 8rpx 15rpx;
border-radius: 5rpx;
}
.brochure-btn:disabled {
background-color: #ccc;
}
.product-info{
display: flex;
flex-direction: column;
}
.mini-btn{
white-space: nowrap;
}
#product-file-group {
margin-top: 15rpx; /* 与其他分组保持间距 */
}
.product-file-list {
flex: 1;
padding: 0 10rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.file-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 6rpx 0;
color: #007aff;
font-size: 24rpx;
cursor: pointer;
word-break: break-all;
}
.no-file {
flex: 1;
padding: 0 10rpx;
color: #999;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
</style>
\ No newline at end of file
<template>
<scroll-view @scroll="handleScroll" scroll-y="true" class="scroll-Y">
<view class="product-list-page">
<!-- 顶部搜索和筛选 -->
<view class="search-bar">
<view class="search-input">
<uni-icons type="search" size="18" color="#999"></uni-icons>
<input
type="text"
placeholder="搜索产品或保险公司"
v-model="searchKeyword"
@input="handleSearch"
>
</view>
<button class="filter-btn" @click="openFilter">
<view class="iconfont icon-shaixuan"></view>
</button>
</view>
<!-- 筛选弹窗 -->
<uni-popup
type="bottom"
ref="popup"
:mask-click="false"
background-color="#fff"
class="filter-popup"
@touchmove="handlePopupTouchMove"
@open="handlePopupOpen"
@close="handlePopupClose"
>
<view class="filter-title">
<text>筛选条件</text>
<button class="close-btn" @click="closeFilter">
<uni-icons type="close" size="18"></uni-icons>
</button>
</view>
<view class="filter-content">
<!-- 保险公司筛选(改为按钮多选) -->
<view class="filter-item">
<text class="filter-label">保险公司</text>
<!-- 按钮组容器,支持横向滚动 -->
<view class="btn-group-container">
<button
class="filter-btn-item"
:class="{ 'active': isCompanySelected(company.insuranceCompanyBizId) }"
@click="toggleSelection('insurer',company.insuranceCompanyBizId)"
v-for="company in insuranceCompanies"
:key="company.insuranceCompanyBizId"
>
{{ company.name }}
</button>
</view>
</view>
<!-- 产品类型筛选(如需同步改为按钮,可参考上面的逻辑) -->
<view class="filter-item">
<text class="filter-label">产品类型</text>
<view class="btn-group-container">
<button
class="filter-btn-item"
:class="{ 'active': isProductTypeSelected(item.dropOptionCode) }"
@click="toggleSelection('productType',item.dropOptionCode)"
v-for="item in productTypes"
:key="item.dropOptionCode"
>
{{ item.dropOptionName }}
</button>
</view>
</view>
</view>
<view class="filter-actions">
<button class="reset-btn" @click="resetFilter">重置</button>
<button class="confirm-btn" @click="confirmFilter">确认筛选</button>
</view>
</uni-popup>
<!-- 产品列表 -->
<view class="product-list">
<view
class="product-item"
v-for="product in products"
:key="product.planBizId"
>
<view class="product-info" @click="navigateToDetail(product.planBizId)">
<view class="product-details">
<view class="product-name">{{ product.planName }}</view>
<view class="product-type">
<text class="type-label">险种:</text>
<text>{{ product.categoryName }}</text>
</view>
<view class="product-company">
<text class="type-label">保险公司:</text>
<text>{{ product.companyName }}</text>
</view>
<view class="product-tag">
<uni-tag :text="product.isInsured==1 ? '保融产品' : '非保融产品'" :type="product.isInsured==1 ? 'success' : 'info'" size="small"></uni-tag>
</view>
</view>
</view>
<button
class="pk-btn"
@click.stop="togglePKSelection(product)"
:disabled="!isInPKList(product.planBizId) && pkList.length >= 3"
>
{{ isInPKList(product.planBizId) ? '取消选择' : 'PK' }}
</button>
</view>
<!-- 空状态 -->
<view class="empty-state" v-if="products.length === 0">
<uni-icons type="empty" size="60" color="#ccc"></uni-icons>
<text class="empty-text">未找到匹配的产品</text>
</view>
</view>
<!-- PK选择条(新增已选产品展示和删除功能) -->
<view class="pk-bar" v-if="pkList.length > 0">
<view class="pk-selected-list">
<view class="selected-product" v-for="(item, index) in pkList" :key="item.planBizId">
<text class="product-short-name">{{ getShortName(item.planName) }}</text>
<button class="delete-btn" @click.stop="removeFromPK(index)">
<uni-icons type="close" size="16" color="#fff"></uni-icons>
</button>
</view>
</view>
<button
class="compare-btn"
@click="navigateToPKPage"
:disabled="pkList.length < 2"
>
去对比
</button>
</view>
</view>
</scroll-view>
</template>
<script setup>
import { ref, computed, onMounted,onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import api from '@/api/api';
// 路由实例
const router = useRouter();
// 搜索和筛选相关
const searchKeyword = ref('');
const selectedTypes = ref([]);
// 筛选弹窗
const popup = ref(null)
// 懒加载新增变量
const page = ref(1); // 当前页码(从1开始)
const pageSize = ref(10); // 每页条数
const loading = ref(false); // 是否正在加载
const hasMore = ref(true); // 是否还有更多数据
const isScrolling = ref(false); // 滚动节流控制
// 新增:初始化数据(首次加载第一页)
const initProducts = () => {
// 重置分页参数(如搜索/筛选时重新开始)
page.value = 1;
products.value = [];
hasMore.value = true;
fetchProducts(); // 调用接口加载数据
};
// 新增:加载产品数据(核心接口请求)
const fetchProducts = async () => {
// 防止重复请求
if (loading.value || !hasMore.value) return;
loading.value = true; // 开始加载
try {
// 构造请求参数(包含分页、搜索、筛选条件)
const params = {
companyIdList:selectedCompanies.value,
name:searchKeyword.value,
categoryList:selectedTypes.value,
paginationInfo:{
noLimitFlag:'',
number: page.value,
size:pageSize.value
}
}
const res = await api.productSearch(params); // 调用产品列表接口
if (res.success) {
const newProducts = res.data.list || [];
// 第一页直接覆盖,后续页追加
if (page.value === 1) {
products.value = newProducts;
} else {
products.value = [...products.value, ...newProducts];
}
// 判断是否还有更多数据(当前页数据小于页大小,说明没有更多)
hasMore.value = newProducts.length >= pageSize.value;
// 页码自增(准备下一页)
page.value++;
} else {
uni.showToast({ title: res.message || '加载失败', icon: 'none' });
}
} catch (err) {
console.error('产品列表请求失败:', err);
uni.showToast({ title: '网络错误', icon: 'none' });
} finally {
loading.value = false; // 结束加载
}
};
// 新增:监听页面滚动(判断是否到达底部)
const handleScroll = (e) => {
// 节流:避免滚动事件触发过于频繁
if (isScrolling.value) return;
isScrolling.value = true;
setTimeout(() => {
isScrolling.value = false;
}, 300);
// 获取设备系统信息(包含可视区域高度)
const sysInfo = uni.getSystemInfoSync();
const windowHeight = ref()
// 关键:windowHeight 是设备可视区域高度(px),不含导航栏/底部栏
windowHeight.value = sysInfo.windowHeight;
// 获取滚动相关参数
const { scrollTop, scrollHeight } = e.detail;
// 计算是否到达底部(距离底部200rpx时触发加载)
if (scrollHeight - scrollTop - windowHeight.value <= 200) {
fetchProducts(); // 加载更多
}
};
// 打开筛选弹窗
const openFilter = ()=>{
if(popup.value) popup.value.open('bottom')
}
// 关闭筛选弹窗
const closeFilter = ()=>{
if(popup.value) popup.value.close()
}
// 1. 存储选中的保险公司ID(与原逻辑兼容,无需修改筛选核心逻辑)
const selectedCompanies = ref([]);
const selectedProducts = ref([]);
// 2. 检查保险公司是否被选中
const isCompanySelected = (companyId) => {
return selectedCompanies.value.includes(companyId.toString());
};
const isProductTypeSelected = (code)=>{
return selectedTypes.value.includes(code.toString())
}
// 3. 切换保险公司选中状态(点击按钮时触发)
const toggleSelection = (type,id) => {
const idStr = id.toString();
let index;
if(type==='insurer'){
index = selectedCompanies.value.findIndex(id => id === idStr);
if (index > -1) {
// 已选中,取消选择
selectedCompanies.value.splice(index, 1);
} else {
// 未选中,添加选择
selectedCompanies.value.push(idStr);
}
}else if(type==='productType'){
index = selectedTypes.value.findIndex(id => id === idStr);
if (index > -1) {
// 已选中,取消选择
selectedTypes.value.splice(index, 1);
} else {
// 未选中,添加选择
selectedTypes.value.push(idStr);
}
}
};
// 4. 重置筛选时,清空选中的保险公司(原逻辑不变,自动生效)
const resetFilter = () => {
selectedCompanies.value = []; // 清空选中的保险公司
selectedTypes.value = [];
searchKeyword.value = '';
};
// PK列表相关
const pkList = ref([]);
const maxPKCount = 3;
// 保险公司
const insuranceCompanies = ref([]);
// 产品类型
const productTypes = ref([]);
// 产品列表
const products = ref([]);
// 检查产品是否已在PK列表中
const isInPKList = (productId) => {
return pkList.value.some(item => item.planBizId === productId);
};
// 新增:切换PK选择状态(添加/取消)
const togglePKSelection = (product) => {
const isSelected = isInPKList(product.planBizId);
if (isSelected) {
// 取消选择
removeFromPKByProductId(product.planBizId);
uni.showToast({
title: '已取消选择',
icon: 'none',
duration: 1500
});
} else {
// 添加选择
if (pkList.value.length < maxPKCount) {
// 检查是否同险种(新增:PK只能选择同险种产品)
if (pkList.value.length > 0 && pkList.value[0].category !== product.category) {
uni.showToast({
title: '只能选择同险种产品对比',
icon: 'none',
duration: 1500
});
return;
}
pkList.value.push(product);
uni.showToast({
title: '已添加到对比',
icon: 'none',
duration: 1500
});
}
}
};
// 新增:通过索引从PK列表移除产品
const removeFromPK = (index) => {
pkList.value.splice(index, 1);
uni.showToast({
title: '已取消选择',
icon: 'none',
duration: 1500
});
};
// 新增:通过产品ID从PK列表移除产品
const removeFromPKByProductId = (productId) => {
pkList.value = pkList.value.filter(item => item.planBizId !== productId);
};
// 新增:获取产品简称(避免PK条显示过长)
const getShortName = (fullName) => {
// 截取前6个字符,超出部分用省略号代替
return fullName.length > 6 ? fullName.slice(0, 6) + '...' : fullName;
};
// 处理搜索
const handleSearch = (e) => {
// 防抖:输入停止300ms后再请求(避免频繁请求)
clearTimeout(handleSearch.timer);
handleSearch.timer = setTimeout(() => {
initProducts(); // 重置分页并加载
}, 300);
};
// 确认筛选
const confirmFilter = () => {
// 重置分页并重新加载
initProducts();
closeFilter();
// 筛选逻辑已在computed中处理,自动更新列表
};
// 导航到产品详情
const navigateToDetail = (productId) => {
};
// 导航到PK结果页面
const navigateToPKPage = () => {
uni.navigateTo({
url:`/myPackageA/compare-result/compare-result?categoryId=${pkList.value[0].category}&productIds=${pkList.value.map(item => item.planBizId).join(',')}`
})
};
const fetchMeta = ()=>{
const params = {
code: "insurance_category"
}
api.metaQuery(params).then(res=>{
// 险种信息insurance_category
productTypes.value = res['data']['dropMasterInfoList'][0]['dropOptionsInfoList'] || []
})
}
const getInsuranceCompanyList = ()=>{
const params = {
insuranceCompanyBizId: '',
name: ''
}
api.getInsuranceCompanyList(params).then(res=>{
insuranceCompanies.value = res['data']['list'] || []
})
}
// 初始化产品数据
onMounted(() => {
// 1. 重置滚动位置
uni.pageScrollTo({ scrollTop: 0, duration: 0 });
// 3. 初始化数据(加载第一页)
initProducts();
// 加载筛选数据(保险公司、产品类型)
fetchMeta()
getInsuranceCompanyList()
});
const handlePopupTouchMove = (e) => {
// 1. 阻止事件冒泡(不传递给底部页面)
e.stopPropagation();
// 2. 可选:限制弹窗内只能垂直滚动(防止横向滚动影响)
const touch = e.touches[0] || e.changedTouches[0];
const scrollView = e.currentTarget.querySelector('.filter-content'); // 弹窗内可滚动容器
if (scrollView) {
const { scrollTop, scrollHeight, clientHeight } = scrollView;
// 顶部边界:已滚动到顶部,且继续向上拉 → 阻止默认滚动
const isTop = scrollTop <= 0 && touch.clientY > e.currentTarget.dataset.lastY;
// 底部边界:已滚动到底部,且继续向下拉 → 阻止默认滚动
const isBottom = scrollTop >= scrollHeight - clientHeight && touch.clientY < e.currentTarget.dataset.lastY;
if (isTop || isBottom) {
e.preventDefault(); // 阻止默认滚动行为(避免顶部/底部拖拽时带动外部页面)
}
// 记录当前触摸位置,用于下次判断边界
e.currentTarget.dataset.lastY = touch.clientY;
}
};
// 关键2:弹窗打开时,锁定底部页面滚动
const handlePopupOpen = () => {
// 获取底部页面容器(通常是根元素或 .product-list-page)
const pageContainer = document.querySelector('.product-list-page');
if (pageContainer) {
// 1. 禁止页面滚动(通过 CSS 定位固定,避免滚动)
pageContainer.style.position = 'fixed';
pageContainer.style.width = '100%';
// 2. 记录当前滚动位置,关闭弹窗时恢复
pageContainer.dataset.scrollTop = window.scrollY || document.documentElement.scrollTop;
}
};
// 关键3:弹窗关闭时,恢复底部页面滚动
const handlePopupClose = () => {
const pageContainer = document.querySelector('.product-list-page');
if (pageContainer) {
// 1. 恢复页面定位
pageContainer.style.position = '';
pageContainer.style.width = '';
// 2. 恢复到弹窗打开前的滚动位置
const scrollTop = pageContainer.dataset.scrollTop || 0;
window.scrollTo({ top: scrollTop, behavior: 'smooth' });
}
};
// 生命周期:页面卸载时移除监听(避免内存泄漏)
onUnmounted(() => {
clearTimeout(handleSearch.timer); // 清除搜索防抖定时器
});
</script>
<style scoped>
.product-list-page {
background-color: #f5f5f5;
min-height: calc(100vh - 200rpx); /* 根据实际布局调整 */
padding-bottom: 120rpx;
}
/* 搜索栏样式 */
.search-bar {
display: flex;
padding: 10rpx 20rpx;
background-color: #fff;
align-items: center;
}
.search-input {
flex: 1;
display: flex;
align-items: center;
background-color: #f5f5f5;
border-radius: 30rpx;
padding: 12rpx 20rpx;
}
.search-input input {
margin-left: 10rpx;
flex: 1;
font-size: 28rpx;
color: #333;
}
.filter-btn {
width: 80rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
padding: 0;
margin-left: 10rpx;
}
/* 筛选弹窗样式 */
.filter-popup {
height: 80vh;
border-top-left-radius: 30rpx;
border-top-right-radius: 30rpx;
}
.filter-title {
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx;
border-bottom: 1px solid #eee;
position: relative;
}
.filter-title text {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.close-btn {
position: absolute;
right: 20rpx;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
padding: 0;
}
.filter-content {
padding: 20rpx;
height: calc(80vh - 180rpx);
overflow-y: auto;
padding-bottom: 100rpx;
}
.filter-item {
margin-bottom: 30rpx;
}
.filter-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 15rpx;
font-weight: 500;
}
.checkbox-item {
display: inline-block;
margin-right: 20rpx;
margin-bottom: 15rpx;
/* 调整checkbox标签样式 */
white-space: nowrap;
}
.filter-actions {
display: flex;
padding: 20rpx;
border-top: 1px solid #eee;
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
}
.reset-btn {
flex: 1;
height: 80rpx;
background-color: #f5f5f5;
color: #666;
border-radius: 40rpx;
margin-right: 20rpx;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
.confirm-btn {
flex: 1;
height: 80rpx;
background-color: #007aff;
color: #fff;
border-radius: 40rpx;
font-size: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 产品列表样式 */
.product-list {
padding: 20rpx;
}
.product-item {
display: flex;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 20rpx;
justify-content: space-between;
align-items: center;
}
.product-details {
flex: 1;
}
.product-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.product-type, .product-company {
font-size: 26rpx;
color: #666;
margin-bottom: 5rpx;
}
.type-label {
color: #999;
}
.product-tag {
margin-top: 10rpx;
}
.pk-btn {
width: 150rpx;
height: 60rpx;
font-size: 26rpx;
background-color: #007aff;
color: #fff;
border-radius: 30rpx;
padding: 0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
/* 取消选择按钮样式 */
.pk-btn:not(:disabled):hover {
opacity: 0.9;
}
.pk-btn:disabled {
background-color: #ccc;
color: #fff;
}
/* 空状态样式 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
/* PK条样式(新增已选产品样式) */
.pk-bar {
display: flex;
align-items: center;
padding: 15rpx 30rpx;
background-color: #fff;
border-top: 1px solid #eee;
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
gap: 20rpx;
}
.pk-selected-list {
flex: 1;
display: flex;
gap: 10rpx;
overflow-x: auto;
padding: 5rpx 0;
}
.selected-product {
display: flex;
align-items: center;
background-color: #f5f7fa;
border-radius: 30rpx;
padding: 5rpx 15rpx;
height: 50rpx;
position: relative;
}
.product-short-name {
font-size: 24rpx;
color: #333;
margin-right: 25rpx;
}
.delete-btn {
position: absolute;
right: 5rpx;
top: 50%;
transform: translateY(-50%);
width: 30rpx;
height: 30rpx;
border-radius: 50%;
background-color: #ff3b30;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.compare-btn {
min-width: 200rpx;
height: 70rpx;
background-color: #007aff;
color: #fff;
border-radius: 35rpx;
font-size: 28rpx;
padding: 0;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
}
.compare-btn:disabled {
background-color: #ccc;
}
/* 1. 按钮组容器:支持自动换行,避免按钮过多溢出 */
.btn-group-container {
display: flex;
gap: 15rpx;
padding: 5rpx 0;
flex-wrap: wrap; /* 自动换行 */
justify-content: flex-start !important; /* 强制左对齐,优先级最高 */
align-items: flex-start;
width: 100%; /* 确保容器占满宽度 */
margin: 0 !important; /* 清除可能的自动外边距 */
text-align: left !important; /* 额外保险,防止文本居中影响 */
}
/* 2. 筛选按钮样式:未选中状态 */
.filter-btn-item {
min-width: 150rpx;
height: 60rpx;
background-color: #f5f7fa;
color: #333;
font-size: 26rpx;
border-radius: 30rpx;
padding: 0 20rpx;
border: none;
white-space: nowrap;
/* 确保按钮本身不会居中 */
margin: 0 !important;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* 3. 筛选按钮样式:选中状态(突出显示) */
.filter-btn-item.active {
background-color: #007aff;
color: #fff;
}
/* 加载中状态 */
.loading-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 0;
color: #999;
font-size: 26rpx;
}
/* 没有更多数据 */
.no-more {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 0;
color: #999;
font-size: 26rpx;
}
.scroll-Y {
height: 100vh /* 根据实际布局调整 */
}
</style>
\ No newline at end of file
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"name": "验证码输入框", "name": "验证码输入框",
"version": "2.0", "version": "2.0",
"dependencies": { "dependencies": {
"@dcloudio/uni-ui": "^1.5.10", "@dcloudio/uni-ui": "^1.5.11",
"@uqrcode/js": "^4.0.7", "@uqrcode/js": "^4.0.7",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
...@@ -27,9 +27,10 @@ ...@@ -27,9 +27,10 @@
} }
}, },
"node_modules/@dcloudio/uni-ui": { "node_modules/@dcloudio/uni-ui": {
"version": "1.5.10", "version": "1.5.11",
"resolved": "https://registry.npmmirror.com/@dcloudio/uni-ui/-/uni-ui-1.5.10.tgz", "resolved": "https://registry.npmmirror.com/@dcloudio/uni-ui/-/uni-ui-1.5.11.tgz",
"integrity": "sha512-v6ylkGSUF6hhgSerm8aVEQE9SBkKz3oNDzorkVC0KLHfulMbkacJQ92YFSQ0kCGeudupVqXuPwNTFjKJF5Qolw==" "integrity": "sha512-DBtk046ofmeFd82zRI7d89SoEwrAxYzUN3WVPm1DIBkpLPG5F5QDNkHMnZGu2wNrMEmGBjBpUh3vqEY1L3jaMw==",
"license": "Apache-2.0"
}, },
"node_modules/@uqrcode/js": { "node_modules/@uqrcode/js": {
"version": "4.0.7", "version": "4.0.7",
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@dcloudio/uni-ui": "^1.5.10", "@dcloudio/uni-ui": "^1.5.11",
"@uqrcode/js": "^4.0.7", "@uqrcode/js": "^4.0.7",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
......
...@@ -575,6 +575,16 @@ ...@@ -575,6 +575,16 @@
"style": { "style": {
"navigationBarTitleText": "提现记录" "navigationBarTitleText": "提现记录"
} }
},{
"path": "compare-result/compare-result",
"style": {
"navigationBarTitleText": "对比结果"
}
},{
"path": "product-list/product-list",
"style": {
"navigationBarTitleText": "产品对比"
}
} }
] ]
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
</view> </view>
<!-- Tab对应的PDF内容(无权限时显示空状态) --> <!-- Tab对应的PDF内容(无权限时显示空状态) -->
<view class="pdf-tab-content"> <view class="pdf-tab-content" v-if="currentPdf.type==='showURL'">
<template v-if="filteredCurrentTabs.length > 0"> <template v-if="filteredCurrentTabs.length > 0">
<web-view <web-view
:src="getPdfViewerUrl(currentPdf.url)" :src="getPdfViewerUrl(currentPdf.url)"
...@@ -49,6 +49,10 @@ ...@@ -49,6 +49,10 @@
<view class="pdf-empty">暂无访问权限</view> <view class="pdf-empty">暂无访问权限</view>
</template> </template>
</view> </view>
<!-- 显示产品搜索组件 -->
<view class="pdf-tab-content" v-if="currentPdf.type==='showPage'">
<productListVue/>
</view>
</view> </view>
<!-- 错误状态 --> <!-- 错误状态 -->
...@@ -63,6 +67,7 @@ ...@@ -63,6 +67,7 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad } from '@dcloudio/uni-app';
import api from "@/api/api"; import api from "@/api/api";
import productListVue from '@/myPackageA/product-list/product-list.vue';
// ========================== 类型定义 ========================== // ========================== 类型定义 ==========================
// PDF文件类型 // PDF文件类型
...@@ -70,6 +75,7 @@ interface PdfItem { ...@@ -70,6 +75,7 @@ interface PdfItem {
id: string; id: string;
title: string; title: string;
url: string; // PDF文件路径 url: string; // PDF文件路径
type:string;
} }
// 权限数据类型(与接口返回结构对齐) // 权限数据类型(与接口返回结构对齐)
...@@ -84,7 +90,6 @@ interface PermissionItem { ...@@ -84,7 +90,6 @@ interface PermissionItem {
// ========================== 基础配置 ========================== // ========================== 基础配置 ==========================
// OSS基础路径(根据实际OSS地址修改) // OSS基础路径(根据实际OSS地址修改)
const OSS_BASE_URL = 'https://csf-doc-center.oss-cn-shanghai-finance-1-pub.aliyuncs.com'; const OSS_BASE_URL = 'https://csf-doc-center.oss-cn-shanghai-finance-1-pub.aliyuncs.com';
// 接收URL参数的type(默认1:公司介绍) // 接收URL参数的type(默认1:公司介绍)
const currentType = ref<number>(1); const currentType = ref<number>(1);
// 加载状态 // 加载状态
...@@ -92,7 +97,7 @@ const loading = ref(true); ...@@ -92,7 +97,7 @@ const loading = ref(true);
// 当前激活的Tab索引 // 当前激活的Tab索引
const activeTab = ref(0); const activeTab = ref(0);
// 当前显示的PDF信息 // 当前显示的PDF信息
const currentPdf = ref<PdfItem>({ id: '', title: '', url: '' }); const currentPdf = ref<PdfItem>({ id: '', title: '', url: '', type:''});
// 接口返回的权限列表 // 接口返回的权限列表
const permissionList = ref<PermissionItem[]>([]); const permissionList = ref<PermissionItem[]>([]);
...@@ -100,24 +105,25 @@ const permissionList = ref<PermissionItem[]>([]); ...@@ -100,24 +105,25 @@ const permissionList = ref<PermissionItem[]>([]);
const companyPdf = ref<PdfItem>({ const companyPdf = ref<PdfItem>({
id: 'company', id: 'company',
title: '公司介绍', title: '公司介绍',
url: `${OSS_BASE_URL}/public/company-intro.pdf` url: `${OSS_BASE_URL}/public/company-intro.pdf`,
type:'showURL'
}); });
// 所有模块的原始Tab配置(未过滤权限) // 所有模块的原始Tab配置(未过滤权限)
const rawTabConfig = ref<Record<number, PdfItem[]>>({ const rawTabConfig = ref<Record<number, PdfItem[]>>({
2: [ // type=2:案例分享(对应权限key=cases) 2: [ // type=2:案例分享(对应权限key=cases)
{ id: 'case1', title: '规划案例', url: `${OSS_BASE_URL}/cases/planning.pdf` }, { id: 'case1', title: '规划案例', url: `${OSS_BASE_URL}/cases/planning.pdf`,type:'showURL' },
{ id: 'case2', title: '团财案例', url: `${OSS_BASE_URL}/cases/CorporateServiceDeliveryCase-GroupandPropertyInsurance.pdf` } { id: 'case2', title: '团财案例', url: `${OSS_BASE_URL}/cases/CorporateServiceDeliveryCase-GroupandPropertyInsurance.pdf`,type:'showURL' }
], ],
3: [ // type=3:产品分析(对应权限key=products) 3: [ // type=3:产品分析(对应权限key=products)
{ id: 'product2', title: '港险分析', url: `${OSS_BASE_URL}/products/hk-analysis.pdf` }, { id: 'product2', title: '港险分析', url: `${OSS_BASE_URL}/products/hk-analysis.pdf`,type:'showURL' },
{ id: 'product1', title: '港险明细', url: `${OSS_BASE_URL}/products/2025Q3-hk-detail.pdf` }, { id: 'product1', title: '港险明细', url: `${OSS_BASE_URL}/products/2025Q3-hk-detail.pdf`,type:'showURL' },
{ id: 'product3', title: '产品搜索', url: '' }, { id: 'product3', title: '产品对比', url: '',type:'showPage' },
], ],
4: [ // type=4:制度(对应权限key=policies) 4: [ // type=4:制度(对应权限key=policies)
{ id: 'policy1', title: '个险政策', url: `${OSS_BASE_URL}/policies/individual-policy.pdf` }, { id: 'policy1', title: '个险政策', url: `${OSS_BASE_URL}/policies/individual-policy.pdf`,type:'showURL' },
{ id: 'policy2', title: '家办政策', url: `${OSS_BASE_URL}/policies/group-policy.pdf` }, { id: 'policy2', title: '家办政策', url: `${OSS_BASE_URL}/policies/group-policy.pdf`,type:'showURL' },
{ id: 'policy3', title: '介绍费政策', url: `${OSS_BASE_URL}/policies/commission-policy.pdf` } { id: 'policy3', title: '介绍费政策', url: `${OSS_BASE_URL}/policies/commission-policy.pdf`,type:'showURL' }
] ]
}); });
...@@ -132,6 +138,13 @@ const typeToPermissionKey = ref<Record<number, string>>({ ...@@ -132,6 +138,13 @@ const typeToPermissionKey = ref<Record<number, string>>({
onLoad((query) => { onLoad((query) => {
// 1. 解析URL中的type参数(默认1) // 1. 解析URL中的type参数(默认1)
currentType.value = Number(query.type) || 1; currentType.value = Number(query.type) || 1;
const index = uni.getStorageSync('tabsIndex');
setTimeout(()=>{
if(index){
switchTab(index)
}
},300)
// 2. 查询用户信息和权限 // 2. 查询用户信息和权限
queryUserInfoAndPermission(); queryUserInfoAndPermission();
}); });
...@@ -182,7 +195,7 @@ const initPdfAfterPermission = () => { ...@@ -182,7 +195,7 @@ const initPdfAfterPermission = () => {
currentPdf.value = filteredTabs[0]; currentPdf.value = filteredTabs[0];
} else { } else {
// 无权限:清空当前PDF // 无权限:清空当前PDF
currentPdf.value = { id: '', title: '', url: '' }; currentPdf.value = { id: '', title: '', url: '',type:'' };
} }
} }
...@@ -232,7 +245,8 @@ const switchTab = (index: number) => { ...@@ -232,7 +245,8 @@ const switchTab = (index: number) => {
setTimeout(() => { setTimeout(() => {
activeTab.value = index; activeTab.value = index;
currentPdf.value = tabs[index]; currentPdf.value = tabs[index];
uni.setStorageSync('tabsIndex', index);
// 延迟关闭加载提示(给WebView启动时间) // 延迟关闭加载提示(给WebView启动时间)
setTimeout(() => { setTimeout(() => {
uni.hideLoading(); uni.hideLoading();
......
## 2.1.2(2025-08-19)
- 修复 传入数字 0 不显示的问题
## 2.1.1(2024-03-20)
- 优化 app下边框过窄导致不显示的bug
## 2.1.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-tag](https://uniapp.dcloud.io/component/uniui/uni-tag)
## 2.0.0(2021-11-09)
- 新增 提供组件设计资源,组件样式调整
- 移除 插槽
- 移除 type 属性的 royal 选项
## 1.1.1(2021-08-11)
- type 不是 default 时,size 为 small 字体大小显示不正确
## 1.1.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.7(2021-06-18)
- 修复 uni-tag 在字节跳动小程序上 css 类名编译错误的 bug
## 1.0.6(2021-06-04)
- 修复 未定义 sass 变量 "$uni-color-royal" 的bug
## 1.0.5(2021-05-10)
- 修复 royal 类型无效的bug
- 修复 uni-tag 宽度不自适应的bug
- 新增 uni-tag 支持属性 custom-style 自定义样式
## 1.0.4(2021-02-05)
- 调整为uni_modules目录规范
<template>
<text class="uni-tag" v-if="showTag" :class="classes" :style="customStyle" @click="onClick">{{text}}</text>
</template>
<script>
/**
* Tag 标签
* @description 用于展示1个或多个文字标签,可点击切换选中、不选中的状态
* @tutorial https://ext.dcloud.net.cn/plugin?id=35
* @property {String} text 标签内容
* @property {String} size = [default|small|mini] 大小尺寸
* @value default 正常
* @value small 小尺寸
* @value mini 迷你尺寸
* @property {String} type = [default|primary|success|warning|error] 颜色类型
* @value default 灰色
* @value primary 蓝色
* @value success 绿色
* @value warning 黄色
* @value error 红色
* @property {Boolean} disabled = [true|false] 是否为禁用状态
* @property {Boolean} inverted = [true|false] 是否无需背景颜色(空心标签)
* @property {Boolean} circle = [true|false] 是否为圆角
* @event {Function} click 点击 Tag 触发事件
*/
export default {
name: "UniTag",
emits: ['click'],
props: {
type: {
// 标签类型default、primary、success、warning、error、royal
type: String,
default: "default"
},
size: {
// 标签大小 normal, small
type: String,
default: "normal"
},
// 标签内容
text: {
type: String,
default: ""
},
disabled: {
// 是否为禁用状态
type: [Boolean, String],
default: false
},
inverted: {
// 是否为空心
type: [Boolean, String],
default: false
},
circle: {
// 是否为圆角样式
type: [Boolean, String],
default: false
},
mark: {
// 是否为标记样式
type: [Boolean, String],
default: false
},
customStyle: {
type: String,
default: ''
}
},
computed: {
showTag() {
return !!this.text.toString()
},
classes() {
const {
type,
disabled,
inverted,
circle,
mark,
size,
isTrue
} = this
const classArr = [
'uni-tag--' + type,
'uni-tag--' + size,
isTrue(disabled) ? 'uni-tag--disabled' : '',
isTrue(inverted) ? 'uni-tag--' + type + '--inverted' : '',
isTrue(circle) ? 'uni-tag--circle' : '',
isTrue(mark) ? 'uni-tag--mark' : '',
// type === 'default' ? 'uni-tag--default' : 'uni-tag-text',
isTrue(inverted) ? 'uni-tag--inverted uni-tag-text--' + type : '',
size === 'small' ? 'uni-tag-text--small' : ''
]
// 返回类的字符串,兼容字节小程序
return classArr.join(' ')
}
},
methods: {
isTrue(value) {
return value === true || value === 'true'
},
onClick() {
if (this.isTrue(this.disabled)) return
this.$emit("click");
}
}
};
</script>
<style lang="scss" scoped>
$uni-primary: #2979ff !default;
$uni-success: #18bc37 !default;
$uni-warning: #f3a73f !default;
$uni-error: #e43d33 !default;
$uni-info: #8f939c !default;
$tag-default-pd: 4px 7px;
$tag-small-pd: 2px 5px;
$tag-mini-pd: 1px 3px;
.uni-tag {
line-height: 14px;
font-size: 12px;
font-weight: 200;
padding: $tag-default-pd;
color: #fff;
border-radius: 3px;
background-color: $uni-info;
border-width: 1rpx;
border-style: solid;
border-color: $uni-info;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
// size attr
&--default {
font-size: 12px;
}
&--default--inverted {
color: $uni-info;
border-color: $uni-info;
}
&--small {
padding: $tag-small-pd;
font-size: 12px;
border-radius: 2px;
}
&--mini {
padding: $tag-mini-pd;
font-size: 12px;
border-radius: 2px;
}
// type attr
&--primary {
background-color: $uni-primary;
border-color: $uni-primary;
color: #fff;
}
&--success {
color: #fff;
background-color: $uni-success;
border-color: $uni-success;
}
&--warning {
color: #fff;
background-color: $uni-warning;
border-color: $uni-warning;
}
&--error {
color: #fff;
background-color: $uni-error;
border-color: $uni-error;
}
&--primary--inverted {
color: $uni-primary;
border-color: $uni-primary;
}
&--success--inverted {
color: $uni-success;
border-color: $uni-success;
}
&--warning--inverted {
color: $uni-warning;
border-color: $uni-warning;
}
&--error--inverted {
color: $uni-error;
border-color: $uni-error;
}
&--inverted {
background-color: #fff;
}
// other attr
&--circle {
border-radius: 15px !important;
}
&--mark {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
border-top-right-radius: 15px !important;
border-bottom-right-radius: 15px !important;
}
&--disabled {
opacity: 0.5;
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
}
.uni-tag-text {
color: #fff;
font-size: 14px;
&--primary {
color: $uni-primary;
}
&--success {
color: $uni-success;
}
&--warning {
color: $uni-warning;
}
&--error {
color: $uni-error;
}
&--small {
font-size: 12px;
}
}
</style>
\ No newline at end of file
{
"id": "uni-tag",
"displayName": "uni-tag 标签",
"version": "2.1.2",
"description": "Tag 组件,用于展示1个或多个文字标签,可点击切换选中、不选中的状态。",
"keywords": [
"uni-ui",
"uniui",
"",
"tag",
"标签"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "",
"uni-app": "^4.07",
"uni-app-x": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [
"uni-scss"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": "√",
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "√",
"lark": "-"
},
"quickapp": {
"huawei": "√",
"union": "√"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
}
}
\ No newline at end of file
## Tag 标签
> **组件名:uni-tag**
> 代码块: `uTag`
用于展示1个或多个文字标签,可点击切换选中、不选中的状态 。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-tag)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
// composables/useProductComparison.js
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
export function useProductComparison() {
// 存储已选择的对比产品
const selectedProducts = ref([]);
// 最大可选产品数量
const maxCount = 3;
const router = useRouter();
// 检查是否已选择该产品
const isSelected = (productId) => {
return selectedProducts.value.some(item => item.id === productId);
};
// 添加产品到对比列表
const addToComparison = (product) => {
if (selectedProducts.value.length >= maxCount) {
uni.showToast({
title: `最多只能选择${maxCount}个产品进行对比`,
icon: 'none'
});
return false;
}
// 检查是否同险种
if (selectedProducts.value.length > 0) {
const firstType = selectedProducts.value[0].typeId;
if (product.typeId !== firstType) {
uni.showToast({
title: '只能选择同险种产品进行对比',
icon: 'none'
});
return false;
}
}
if (!isSelected(product.id)) {
selectedProducts.value.push(product);
return true;
}
return false;
};
// 从对比列表移除产品
const removeFromComparison = (productId) => {
selectedProducts.value = selectedProducts.value.filter(
item => item.id !== productId
);
};
// 清空对比列表
const clearComparison = () => {
selectedProducts.value = [];
};
// 跳转到对比结果页
const goToComparisonResult = () => {
if (selectedProducts.value.length < 2) {
uni.showToast({
title: '至少选择2个产品才能进行对比',
icon: 'none'
});
return;
}
const productIds = selectedProducts.value.map(item => item.id).join(',');
router.push({
path: '/pages/compare-result/compare-result',
query: { productIds }
});
};
return {
selectedProducts,
maxCount,
isSelected,
addToComparison,
removeFromComparison,
clearComparison,
goToComparisonResult
};
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment