父组件代码:
<template>
<view>
<view v-for="(item,index) in stockList" :key="index">
<view class="s-title">{{item.typeName}}</view>
<view class="s-list">
<view :class="['s-item', i.isClick?'s-item-active':'']" v-for="(i,idx) in item.childNode" :key="idx" @click="itemClick(i)">
{{i.name}}{{item.isClick}}
<view class="s-item-close" v-if="i.isClick" @click.stop="cancelSelect(i)">
<image src="/static/images/close.png" mode=""></image>
</view>
</view>
</view>
</view>
<view style="height: 150rpx;"></view>
<view class="footer-bar">
<view class="b-left">已选择<text>{{total}}</text>个条件</view>
<view :class="['b-right',total == 0 ?'b-right-active':'']">执行选股</view>
</view>
<!-- 弹框组件 -->
<view>
<u-popup :show="popupShow" mode="bottom" :round="10" @close="closePopup" class="popup">
<selection-range ref="selectionRange" :select-item="selectItem" :crArr="crArr" :checkedList="checkedList" :marketCheckedList="marketCheckedList" @cancelPopup="closePopup" @getSelectValue="getSelectValue">
</selection-range>
</u-popup>
</view>
</view>
</template>
<script>
import selectionRange from './selection-range.vue'
import {
querySelectStockList
} from '@/api/selectStock.js' // 后端接口
import {
signalrsOn,
signalrInvoke
} from "@/util/signalR.js"; // 获取订阅接口
export default {
components: {
selectionRange
},
data() {
return {
stockList: [],
popupShow: false,
selectItem: {},
symbolArr: [], // 市场订阅所需参数数组
crArr: [], // 涨跌幅
checkedList: [], //行业选中值
marketCheckedList: [], // 市场选中值
total:0, // 选中条件个数
};
},
onShow() {
this.total = 0
this.marketCheckedList = []
this.checkedList= []
this.getSelectStockList() // 获取按钮列表
},
methods: {
// 按钮点击事件
itemClick(item) {
this.popupShow = true
this.selectItem = item
if (item.plateTreeID == 10) {
this.symbolArr = []
if (item.plateId != 1001) {
item.childNode.forEach(res => {
this.symbolArr.push(res.code)
})
signalrInvoke("Quotation", "Subscribe", { // 订阅行情
QuotationType: "PlateIndexQuotationInfo",
SearchParamData: {
Symbols: this.symbolArr.join(','),
},
});
this.crArr = []
signalrsOn('SubscribeReturn', (res) => { // 接收行情数据
let obj = {
cr: res.data.cr,
plateId: res.data.plateid
}
this.crArr.push(obj)
})
}
}
},
// 获取选股列表
getSelectStockList() {
querySelectStockList().then(res => {
if (res.isSuccess == true) {
this.stockList = res.resultData
}
})
},
// 获取行业与市场选中值
getSelectValue(value) {
if(value == 2) {
//不用执行
} else if(value.length > 0) {
if (this.selectItem.plateID != 1001) {
this.checkedList = value
} else {
this.marketCheckedList = value
}
this.popupShow = false
} else {
this.popupShow = true
}
// 计算总数
this.total = 0
this.stockList.forEach(item=>{
item.childNode.forEach(i=>{
if(i.isClick) {
this.total++
}
})
})
},
// 关闭弹框
closePopup() {
this.popupShow = false
},
// 点击按钮取消选中
cancelSelect(item) {
item.isClick = false
this.getSelectValue(2)//计算数量
if(item.typeCode == 4) {
item.childNode.forEach(item =>{
item.isChecked = false
})
} else if(item.typeCode == 3) {
item.selectMinValue = 0
item.selectMaxValue = 0
} else if(item.typeCode == 7) {
this.marketCheckedList = [] // 市场选中值
} else if(item.typeCode == 8) {
this.checkedList= [] //行业选中值
}
this.$forceUpdate() // 更新
}
}
}
</script>
<style lang="less">
page {
padding-left: 30rpx;
line-height: 1;
}
.s-title {
font-size: 30rpx;
font-weight: bold;
color: #333333;
margin-top: 44rpx;
}
.s-list {
display: flex;
flex-wrap: wrap;
margin-top: 6rpx;
.s-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #333333;
width: 216rpx;
height: 78rpx;
background: #EEEEEE;
border-radius: 4rpx;
margin: 24rpx 22rpx 0 0;
padding: 10rpx;
.s-item-close {
position: absolute;
right: 0;
top: 0;
width: 32rpx;
height: 26rpx;
background: #2F569E;
border-radius: 0 0 0 10rpx;
display: flex;
align-items: center;
justify-content: center;
image {
width: 16rpx;
height: 16rpx;
}
}
}
.s-item-active {
background: rgba(47, 86, 158, 0.16);
border: 1rpx solid #2F569E;
}
}
.footer-bar {
display: flex;
position: fixed;
bottom: 0;
left: 0;
width: 750rpx;
height: 102rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
box-sizing: content-box;
background-color: #ffffff;
.b-left {
width: 420rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
color: #333333;
background-color: #ffffff;
text {
color: #FF2536;
}
}
.b-right {
display: flex;
align-items: center;
justify-content: center;
width: 330rpx;
background: #2F569E;
font-size: 36rpx;
color: #FFFFFF;
}
.b-right-active {
opacity: .5;
}
}
/deep/ .u-popup__content {
border-radius: 10rpx 10rpx 0px 0px;
background-color: #FBFAFA;
}
</style>
子组件selectionRange代码:
<template>
<view class="rangeBox" ref="selection">
<view class="titleBox">
<text class="title">{{ selectItem.name }}</text>
<text class="title1">(可多选)</text>
</view>
<view class="secondTitle">
{{ selectItem.desc }}
</view>
<!-- 市场板块 -->
<view v-if="selectItem.plateID == 1001">
<view class="checkList">
<view
v-for="(i, dex) in marketValueList"
:key="dex"
:class="i.isChecked ? 'active' : 'checkItem'"
@click="selectMarket(i)"
>
{{ i.name }}
</view>
</view>
</view>
<!-- 行业板块 -->
<view v-else>
<view class="selectBox">
<text class="title">已选</text>
<text class="title1">({{ checkboxValue.length }})</text>
</view>
<!-- 搜索框 -->
<u--input
placeholder="搜索"
border="surround"
prefixIcon="search"
prefixIconStyle="font-size: 50rpx;color: #a8a9a9"
v-model="keyword"
clearable
@change="searchContent"
class="search"
></u--input>
<!-- 多选框 -->
<scroll-view
scroll-y="true"
scroll-x="false"
class="cateCheckbox"
v-if="checkboxListCopy && checkboxListCopy.length > 0"
>
<view
class="checkBox"
v-for="(item, index) in checkboxListCopy"
:key="index"
>
<view class="leftBox" @click="checkboxChange(item)">
<view
class="circle"
:style="
item.isCheck
? { 'background-color': '#FF0000', 'border-color': '#FF0000' }
: {}
"
>
<i class="iconfont icon-gouxuan1" v-if="item.isCheck"></i>
</view>
<view class="text">{{ item.name }}</view>
</view>
<text
class="rightBox"
:style="{
color:
toRatio(item.cr) > 0
? colorStyle[1]
: toRatio(item.cr) == 0
? colorStyle[0]
: colorStyle[2],
}"
>{{ item.cr > 0 ? "+" : ""
}}{{
toRatio(item.cr == null || item.cr == 0 ? 0.0 : item.cr)
}}%</text
>
</view>
</scroll-view>
<view v-else class="noData">暂无数据</view>
</view>
<view class="btnOptions">
<view class="leftBtn btn" @click="cancelPopup(selectItem.plateID)">
取消
</view>
<view
class="btn rightBtn"
@click="comfirmSelect(selectItem.plateID)"
:class="checkboxValue.length > 0 ? 'checkedBtn' : 'unCheckedBtn'"
>
确定
</view>
</view>
</view>
</template>
<script>
import { toDecimal } from "@/common/numberFormat.js";
import { signalrInvoke } from "@/util/signalR.js"; // 行情订阅接口
export default {
props: {
selectItem: {
type: Object,
default: {},
},
crArr: {
type: Array,
default: [],
},
checkedList: {
type: Array,
default: [],
},
marketCheckedList: {
type: Array,
default: [],
},
},
data() {
return {
timer: null,
selectNum: 0,
keyword: "", // 搜索值
checkboxValue:
this.checkedList.length > 0 ? [].concat(this.checkedList) : [], // 行业选中的值
checkboxList: [], //行业列表
checkboxListCopy: [], // 页面搜索备份行业列表
marketValueList: [], // 市场列表
marketCheckedValue:
this.marketCheckedList.length > 0
? [].concat(this.marketCheckedList)
: [], // 市场选中列表
colorStyle: ["#666666", "#FF0000", "#069c42"],
};
},
created() {
this.getCheckboxData();
},
watch: {
keyword(newVal, oldVal) {
if (oldVal !== newVal) {
this.searchContent(newVal);
}
},
},
methods: {
// 多选框列表
getCheckboxData() {
this.marketValueList = [];
this.checkboxList = [];
clearTimeout(this.timer);
uni.showLoading();
this.timer = setTimeout(() => {
if (this.selectItem.plateID != 1001) { // 行业列表
this.crArr.forEach((i) => {
this.selectItem.childNode.forEach((res) => {
if (i.plateId == res.plateID) {
let obj = {
code: res.code,
name: res.name,
plateID: res.plateID,
plateTreeID: res.plateTreeID,
typeCode: res.typeCode,
cr: i.cr,
isCheck: this.checkboxValue.some( // 设置选中项
(item) => item.id == res.plateID
)
? true
: false,
};
uni.hideLoading();
this.checkboxList.push(obj);
this.checkboxListCopy = this.checkboxList;
}
});
});
} else { // 市场列表
this.selectItem.childNode.forEach((res1) => {
let obj = {
name: res1.name,
plateID: res1.plateID,
plateTreeID: res1.plateTreeID,
typeCode: res1.typeCode,
isChecked: this.marketCheckedValue.some( // 设置选中项
(item) => item.id == res1.plateID
)
? true
: false,
};
this.marketValueList.push(obj);
uni.hideLoading();
});
}
}, 400);
},
// 多选项选中与非选中改变
checkboxChange(item) {
item.isCheck = !item.isCheck;
if (this.checkboxValue.some((res) => res.id == item.plateID)) { // 取消选中(这里是对象obj格式)
this.checkboxValue = this.checkboxValue.filter((item1) => {
return item1.id != item.plateID;
});
} else { // 选中
let obj = {
id: item.plateID,
name: item.name,
};
this.checkboxValue.push(obj);
}
},
// 选择市场
selectMarket(item) {
item.isChecked = !item.isChecked;
if (this.marketCheckedValue.some((res) => res.id == item.plateID)) {
this.marketCheckedValue = this.marketCheckedValue.filter((item1) => {
return item1.id != item.plateID;
});
} else {
let obj = {
id: item.plateID,
name: item.name,
};
this.marketCheckedValue.push(obj);
}
},
// 点击取消关闭弹框
cancelPopup(plateID) {
if (uni.hideLoading()) {
if (plateID != 1001) {
this.keyword = "";
this.checkboxValue = this.checkboxValue.length > 0 ? this.checkboxValue : [];
this.selectNum = this.checkboxValue.length > 0 ? this.checkboxValue.length : 0;
} else {
this.marketCheckedValue = this.marketCheckedValue > 0 ? this.marketCheckedValue : [];
}
this.$emit("cancelPopup"); // 关闭弹框
signalrInvoke("Quotation", "UnSubscribe", {
// 取消订阅行情
QuotationType: "PlateIndexQuotationInfo",
});
}
},
// 搜索
searchContent(value) {
if (value.trim() != "") {
this.checkboxListCopy = this.fuzzyQuery(
this.checkboxList,
value.trim()
);
} else {
this.checkboxListCopy = this.checkboxList;
}
},
// 确定选择条件
comfirmSelect(plateID) {
this.selectItem.isClick = true;
if (plateID != 1001) {
this.$emit("getSelectValue", this.checkboxValue);
} else {
this.$emit("getSelectValue", this.marketCheckedValue);
}
},
// 输入框搜索
fuzzyQuery(list, keyWord) {
let arr = [];
for (let i = 0; i < list.length; i++) {
if (list[i].name.indexOf(keyWord) >= 0) {
arr.push(list[i]);
}
}
return arr;
},
// 数字格式化
toDecimal(num, digit = 2, isRround = true) {
if (num == null || num == undefined) {
return "";
}
return toDecimal(num, digit, isRround);
},
// 涨跌幅小数转百分比(乘以100)
numMul(arg1, arg2) {
var r1 = arg1 + "";
var r2 = arg2 + "";
var r3 = 0;
var r4 = 0;
try {
r3 = r1.split(".")[1].length;
} catch (err) {
r3 = 0;
}
try {
r4 = r2.split(".")[1].length;
} catch (err) {
r4 = 0;
}
return (
(Number(r1.replace(".", "")) * Number(r2.replace(".", ""))) /
Math.pow(10, r4 + r3)
);
},
// 数值保留2位小数
toRatio(num, digit = 2) {
if (num == null) {
return 0.0;
}
return this.toDecimal(this.numMul(num, 100), digit);
},
},
};
</script>
<style lang="scss" scoped>
.rangeBox {
width: 100%;
padding: 0 30rpx;
.titleBox {
margin: 48rpx 0 30rpx 0;
.title {
font-size: 36rpx;
font-family: Noto Sans S Chinese;
font-weight: bold;
color: #333333;
}
.title1 {
font-size: 24rpx;
font-family: Noto Sans S Chinese;
color: #ff0000;
}
}
.secondTitle {
font-size: 30rpx;
font-family: Noto Sans S Chinese;
font-weight: normal;
color: #6a6a6a;
margin-bottom: 57rpx;
}
.checkList {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 216rpx;
.checkItem {
width: 216rpx;
height: 77rpx;
background: #eeeeee;
border-radius: 3rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-family: Noto Sans S Chinese;
font-weight: 400;
color: #333333;
margin-bottom: 24rpx;
}
.active {
background-color: #dee4ef;
border: 2rpx solid #2f569e;
color: #2f569e;
width: 216rpx;
height: 77rpx;
border-radius: 3rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-family: Noto Sans S Chinese;
font-weight: 400;
margin-bottom: 24rpx;
}
}
.selectBox {
margin-bottom: 30rpx;
.title {
font-size: 30rpx;
font-family: Noto Sans S Chinese;
font-weight: 500;
color: #333333;
}
.title1 {
font-size: 30rpx;
font-family: Noto Sans S Chinese;
color: #ff0000;
}
}
.search {
height: 70rpx !important;
margin-bottom: 60rpx;
background-color: #eeeeee;
border-radius: 10rpx;
}
.cateCheckbox {
height: 430rpx;
overflow-y: scroll;
margin-bottom: 56rpx;
.checkBox {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 50rpx;
.leftBox {
display: flex;
.circle {
width: 42rpx;
height: 42rpx;
border-radius: 50%;
border: 2rpx solid #bfbfbf;
display: flex;
justify-content: center;
align-items: center;
.icon-gouxuan1 {
font-size: 42rpx;
color: #fff;
}
}
.text {
font-size: 30rpx;
font-family: Noto Sans S Chinese;
font-weight: 400;
color: #333333;
margin-left: 18rpx;
}
}
.rightBox {
font-size: 30rpx !important;
font-family: Noto Sans S Chinese;
font-weight: 400;
}
}
}
.noData {
height: 200rpx;
width: 307rpx;
margin: 120rpx auto;
padding-top: 220rpx;
text-align: center;
font-size: 30rpx;
color: #939aaa;
background: url(@/static/images/noData.png) center 10rpx no-repeat;
background-size: contain;
}
.btnOptions {
margin-bottom: 30rpx;
display: flex;
justify-content: space-between;
.btn {
display: flex;
align-items: center;
justify-content: center;
border-radius: 10rpx;
font-size: 30rpx;
font-family: Noto Sans S Chinese;
font-weight: 400;
}
.leftBtn {
width: 202rpx;
height: 88rpx;
background: #eeeeee;
color: #333333;
}
.rightBtn {
width: 377rpx;
height: 88rpx;
}
.checkedBtn {
background: #2f569e;
color: #ffffff;
}
.unCheckedBtn {
background: #eeeeee;
color: #333333;
}
}
}
</style>
numberFormat.js文件代码:
/**
* [toDecimal 转化数字为几位小数,四舍五入或者截取多于位数]
* @param {[Number]} num [需转化的数字]
* @param {Number} [digit=2] [需保留或截取的位数]
* @param {Boolean} [isRround=true] [true四舍五入,false截取多于小数]
* @return {[Number, String]} [转换后的结果]
*/
export function toDecimal(num, digit = 2, isRround = true) {
if (num === null || num === undefined) {
return ''
}
// 负数处理四舍五入,先转成正数
let sign = ''
if (num < 0) {
sign = '-'
}
num = Math.abs(num).toString()
// 出现了科学计数法
if (num.indexOf('e-') != -1) {
num = noKeXueJiShuFa(num)
}
const numArr = num.split('.')
if (numArr.length > 1) {
// 有小数时的处理
if (numArr[1].length <= digit) {
// 小数位数少于需保留位数的时候
return sign + Number(num).toFixed(digit)
} else {
// 小数位数多于需保留位数的时候
if (isRround) {
// 四舍五入处理
num = numArr[1][digit] >= 5 ? parseInt(noKeXueJiShuFa(num * Math.pow(10, digit))) + 1 : parseInt(noKeXueJiShuFa(num * Math.pow(10, digit)))
return sign + (num / Math.pow(10, digit)).toFixed(digit)
} else {
// 去除多于小数
return sign + `${numArr[0]}.${numArr[1].substring(0, digit)}`
}
}
} else {
// 无小数时的处理
return sign + Number(num).toFixed(digit)
}
}
function noKeXueJiShuFa(num) {
if (String(num).indexOf('e-') != -1) {
num = '0' + String(Number(num) + 1).substr(1)
}
return num
}
用到的图片: