父组件代码:
<template>
<view>
<!-- 表格插件 -->
<lyy-table
:headerFixed="true"
:columnFixed="1"
:emptyString="'-'"
:contents="tableData"
:headers="columnsData"
@tdClick="goToSimilarDetail"
class="lyyTable"
></lyy-table>
</view>
</template>
<script>
import lyyTable from "@/components/lyy-table/lyy-table.vue";
export default {
components: {
lyyTable
},
data() {
return {
queryInfo: {
symbol: '000002',
eventtype: 2,
eventdate: '2024-06-17',
eventtypename: '解禁'
shortname: '万科A'
},
columnsData: [
{
label: '代码名称',
width: 40 + 5 * 16 + "px",
key: 'shortname'
},
{
label: '代码',
width: 40 + 4 * 16 + "px",
key: 'symbol'
},
{
label: '事件名称',
width: 40 + 5 * 16 + "px",
key: 'eventtypename'
},
{
label: '事件类型',
width: 40 + 5 * 16 + "px",
key: 'eventtype'
},
{
label: '事件日期',
width: 40 + 5 * 16 + "px",
key: 'eventdate'
},
{
label: '相似度',
width: 40 + 4 * 16 + "px",
key: 'similarvalue'
}
],
tableData: [
{shortname: '平安银行', symbol: '000001', similarvalue: '87.5%', eventtype: 2, eventdate: '2024-07-01', eventtypename: '解禁'},
{shortname: '中金岭南', symbol: '000060', similarvalue: '89.5%', eventtype: 1, eventdate: '2024-07-16', eventtypename: '财报'}
], // 表格数据
}
},
methods: {
// 跳转相似度详情页面
goToSimilarDetail(row) {
let arr = []
if (row.eventtype == 1) {
this.eventtypename = '财报'
} else if (row.eventtype == 2) {
this.eventtypename = '解禁'
} else if (row.eventtype == 3) {
this.eventtypename = '定向增发'
} else if (row.eventtype == 4) {
this.eventtypename = 'ST摘帽'
} else if (row.eventtype == 5) {
this.eventtypename = '银行贷款'
} else if (row.eventtype == 6) {
this.eventtypename = '分红送转'
} else if (row.eventtype == 7) {
this.eventtypename = '股权激励'
}
arr = [
{ event_date: this.queryInfo.eventdate,
event_type: this.queryInfo.eventtype,
eventtypename: this.queryInfo.eventtypename,
shortname: this.queryInfo.shortname,
symbol: this.queryInfo.symbol},
{ event_date: row.eventdate,
event_type: row.eventtype,
shortname: row.shortname,
eventtypename: this.eventtypename,
symbol: row.symbol,
similarvalue: row.similarvalue }
]
sessionStorage.setItem('similarParams', JSON.stringify(arr))
uni.navigateTo({ url: `/pages/similarDetail/similarDetail?row=${JSON.stringify(row)}` })
},
}
}
</script>
<style lang="scss" scoped>
.lyyTable {
padding: 26rpx 26rpx 30rpx;
height: 484rpx !important;
}
</style>
lyyTable表格组件代码:
<template>
<view
:style="{ height }"
:prop="ready"
:change:prop="tableRender.documentReady"
class="pageBox noBorderTable"
>
<scroll-view
scroll-x
scroll-y
upper-threshold="2"
class="container"
:scroll-top="scrollTop"
@scroll="scrollView"
@scrolltoupper="scrolltoupper"
@scrolltolower="scrolltolower"
>
<uni-table
border
class="tableBox"
:loading="loading"
style="min-height: 100%"
>
<uni-tr
id="lyy-thead"
:class="headerFixed ? 'fixed-head' : ''"
style="display: flex"
>
<uni-th
v-for="(item, index) in headers"
:key="index"
:width="item.width"
align="center"
:style="{
display: item.hidden ? 'none' : 'flex',
width: item.width,
justifyContent: 'center',
left: columnFixed > 0 ? fixedLeft(index) : 'unset',
right: columnFixed < 0 ? fixedRight(index) : 'unset',
position: isFixed(index) ? 'sticky' : 'unset',
borderLeft:
columnFixed < 0 && isFixed(index)
? '1px solid #EBEBED'
: 'unset',
zIndex: index <= columnFixed - 1 ? 999 : 99,
backgroundColor: 'inherit',
}"
>
<view
style="
display: flex;
flex-direction: row;
justify-content: center;
"
>
<text class="header-txt" :style="{ lineHeight: '20px' }">{{
item.label
}}</text>
<!-- 相似事件表格加入注释图标 -->
<view
class="iconfont icon-jieshi"
v-if="item.key === 'similarvalue'"
@click="showInfoDialog"
></view>
<view
class="header-icon"
style="
line-height: 6px;
display: flex;
flex-direction: column;
margin-left: 5px;
justify-content: center;
"
v-if="item.sort"
@click="doSort(item)"
>
<text
class="iconfont icon-arrow-up"
:style="{
color:
lastSortItem === item.key && sortWay == 'asc'
? '#1E81FF'
: '#CCCCCC',
}"
/>
<text
class="iconfont icon-arrow-down"
:style="{
color:
lastSortItem === item.key && sortWay == 'desc'
? '#1E81FF'
: '#CCCCCC',
}"
/>
</view>
</view>
</uni-th>
</uni-tr>
<template v-if="contents.length > 0">
<uni-tr
v-for="(content, sindex) in sortContents"
:key="sindex"
style="display: flex; table-layout: fixed; min-width: 100%"
@click.native="trClick(content, sindex)"
>
<uni-td
v-for="(header, hindex) in headers"
class="tableCell"
:data-wrap="overflow"
:key="hindex"
align="center"
:style="{
display: header.hidden ? 'none' : 'flex',
textAlign: 'center',
width: header.width,
left: columnFixed > 0 ? fixedLeft(hindex) : 'unset',
right: columnFixed < 0 ? fixedRight(hindex) : 'unset',
justifyContent: 'center',
alignItems: 'center',
position: isFixed(hindex) ? 'sticky' : 'unset',
borderLeft:
columnFixed < 0 && isFixed(hindex)
? '1px solid #EBEBED'
: 'unset',
zIndex: hindex <= columnFixed - 1 ? 9 : 'unset',
backgroundColor: 'inherit',
overflow: 'hidden',
}"
>
<template v-if="header.format !== undefined">
<lyy-progress
v-if="
header.format.type === 'progress' &&
!isNaN(parseFloat(content[header.key]))
"
:percent="content[header.key].toFixed(2)"
show-info
round
></lyy-progress>
<view
v-else-if="header.format.type === 'html'"
v-html="content[header.key]"
></view>
<text v-else>{{ content[header.key] }}</text>
</template>
<template v-else>
<text
v-if="header.countColor"
:ref="'ref' + sindex + header.key"
:style="{ color: turnColor(0, content[header.key]) }"
@click.native="tdClick(content, header, header.key, sindex)"
>{{ content[header.key] }}</text
>
<!-- 相似事件相似度显示处理 -->
<text
v-else-if="header.key === 'similarvalue'"
:ref="'ref' + sindex + header.key"
style="color: #1e81ff; text-decoration: underline"
@click.native="tdClick(content, header, header.key, sindex)"
>{{ content[header.key] }}</text
>
<!-- 表格星级显示 -->
<view
v-else-if="
header.key === 'EventHot' || header.type === 'starIcon'
"
@click.native="tdClick(content, header, header.key, sindex)"
>
<text
v-for="(items, index) in 5"
:key="index"
:style="{
color:
content[header.key] > index ? '#F7BA2A' : '#DAE0E7',
}"
class="iconfont star-icon"
>
{{ content[header.key] > index ? "★" : "☆" }}
</text></view
>
<!-- 表格涨跌幅等颜色显示判断 -->
<text
v-else
:ref="'ref' + sindex + header.key"
@click.native="tdClick(content, header, header.key, sindex)"
:style="content[header.key + 'Style']"
>
{{ content[header.key] }}
</text>
</template>
</uni-td>
</uni-tr>
</template>
<uni-tr
v-if="contents.length > 0 && totalRow.length > 0"
style="min-width: 100%; display: flex"
>
<uni-td
v-for="(header, index) in headers"
:key="Math.random()"
align="center"
:style="{
textAlign: 'center',
display: header.hidden ? 'none' : 'table-cell',
width: header.width,
left: columnFixed > 0 ? fixedLeft(index) : 'unset',
right: columnFixed < 0 ? fixedRight(index) : 'unset',
position: isFixed(index) ? 'sticky' : 'unset',
borderLeft:
columnFixed < 0 && isFixed(index)
? '1px solid #EBEBED'
: 'unset',
zIndex: index <= columnFixed - 1 ? 9 : 'unset',
backgroundColor: 'inherit',
}"
>
<text v-if="index == 0">合计</text>
<view v-else>
<!--<progress v-if="typeof header.format!=='undefined'&& header.format.type==='progress'" :percent="renderTotalRow(header)" :show-info="true" stroke-width="10" :active="true"></progress>-->
<lyy-progress
v-if="
typeof header.format !== 'undefined' &&
header.format.type === 'progress' &&
!isNaN(parseFloat(renderTotalRow(header)))
"
:percent="renderTotalRow(header)"
:show-info="true"
round
></lyy-progress>
<text v-else>{{ renderTotalRow(header) }}</text>
</view>
</uni-td>
</uni-tr>
</uni-table>
<!-- <uni-load-more v-show="showLoadMore" :status="loadMore"></uni-load-more> -->
</scroll-view>
<view v-if="contents.length < 1" class="no_data">暂无数据</view>
<view class="tooltip-box" v-if="moreText">
<view class="text-box">{{ moreText }}</view>
</view>
<!-- 表格展示不全点击展示文本弹框 -->
<u-popup
:show="detailTxt != ''"
mode="bottom"
:round="10"
@close="detailTxt = ''"
>
<view class="pop-content-box">
<view class="title-box">
<text
class="icon iconfont icon-shanchu"
@click="detailTxt = ''"
></text>
</view>
<scroll-view scroll-y="true" style="max-height: 622rpx">
<view class="content">{{ detailTxt }}</view>
</scroll-view>
<view style="height: 16rpx; background: #f7f7f9"></view>
<view class="confrim-btn" @click="detailTxt = ''">确定</view>
</view>
</u-popup>
</view>
</template>
<script>
import lyyProgress from "./lyy-progress";
/**
* lyyTable ver1.3.8
* @description lyyTable表格组件 ver1.3.8
*/
export default {
name: "lyyTable",
components: {
lyyProgress,
},
data() {
return {
scrollTop: 0,
ready: 1,
lastSortItem: "", //上一次排序列
sortWay: "none", //默认无排序
sortIndex: 0,
sortContents: [], //排序时的表格内容
footContent: {},
scrollHeight: "",
theadHeight: "",
pageindex: this.pageIndex,
moreText: "",
detailTxt: "", // 底部弹出模态框的文字
timer: null,
};
},
props: {
//表格高度 1.3.8
// #ifdef H5
height: {
type: String,
default: "calc(100vh - 44px - env(safe-area-inset-top))",
},
// #endif
// #ifndef H5
height: {
type: String,
default: "100vh",
},
// #endif
overflow: {
type: String,
default: "nowrap",
validator: (val) => ["wrap", "nowrap"].indexOf(val) > -1,
},
//显示加载
loading: {
type: Boolean,
default: false,
},
//上拉加载文字,参考uni-load-more
loadMore: {
type: String,
default: "more",
},
//是否显示上拉加载组件
showLoadMore: {
type: Boolean,
default: false,
},
//固定表头
headerFixed: {
type: Boolean,
default: false,
},
//固定首列 ver1.3.3弃用
/*firstColumnFixed: {
type: Boolean,
default: false
},*/
//固定列数 ver1.3.3新增
columnFixed: {
type: Number,
default: 0,
},
//排序方式
sortWays: {
type: Array,
default: () => ["none", "asc", "desc"],
},
//数据为空时的占位符
emptyString: {
type: String,
default: "-",
},
//表头
headers: {
type: Array,
default: () => [],
},
//表格数据
contents: {
type: Array,
default: () => [],
},
//合计列
totalRow: {
type: Array,
default: () => [],
},
},
mounted() {
//uni.setStorageSync('contents',this.contents)
this.sortContents = JSON.parse(JSON.stringify(this.contents));
this.renderContents();
this.createTotalRow();
if (this.overflow == "nowrap") {
this.ready = this.headers.length * this.contents.length;
}
},
watch: {
contents: {
//console.log(value)
handler(value) {
this.sortContents = JSON.parse(JSON.stringify(value));
this.renderContents();
this.createTotalRow();
// this.lastSortItem = "";
// this.sortWay = "none";
for (var header of this.headers) {
this.renderTotalRow(header);
}
// this.$forceUpdate()
this.$nextTick(function () {
if (this.overflow == "nowrap") {
this.ready = this.headers.length * this.contents.length;
}
});
},
deep: true,
},
},
methods: {
// 相似度图标点击显示注释弹框
showInfoDialog() {
this.detailTxt =
"通过事件标的和时间点对应的基础数据、财务数据、行情数据、事件数据等,通过对比全市场的代码衍生计算,得到跟标的事件的相似度数值。";
},
scrollView(event) {
this.scrollTop = event.detail.scrollTop;
},
viewScrollTop(top) {
this.scrollTop = top || 0;
},
//点击排序表头时存储上次排序列名,并循环切换排序方式
doSort(item) {
if (item.sort) {
if (this.lastSortItem !== item.key) {
this.lastSortItem = item.key;
this.sortIndex = 0;
this.sortIndex++;
this.sortWay = this.sortWays[this.sortIndex];
} else {
if (this.sortIndex < 2) {
this.sortIndex++;
this.sortWay = this.sortWays[this.sortIndex];
} else {
this.sortIndex = 0;
this.sortWay = this.sortWays[0];
}
}
this.$emit("tableSort", [item.key, this.sortWay]);
}
},
//表格内容渲染
renderContents() {
const headers = this.headers;
//防止修改穿透
var contents = JSON.parse(JSON.stringify(this.contents));
//var contents=uni.getStorageSync('contents')
let sortContents = JSON.parse(JSON.stringify(this.sortContents));
var newArr = [];
var result = "";
sortContents.forEach(function (content, index) {
var item = content;
headers.forEach(function (header) {
//字符类型格式化
if (
typeof header.format !== "undefined" &&
(header.format.type === "string" || header.format.type === "html")
) {
var template = header.format.template;
var keys = header.format.keys;
if (typeof template === "function") {
var arg = [];
keys.forEach((el, i) => {
arg.push(contents[index][el]);
});
result = template(arg);
} else {
keys.forEach((el, i) => {
var value = contents[index][el];
var reg = new RegExp("\\{" + i + "}", "g");
template = template.replace(reg, value);
});
result = template;
}
item[header.key] = result;
}
//计算类型格式化
else if (
typeof header.format !== "undefined" &&
(header.format.type === "compute" ||
header.format.type === "progress")
) {
//console.log(header.format.template)
var temp = header.format.template;
var keys = header.format.keys;
//1.3.7 使计算列支持function
if (typeof temp === "function") {
var arg = [];
keys.forEach((el, i) => {
arg.push(contents[index][el]);
});
item[header.key] = temp(arg);
//console.log(result)
} else {
keys.forEach((el, i) => {
var reg = new RegExp("\\{" + i + "}", "g");
temp = temp.replace(reg, contents[index][el]);
});
//console.log(temp)
item[header.key] = eval(temp);
//this.sortContents[index][header.key]=result
}
}
});
newArr.push(item);
});
this.sortContents = newArr;
},
createTotalRow() {
if (this.totalRow.length > 0 && this.sortContents[0] !== undefined) {
/*var obj = {...this.contents[0]}
console.log(obj)
for (var i in obj) {
this.footContent[i] = obj[i]
}*/
this.footContent = JSON.parse(JSON.stringify(this.sortContents[0]));
for (var i in this.footContent) {
var result = 0;
if (this.sortContents.length > 0) {
for (var j in this.sortContents) {
result += parseFloat(this.sortContents[j][i]) || 0;
}
}
this.footContent[i] = result;
}
}
},
//合计列渲染
renderTotalRow(header) {
var content = JSON.parse(JSON.stringify(this.footContent));
var result = this.emptyString;
if (this.totalRow.indexOf(header.key) > -1) {
if (
typeof header.format !== "undefined" &&
header.format.type === "progress"
) {
var temp = header.format.template;
var keys = header.format.keys;
for (var index in keys) {
var reg = new RegExp("\\{" + index + "}", "g");
temp = temp.replace(reg, content[keys[index]]);
}
result = eval(temp);
result = isNaN(result) ? 0 : result.toFixed(2);
} else {
if (content[header.key] != null && !isNaN(content[header.key])) {
result = content[header.key];
}
}
}
return result;
},
//上拉加载事件
scrolltolower(e) {
if (e.detail.direction == "bottom") {
this.$emit("onPullBottom");
}
},
scrolltoupper(e) {
if (e.detail.direction == "top") {
this.$emit("onPullTop");
}
},
//固定列left计算
fixedLeft(index) {
var headers = this.headers.filter((item) => !item.hidden);
var left = "calc(1px";
for (var i = 1; i < index + 1; i++) {
left += " + " + headers[i - 1].width;
}
left += ")";
return left;
},
//v1.3.5
//固定列right计算
fixedRight(index) {
var headers = this.headers.filter((item) => !item.hidden);
var columnFixed = Math.abs(this.columnFixed);
if (index >= headers.length + this.columnFixed) {
var right = "calc(1px";
for (var i = index; i < headers.length - 1; i++) {
if (index + 1 == headers.length) {
break;
} else {
right += " + " + headers[i + 1].width;
}
}
right += ")";
return right;
} else {
return "unset";
}
},
//v1.3.5
isFixed(index) {
if (this.columnFixed > 0) {
return index <= this.columnFixed - 1;
} else if (this.columnFixed < 0) {
var headers = this.headers.filter((item) => !item.hidden);
return index >= headers.length + this.columnFixed;
} else {
return false;
}
},
/**
* 设置跌涨颜色
* val1: 对比的值
* val2: 当前显示的值
* */
turnColor(val1, val2) {
if (String(val2).indexOf("%") !== -1) {
var num1 = isNaN(parseFloat(val1)) ? 0 : parseFloat(val1);
var num2 = isNaN(parseFloat(val2)) ? 0 : parseFloat(val2);
// 判断是否是-0
if (num1 == 0 && num2 == 0 && 1 / num2 < 0) {
return "#38BD22";
}
if (num1 == 0 && num2 == 0) {
return "#666666";
}
if (num1 > num2) {
return "#38BD22";
} else if (num1 < num2) {
return "#F33030";
} else {
return "#666666";
}
}
},
tdClick(row, header, key, index) {
if (
this.$refs["ref" + index + key] &&
this.$refs["ref" + index + key][0].$el.scrollWidth >
this.$refs["ref" + index + key][0].$el.clientWidth
) {
console.log(true);
let text = row[key];
clearTimeout(this.timer);
if (text && text.length > 50) {
this.detailTxt = text;
this.moreText = "";
} else {
this.moreText = text;
this.detailTxt = "";
this.timer = setTimeout(() => {
this.moreText = "";
}, 1000);
}
} else {
this.$emit("tdClick", row, header, key, index);
}
},
trClick(row, i) {
this.$emit("trClick", row, i);
},
},
};
</script>
<script module="tableRender" lang="renderjs">
function test(a, b) {
alert(a, b)
}
export default {
methods: {
documentReady(newValue, oldValue, ownerInstance, instance) {
//alert(`${newValue},${oldValue}`)
if (newValue > oldValue) {
var cells = document.querySelectorAll('.tableCell')
function changeWrap(e) {
var wrap = e.currentTarget.dataset.wrap
e.currentTarget.dataset.wrap = wrap == 'nowrap' ? 'unset' : 'nowrap'
}
function fun(e) {
changeWrap(e)
}
// for (var i = oldValue - 1; i < cells.length; i++) {
// cells[i].addEventListener('click', fun)
// }
}
}
}
}
</script>
<style>
/deep/.uni-table-loading {
display: none !important;
}
</style>
<style lang="scss" scoped>
@import "./css/iconfont.css";
.pageBox {
position: relative;
}
.container {
// width: 100vw;
width: 100%;
height: 100%;
// border-bottom: 1px solid #ebebed;
}
.star-icon {
margin-right: 6px;
font-size: 14px;
&:nth-last-of-type(1) {
margin-right: 0;
}
}
.icon-jieshi {
font-size: 36rpx !important;
color: #1e81ff;
}
/deep/ .uni-table-tr:nth-child(2n + 3) {
// background-color: #fcfcfd !important;
}
/deep/ .table-border {
border-right: 1px solid #ebebed !important;
}
/deep/ .uni-table-td {
border-bottom: 1px solid #ebebed !important;
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 400;
/* color: #666666; */
padding: 6rpx 16rpx;
height: 80rpx;
}
/deep/ .uni-table-th {
border-bottom: 1px solid #ebebed !important;
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 500;
color: #333333;
padding: 27rpx 16rpx;
.header-txt {
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
}
.uni-table-scroll {
overflow: unset !important;
border-top: none;
}
.no_data {
position: absolute;
top: 90rpx;
width: 100%;
// background-color: #fff;
height: 50px;
text-align: center;
line-height: 50px;
font-size: 26rpx;
}
#lyy-thead {
/*min-width: 750upx;*/
display: table;
background: #f7f7f9;
height: 80rpx;
}
[data-wrap="unset"] {
white-space: unset !important;
display: flex !important;
}
[data-wrap="nowrap"] {
white-space: nowrap !important;
}
[data-wrap="nowrap"] uni-text {
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
}
.fixed-head {
position: sticky;
top: 0;
z-index: 99;
border-top: 1px solid #ebebed;
}
/*修复滑动时偏移1px问题*/
/*.table--border {
border-top: none;
border-left: none;
}*/
.uni-table-tr {
background-color: #fff;
}
.tooltip-box {
position: fixed;
left: 50%;
top: 50%;
width: 100%;
transform: translate(-50%, -50%);
text-align: center;
box-sizing: border-box;
z-index: 99;
.text-box {
display: inline-block;
background-color: #333;
border-radius: 16rpx;
color: #fff;
font-size: 24rpx;
text-align: left;
line-height: 48rpx;
max-width: 600rpx;
padding: 12rpx 24rpx;
box-sizing: border-box;
}
}
.pop-content-box {
.title-box {
height: 50rpx;
display: flex;
justify-content: flex-end;
align-items: center;
.iconfont {
margin-right: 20rpx;
font-size: 32rpx !important;
transform: scale(1);
}
}
.content {
padding: 20rpx 40rpx;
font-size: 30rpx;
color: #333333;
white-space: pre-line;
text-align: justify;
}
.confrim-btn {
height: 70rpx;
background: #1e81ff;
border-radius: 6rpx;
font-size: 30rpx;
font-family: PingFang SC;
font-weight: bold;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
margin: 40rpx;
}
}
.noBorderTable {
.tableBox {
border-left: none;
}
.uni-table-th {
border-right: none;
}
.tableCell {
border-right: none;
}
}
</style>
/pages/similarDetail/similarDetail跳转详情页面代码:
<template>
<view class="page-wrapper">
<view class="swiper-box">
<swiper class="swiper" :current="swiperCurrent" circular :indicator-dots="indicatorDots" @change="changeSwiper">
<swiper-item v-for="(item, index) in swiperList" :key="item.event_date + index" class="swiper-item">
<view class="left-item">
<view>
{{ `${queryParams[0].shortname}${getDate(queryParams[0].event_date)}${queryParams[0].eventtypename}` }}
</view>
</view>
<view class="icon-box">
<view class="iconfont icon-VS"></view>
</view>
<view class="right-item">
<view>
{{ item.class }}
</view>
</view>
<view class="close-box" v-if="index > 0" @click="delSimilar(item, index)">
<view class="iconfont icon-shanchu"></view>
</view>
</swiper-item>
</swiper>
<view class="contrast-btn" @click="selectContrastPopup">
<text class="iconfont icon-tianjia"></text>
<text class="text">添加对比</text>
</view>
</view>
<!-- 回到列表顶部按钮 -->
<b-fab :defaultPost="'right'" :defaultY="100">
<view class="fab" :xGap="40" :bottomGap="40" @tap="scrollTopData">
<image src="/static/images/toTop.png" class="scroll-top"
v-show="scrollTopPage >= 50"></image>
</view>
</b-fab>
<!-- 添加对比弹框 -->
<u-popup :show="showContrastPopup" mode="bottom" :round="10" @close="cancelSelect">
<view class="pop-content-box">
<view class="title-box">
<text class="title">添加对比</text>
<text class="icon iconfont icon-shanchu" @click="cancelSelect"></text>
</view>
<scroll-view scroll-y="true" class="contrast-list">
<view class="item-box" v-for="(item, index) in similarOptions" :key="index" @click="changeSelect(item)">
<image class="circle-selected" mode="aspectFit" src="@/static/images/selected.svg" v-if="item.checked"></image>
<image class="circle-selected" mode="aspectFit" src="@/static/images/un_select.svg" v-else></image>
<view class="text" :class="item.checked ? 'selected-label' : 'label'">{{`${getDate(item.eventdate.substring(0, 10))}${item.shortname}${item.eventtypename}事件(${item.similarvalue})`}}</view>
</view>
</scroll-view>
<view class="confrim-btn" @click="submitSimilar">确定</view>
</view>
</u-popup>
</view>
</template>
<script>
import { querySimilarDetailData } from '@/api/queryEvent' // 后端接口
import bFab from './bFab'
export default {
components: {
bFab
},
data() {
return {
scrollTopPage: 0, // 距离顶部距离
swiperList: [], // 轮播图列表数据
swiperCurrent: 0, // swiper组件的current值,表示当前那个swiper-item是活动的
indicatorDots: true, // 是否展示轮播图指示器
similarBaseInfoList: [], // 基本信息数据
showContrastPopup: false, // 是否展示对比列表弹框
similarVal: [], // 列表选中值
similarOptions: [], // 弹框列表数据
queryParams: [], // 页面初始请求参数
queryParams1: [], // 第一条数据请求参数
tableParams: [] // 添加删除请求参数
}
},
created() {
let arr = []
arr = sessionStorage.getItem('similarParams')
this.queryParams = JSON.parse(arr)
const popupList = JSON.parse(sessionStorage.getItem('tableData'))
this.similarOptions = []
popupList.forEach(res => {
let obj = res
if (res.eventtype == 1) {
obj.eventtypename = '财报'
} else if (res.eventtype == 2) {
obj.eventtypename = '解禁'
} else if (res.eventtype == 3) {
obj.eventtypename = '定向增发'
} else if (res.eventtype == 4) {
obj.eventtypename = 'ST摘帽'
} else if (res.eventtype == 5) {
obj.eventtypename = '银行贷款'
} else if (res.eventtype == 6) {
obj.eventtypename = '分红送转'
} else if (res.eventtype == 7) {
obj.eventtypename = '股权激励'
}
this.similarOptions.push(obj)
})
this.similarOptions.forEach(item=> {
this.$set(item, 'checked', false)
})
this.getSimilarDetailData(this.queryParams)
},
methods: {
// 获取相似度详情数据
getSimilarDetailData(params) {
uni.showLoading({
title: "加载中"
})
this.swiperList = []
this.similarBaseInfoList = []
querySimilarDetailData(params).then(res=> {
uni.hideLoading()
if (res) {
// 基本信息数据处理
res.similarBaseInfoList && res.similarBaseInfoList.forEach((item, index) => {
this.similarBaseInfoList.push(item)
params.forEach((i, idx) => {
const title = this.getDate(i.event_date)
if (index + 1 === idx) {
let obj = {
class: `${i.shortname}${title}${i.eventtypename}(相似度${i.similarvalue})`,
event_date: i.event_date,
eventtypename: i.eventtypename,
shortname: i.shortname,
similarvalue: i.similarvalue
}
this.swiperList.push(obj)
}
})
})
}
})
},
// 格式化日期
getDate(date) {
const date1 = new Date(date)
// 获取年份、月份和日期
const year = date1.getFullYear()
const month = date1.getMonth() + 1 // 注意月份从0开始计数,所以需要加上1得到真正的月份(12)
const day = date1.getDate()
return `${year}年${month}月${day}日`
},
//回到列表顶部
scrollTopData() {
uni.pageScrollTo({
selector: ".page-wrapper",
scrollTop: 0
})
},
// 实时获取滚动条的位置
onPageScroll(res) {
this.scrollTopPage = res.scrollTop
},
// 展示弹框
selectContrastPopup() {
this.similarVal = []
this.swiperList.forEach((res) => {
this.similarOptions.forEach((i) => {
const title = this.getDate(res.event_date)
if (`${title}${res.shortname}${res.eventtypename}事件(${res.similarvalue})` === `${this.getDate(i.eventdate.substring(0, 10))}${i.shortname}${i.eventtypename}事件(${i.similarvalue})`
) {
i.checked = true
this.similarVal.push(`${title}${i.shortname}${i.eventtypename}事件(${i.similarvalue})`)
}
})
})
this.showContrastPopup = true
},
// 滑动改变轮播图
changeSwiper(e) {
this.swiperCurrent = e.detail.current
},
// 关闭弹框
cancelSelect() {
this.swiperList.forEach((res) => {
this.similarOptions.forEach((i) => {
const title = this.getDate(res.event_date)
if (`${title}${res.shortname}${res.eventtypename}事件(${res.similarvalue})` !== `${this.getDate(i.eventdate.substring(0, 10))}${i.shortname}${i.eventtypename}事件(${i.similarvalue})`
) {
i.checked = false
const arr = this.similarVal.filter((i) => i !== `${title}${res.shortname}${res.eventtypename}事件(${res.similarvalue})`)
this.similarVal = arr
}
})
})
this.showContrastPopup = false
},
// 点击选择与取消选择
changeSelect(item) {
if (this.similarVal && this.similarVal.length > 2 && !item.checked) {
uni.showToast({
title: "最多选择3项",
icon: "none"
})
return
}
item.checked = !item.checked
if (item.checked) {
this.similarVal.push(`${this.getDate(item.eventdate.substring(0, 10))}${item.shortname}${item.eventtypename}事件(${item.similarvalue})`)
} else {
const arr = this.similarVal.filter((i) => i !== `${this.getDate(item.eventdate.substring(0, 10))}${item.shortname}${item.eventtypename}事件(${item.similarvalue})`)
this.similarVal = arr
}
},
// 点击确定选择
submitSimilar() {
if (this.similarVal && this.similarVal.length === 0) {
uni.showToast({
title: "请至少选择一项",
icon: "none"
})
return
}
this.queryParams1 = []
let queryParams2 = []
let queryParams3 = []
this.tableParams = []
// 清除财务信息图表
this.$refs.financeChart.financeChart.clear()
// 清除行情走势图表
this.$refs.marketTrendChart.marketTrendChart.clear()
const obj1 = {
event_date: this.queryParams[0].event_date.substring(0, 10),
event_type: Number(this.queryParams[0].event_type),
shortname: this.queryParams[0].shortname,
eventtypename: this.queryParams[0].eventtypename,
symbol: this.queryParams[0].symbol
}
this.queryParams1.push(obj1)
this.similarVal.forEach((i) => {
queryParams2 = this.similarOptions.filter((item) =>
`${this.getDate(item.eventdate.substring(0, 10))}${item.shortname}${item.eventtypename}事件(${item.similarvalue})` === i
)
queryParams2.forEach((item) => {
const obj = {
event_date: item.eventdate.substring(0, 10),
event_type: Number(item.eventtype),
shortname: item.shortname,
eventtypename: item.eventtypename,
symbol: item.symbol,
similarvalue: item.similarvalue
}
queryParams3.push(obj)
this.tableParams = this.queryParams1.concat(queryParams3)
})
})
this.getSimilarDetailData(this.tableParams)
this.showContrastPopup = false
},
// 删除事件
delSimilar(item, index) {
// 处理请求参数
this.tableParams.splice(index + 1, 1)
// 清除财务信息图表
this.$refs.financeChart.financeChart.clear()
// 清除行情走势图表
this.$refs.marketTrendChart.marketTrendChart.clear()
// 删除选中值
const arr = this.similarVal.filter((i) => i !== `${this.getDate(item.event_date)}${item.shortname}${item.eventtypename}事件(${item.similarvalue})`)
this.similarVal = arr
// 下拉弹框列表回显
this.similarOptions.forEach((it) => {
if (`${this.getDate(item.event_date)}${item.shortname}${item.eventtypename}事件(${item.similarvalue})` === `${this.getDate(it.eventdate.substring(0, 10))}${it.shortname}${it.eventtypename}事件(${it.similarvalue})`
) {
it.checked = false
}
})
this.swiperCurrent = 0
// 重新请求数据
this.getSimilarDetailData(this.tableParams)
}
}
}
</script>
<style lang="scss" scoped>
.page-wrapper {
background-color: #F7F7F9;
padding: 0 20rpx;
.swiper-box {
background-color: #FFFFFF;
margin: 20rpx auto;
padding: 30rpx 20rpx;
border-radius: 16rpx;
.swiper {
height: 170rpx;
/deep/ .uni-swiper-dots-horizontal {
bottom: 14rpx;
}
/deep/ .uni-swiper-dot {
width: 20rpx;
height: 4rpx;
background: rgba(30,129,255,0.3);
border-radius: 2rpx;
margin-right: 10rpx;
}
/deep/ .uni-swiper-dot-active {
background: #1E81FF;
}
.swiper-item {
background: url('@/static/images/swiperBg.png') no-repeat;
background-size: 100% 100%;
position: relative;
height: 140rpx !important;
display: flex;
align-items: center;
justify-content: space-between;
.left-item {
font-size: 26rpx;
font-family: PingFang SC;
font-weight: bold;
color: #FFFFFF;
width: 45%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-left: 16rpx;
margin-right: 20rpx;
}
.right-item {
font-size: 26rpx;
font-family: PingFang SC;
font-weight: bold;
color: #FFFFFF;
width: 45%;
display: flex;
align-items: center;
justify-content: center;
margin-left: 20rpx;
margin-right: 16rpx;
}
.icon-box {
width: 60rpx;
height: 60rpx;
background: #FAC554;
border: 3px solid #FFFFFF;
border-radius: 50%;
text-align: center;
line-height: 60rpx;
.icon-VS {
box-shadow: 0rpx 2rpx 2rpx 0rpx rgba(195,145,38,0.3);
color: #FFFFFF;
}
}
.close-box {
width: 34rpx;
height: 34rpx;
background: rgba(255,255,255,0.2);
border-radius: 0rpx 16rpx 0rpx 16rpx;
position: absolute;
top: 0;
right: 0;
text-align: center;
line-height: 34rpx;
.icon-shanchu {
font-size: 16rpx;
color: #FFFFFF;
}
}
}
}
.contrast-btn {
height: 70rpx;
background: #FFFFFF;
border: 1px solid #1E81FF;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
margin-top: 10rpx;
.icon-tianjia {
font-size: 34rpx;
color: #1E81FF;
margin-right: 21rpx;
}
.text {
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 500;
color: #1E81FF;
}
}
}
.top-box {
height: 90rpx;
display: flex;
align-items: center;
border-bottom: 1rpx solid #F2F2F3;
.line {
width: 8rpx;
height: 30rpx;
background: #1E81FF;
border-radius: 0rpx 4rpx 4rpx 0rpx;
}
.title {
font-size: 28rpx;
font-family: PingFang SC;
font-weight: bold;
color: #333333;
margin-left: 18rpx;
}
}
.info-box {
background-color: #FFFFFF;
margin-bottom: 20rpx;
border-radius: 16rpx;
}
.show-img {
opacity: 0;
}
.scroll-top {
position: relative;
width: 82rpx;
height: 82rpx;
border-radius: 50%;
opacity: 1;
transition: opacity .5s;
-webkit-transition: opacity .5s;
bottom: 60rpx;
right: 80rpx;
}
.pop-content-box {
.title-box {
margin: 50rpx auto;
display:flex;
justify-content: space-between;
align-items: center;
padding: 0 40rpx;
.title {
font-size: 34rpx;
font-family: PingFang SC;
font-weight: bold;
color: #333333;
}
.icon-shanchu {
margin-right: 20rpx;
font-size: 32rpx!important;
transform: scale(1);
}
}
.contrast-list {
max-height: 622rpx;
margin-bottom: 30rpx;
.item-box {
display: flex;
align-items: center;
padding: 0 40rpx;
margin-bottom: 28rpx;
.circle-selected {
width: 38rpx;
height: 40rpx;
}
.text {
width: 620rpx;
height: 76rpx;
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 500;
border-radius: 10rpx;
margin-left: 22rpx;
display: flex;
justify-content: center;
align-items: center;
}
.label {
color: #333333;
border: 1px solid #E2E2E2;
}
.selected-label {
color: #1E81FF;
border: 1px solid #1E81FF;
}
}
}
.confrim-btn {
height: 76rpx;
background: #1E81FF;
border-radius: 10rpx;
font-size: 30rpx;
font-family: PingFang SC;
font-weight: bold;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
margin: 40rpx;
}
}
}
</style>
相关所需图片:
选中svg代码:
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 34 34" style="enable-background:new 0 0 34 34;" xml:space="preserve">
<style type="text/css">
.st0{fill:#1E81FF;}
</style>
<path class="st0" d="M17,0C7.6,0,0,7.6,0,17c0,9.4,7.6,17,17,17c9.4,0,17-7.6,17-17C34,7.6,26.4,0,17,0z M26.3,12.2L14.9,23.6
C14.7,23.9,14.3,24,14,24c-0.3,0-0.7-0.1-0.9-0.4l-5.4-5.4c-0.5-0.5-0.5-1.3,0-1.8c0.5-0.5,1.3-0.5,1.8,0l4.5,4.5l10.5-10.5
c0.5-0.5,1.3-0.5,1.8,0C26.8,10.9,26.8,11.7,26.3,12.2z"/>
</svg>
未选中svg代码:
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 34 34" style="enable-background:new 0 0 34 34;" xml:space="preserve">
<style type="text/css">
.st0{fill:#D6D6D6;}
</style>
<path class="st0" d="M17,0C7.6,0,0,7.6,0,17c0,9.4,7.6,17,17,17c9.4,0,17-7.6,17-17C34,7.6,26.4,0,17,0z M17,32.4
C8.5,32.4,1.6,25.5,1.6,17C1.6,8.5,8.5,1.6,17,1.6S32.4,8.5,32.4,17C32.4,25.5,25.5,32.4,17,32.4z"/>
</svg>
bFab组件代码:
<template>
<view class="mo-fab"
:animation="animationData"
@touchstart="drag_start"
@touchmove.stop.prevent="drag_move"
@touchend.stop="drag_end"
@touchcancel.stop="drag_end"
>
<view>
<slot
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO || MP-BAIDU -->
v-if="zScopedSlots.default||zSlots.default"
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-QQ || MP-TOUTIAO || MP-BAIDU -->
v-if="$scopedSlots.default||$slots.default"
<!-- #endif -->
></slot>
<template v-else>
<view class="mo-fab--wrap">
<slot
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO || MP-BAIDU -->
v-if="zScopedSlots.content||zSlots.content"
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-QQ || MP-TOUTIAO || MP-BAIDU -->
v-if="$scopedSlots.content||$slots.content"
<!-- #endif -->
name="content"
></slot>
<text v-else>示例</text>
</view>
</template>
</view>
</view>
</template>
<script>
/**
* 一个悬浮拖拽框
使用示例1:<moFab />
使用示例2:<moFab>内容</moFab>
使用示例3:<moFab><template #content>示例3</template></moFab>
*
*/
export default {
name: 'moFab',
props: {
type: { // 悬停类型 left-right || left || right || none
type: String,
default: 'left-right'
},
defaultPost: { // 初始化位置 left || right
type: String,
default: 'left'
},
defaultY: { // 初始化位置距离底部的距离
type: Number,
default: 200
},
xGap: { // 距离左右两边最小间距
type: Number,
default: 35
},
topGap: { // 距离顶部最小间距
type: Number,
default: 200
},
bottomGap: { // 距离底部最小间距
type: Number,
default: 80
},
defer: { // 是否开启惯性位移
type: Boolean,
default: true
},
deferVal: { // 惯性位移量
type: Number,
default: 150
},
},
data() {
return {
fabWidth: 50,
fabHeight: 50,
start: [0,0],
move: [0,0],
pages: [0,0],
animationData: {},
times: [0,0],
}
},
computed:{
zScopedSlots() {
return this.$scopedSlots;
},
zSlots() {
return this.$slots;
}
},
mounted() {
this.initmove()
},
methods: {
getPageHeight(){
// px转换到rpx的比例
const { windowWidth, windowHeight } = uni.getSystemInfoSync();
const pxToRpxScale = 750 / windowWidth;
this.pages = [windowWidth, windowHeight]
return { pxToRpxScale }
},
initmove(){
const { pxToRpxScale } = this.getPageHeight()
const defaultY = this.defaultY / pxToRpxScale
const bottomGap = this.bottomGap / pxToRpxScale
const query = uni.createSelectorQuery().in(this);
query.select('.mo-fab').boundingClientRect(res=>{
if(!res) return
this.fabWidth = res.width
this.fabHeight = res.height
let moveX = this.pages[0] - this.fabWidth - this.xGap / pxToRpxScale
let moveY = this.pages[1] - this.fabHeight - (bottomGap > defaultY ? bottomGap : defaultY )
if(this.defaultPost == 'left'){
moveX = this.xGap / pxToRpxScale
}
const dir = moveX > (this.pages[0] - this.fabWidth) / 2 ? -1 : 1
this.move = [moveX,moveY]
this.setmove({
duration: 0,
timingFunction: 'linear',
x: this.move[0],
y: this.move[1]
})
this.$emit('dragInit',{dir, grid: this.move})
}).exec();
},
setmove(ops){
const { duration, timingFunction, x, y } = Object.assign({
duration: 0,
timingFunction: 'linear',
x: 0,
y: 0
}, ops)
let animation = uni.createAnimation({
duration,
timingFunction,
delay: 0,
})
animation.translate(x,y).step()
this.animation = animation
this.animationData = this.animation.export()
},
drag_start(event){
const touches = event.touches[0] || event.changedTouches[0]
this.start = [touches.clientX, touches.clientY ]
this.times[0] = event.timeStamp
this.$emit('dragstart',event)
},
drag_move(event){
const touches = event.touches[0] || event.changedTouches[0];
const moveX = this.move[0] - this.start[0] + touches.clientX
const moveY = this.move[1] - this.start[1] + touches.clientY
const dir =moveX > (this.pages[0] - this.fabWidth) / 2 ? -1 : 1
this.setmove({
duration: 0,
timingFunction: 'linear',
x: moveX,
y: moveY
})
this.$emit('dragmove',{...event, dir, grid: this.move})
},
drag_end(event){
const touches = event.touches[0] || event.changedTouches[0];
this.times[1] = event.timeStamp
const timeDiff = this.times.reduce((a,b)=>b-a, 0)
const { pxToRpxScale } = this.getPageHeight()
const xGap = this.xGap / pxToRpxScale
const topGap = this.topGap / pxToRpxScale
const bottomGap = this.bottomGap / pxToRpxScale
const endX = this.pages[0] - this.fabWidth - xGap
const endY = this.pages[1] - this.fabHeight - bottomGap
const offx = touches.clientX - this.start[0]
const offy = touches.clientY - this.start[1]
let moveX = this.move[0] + offx
let moveY = this.move[1] + offy
if(this.defer){
moveX = moveX + this.getEndPost(offx,timeDiff)
moveY = moveY + this.getEndPost(offy,timeDiff)
}
moveY = moveY < topGap ? topGap : (moveY > endY ? endY : moveY)
if(this.type == 'left'){
moveX = xGap
}
if(this.type == 'right'){
moveX = endX
}
if(this.type == 'left-right'){
moveX = moveX > (this.pages[0] - this.fabWidth) / 2 ? endX : xGap
}
if(this.type == 'none'){
moveX = moveX < xGap ? xGap : (moveX > endX ? endX : moveX)
}
const dir = moveX > (this.pages[0] - this.fabWidth) / 2 ? -1 : 1
this.move = [moveX, moveY]
this.setmove({
duration: 250,
timingFunction: 'ease-out',
x: this.move[0],
y: this.move[1]
})
this.$emit('dragend',{...event, dir, grid: this.move})
},
getEndPost(offset, timeDiff){
let g = 2 * offset / Math.pow(timeDiff, 2)
const abs = Math.abs(+ (g * 10).toFixed(4))
g = abs < 0.01 ? 0 : g
return g * Math.pow(this.deferVal, 2) / 2
}
}
}
</script>
<style lang="scss" scoped>
.mo-fab {
display: block;
position: fixed;
top: 0;
left: 0;
color: #000;
z-index: 1000;
user-select: none;
&--wrap{
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
height: 100rpx;
width: 100rpx;
color: #fff;
box-shadow: 0px 7rpx 18rpx 0px rgba(107, 14, 195, 0.38);
border: 4rpx solid rgba(250, 121, 255, 0.8);
background: linear-gradient(90deg, #CD56FF, #833AD6);
}
}
</style>