(Update townDetail components with optimized API parameters and added sotre_id for improved accuracy)

This commit is contained in:
mkm 2024-07-17 11:20:37 +08:00
parent 439e2aeade
commit 04dc971f56
10 changed files with 183 additions and 197 deletions

View File

@ -70,7 +70,8 @@ export function getUserNumApi(params) {
export function getOrderNumApi(params) { export function getOrderNumApi(params) {
return instacne.get('/statistics/store_order_day', { params }) // return instacne.get('/statistics/store_order_day', { params })
return instacne.get('/statistics/demo/bottomLeft', { params })
} }
export function getSalesRankApi(params) { export function getSalesRankApi(params) {
return instacne.get('/statistics/sales_ranking', { params }) return instacne.get('/statistics/sales_ranking', { params })
@ -87,6 +88,9 @@ export function getTodayOrderAmountApi(params) {
return instacne.get('/statistics/store_order_day_two', { params }) return instacne.get('/statistics/store_order_day_two', { params })
} }
export function getDemoTodayOrderAmountApi(params) {
return instacne.get('/statistics/demo/store_order_day_two', { params })
}
export function getProductCountApi(params) { export function getProductCountApi(params) {
return instacne.get('/statistics/product_count', { params }) return instacne.get('/statistics/product_count', { params })
@ -95,11 +99,15 @@ export function getProductCountApi(params) {
export function getViewCountApi(params) { export function getViewCountApi(params) {
return instacne.get('/statistics/index', { params }) return instacne.get('/statistics/index', { params })
} }
export function getDemoViewCountApi(params) {
return instacne.get('/statistics/demo/index', { params })
}
export function getUserTradeCountApi(params) { export function getUserTradeCountApi(params) {
return instacne.get('/statistics/user_trade_count', { params }) return instacne.get('/statistics/user_trade_count', { params })
} }
export function getDemoUserTradeCountApi(params) {
return instacne.get('/statistics/demo/user_trade_count', { params })
}
export function getProductCategoryListApi(params) { export function getProductCategoryListApi(params) {
return instacne.get('/api/dataview/product_category_list', { params }) return instacne.get('/api/dataview/product_category_list', { params })
} }

View File

@ -4,12 +4,12 @@
</template> </template>
<script setup> <script setup>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { getUserTradeCountApi } from "@/api.js" import { getDemoUserTradeCountApi } from "@/api.js"
import { areaObj } from '../../store'; import { areaObj } from '../../store';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
let areaStore = areaObj() let areaStore = areaObj()
const route=useRoute() const route=useRoute()
getUserTradeCountApi( getDemoUserTradeCountApi(
{ ...areaStore.area, date: areaStore.date,sotre_id:route.query.sotre_id } { ...areaStore.area, date: areaStore.date,sotre_id:route.query.sotre_id }
).then(res => { ).then(res => {
// res.data.forEach(item => { // res.data.forEach(item => {
@ -22,6 +22,7 @@ getUserTradeCountApi(
transactionUsersTown.xAxis[0].data = res.data.x; transactionUsersTown.xAxis[0].data = res.data.x;
res.data.series.reverse(); res.data.series.reverse();
res.data.series.forEach((item, index) => { res.data.series.forEach((item, index) => {
transactionUsersTown.series[index].name=res.data.name
transactionUsersTown.series[index].data = item.value; transactionUsersTown.series[index].data = item.value;
}); });
initCharts('transactionUsers', transactionUsersTown) initCharts('transactionUsers', transactionUsersTown)
@ -49,7 +50,7 @@ const transactionUsersTown = {
} }
}, },
legend: { legend: {
data: ['昨日', '今日'], data: ['本年交易曲线'],
textStyle: { textStyle: {
color: "white" color: "white"
}, },
@ -92,33 +93,7 @@ const transactionUsersTown = {
], ],
series: [ series: [
{ {
name: '昨日', name: '本年交易曲线',
type: 'line',
smooth: true,
lineStyle: {
width: 0
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: '#4B5FDB'
},
{
offset: 1,
color: '#5A649D'
}
])
},
emphasis: {
focus: 'series'
},
data: []
},
{
name: '今日',
type: 'line', type: 'line',
smooth: true, smooth: true,
lineStyle: { lineStyle: {
@ -143,33 +118,6 @@ const transactionUsersTown = {
}, },
data: [] data: []
}, },
// {
// name: '',
// type: 'line',
// smooth: true,
// lineStyle: {
// width: 0
// },
// showSymbol: false,
// areaStyle: {
// opacity: 0.8,
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
// {
// offset: 0,
// color: '#583936'
// },
// {
// offset: 1,
// color: '#8b7042'
// }
// ])
// },
// emphasis: {
// focus: 'series'
// },
// data: []
// },
] ]
} }

View File

@ -11,14 +11,15 @@
import { ref, reactive, defineProps } from "vue" import { ref, reactive, defineProps } from "vue"
import { getOrderNumApi } from "@/api.js" import { getOrderNumApi } from "@/api.js"
import { areaObj } from '../../store';
let areaStore = areaObj()
const props = defineProps({ const props = defineProps({
areaCodes: Object, areaCodes: Object,
}) })
const configs = reactive( const configs = reactive(
{ {
header: ['所属地区', '日订单数', '日订单金额', '月订单数', '月订单金额'], header: ['所属地区', '本月订单金额', '上月订单金额'],
headerSize: '11px', headerSize: '11px',
align: ['center', 'center', 'center', 'center', 'center'], align: ['center', 'center', 'center', 'center', 'center'],
headerBGC: '#223B7E', headerBGC: '#223B7E',
@ -31,10 +32,10 @@ const configs = reactive(
) )
getOrderNumApi(props.areaCodes).then(res => { getOrderNumApi({...props.areaCodes,date: areaStore.date}).then(res => {
res.data.forEach(item => { res.data.forEach(item => {
configs.data.push( configs.data.push(
[item.street_name, item.dayOrderCount, item.dayOrderAmount, item.monthOrderCount, item.monthOrderAmount] [item.street_name, item.monthDayOrder, item.monthYesterOrder]
) )
}) })
}) })

View File

@ -8,12 +8,14 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, defineProps } from "vue" import { ref, reactive, defineProps } from "vue"
import { getTodayOrderAmountApi } from "@/api.js" import { getDemoTodayOrderAmountApi } from "@/api.js"
import { areaObj } from '../../store';
let areaStore = areaObj()
const props = defineProps({ const props = defineProps({
areaCodes: Object, areaCodes: Object,
}) })
getTodayOrderAmountApi(props.areaCodes).then(res => { getDemoTodayOrderAmountApi({...props.areaCodes,date: areaStore.date,}).then(res => {
res.data.forEach(item => { res.data.forEach(item => {
configs2.data.push([ configs2.data.push([
item.street_name, item.street_name,

View File

@ -9,7 +9,7 @@
<div class="bubble2"></div> <div class="bubble2"></div>
</div> </div>
<div class="product-content"> <div class="product-content">
<div style="margin-top: 14vh;position: relative;"> </div> <div style="margin-top: 14vh;position: relative;"> </div>
<div style="font-size: 10px;">NUMBER OF COMMODITIES</div> <div style="font-size: 10px;">NUMBER OF COMMODITIES</div>
<img src="/static/index/JR.png" style="width: 75%;position: relative;margin-top: 3.7vh;" alt=""> <img src="/static/index/JR.png" style="width: 75%;position: relative;margin-top: 3.7vh;" alt="">
<div style="margin-top: 2vh;position: relative;"> <span style="color: #9DD2E0;font-size: 16px;">{{ <div style="margin-top: 2vh;position: relative;"> <span style="color: #9DD2E0;font-size: 16px;">{{
@ -19,7 +19,7 @@
<div style="font-size: 9px;position: relative;display: flex;align-items: center;"> <div style="font-size: 9px;position: relative;display: flex;align-items: center;">
<img src="/static/index/ZRSJ.png" style="width: 1vw;height: 1vw;" alt=""> <img src="/static/index/ZRSJ.png" style="width: 1vw;height: 1vw;" alt="">
&nbsp; &nbsp;
总数 {{ data.totalProductCounInfo.yestertodayProductCount }} 商品总数 {{ data.totalProductCounInfo.yestertodayProductCount }}
</div> </div>
<div style="font-size: 9px;position: relative;display: flex;align-items: center;"> <div style="font-size: 9px;position: relative;display: flex;align-items: center;">
<img src="/static/index/ZHB.png" style="width: 1vw;height: 1vw;" alt=""> <img src="/static/index/ZHB.png" style="width: 1vw;height: 1vw;" alt="">
@ -38,7 +38,7 @@
<div class="bubble2"></div> <div class="bubble2"></div>
</div> </div>
<div class="product-content"> <div class="product-content">
<div style="margin-top: 14vh;position: relative;"> </div> <div style="margin-top: 14vh;position: relative;"> </div>
<div style="font-size: 10px;">NUMBER OF NEW SHOPS</div> <div style="font-size: 10px;">NUMBER OF NEW SHOPS</div>
<img src="/static/index/JRC.png" style="width: 75%;position: relative;margin-top: 3.7vh;" alt=""> <img src="/static/index/JRC.png" style="width: 75%;position: relative;margin-top: 3.7vh;" alt="">
@ -49,7 +49,7 @@
<div style="font-size: 9px;position: relative;display: flex;align-items: center;"> <div style="font-size: 9px;position: relative;display: flex;align-items: center;">
<img src="/static/index/ZRSJ.png" style="width: 1vw;height: 1vw;" alt=""> <img src="/static/index/ZRSJ.png" style="width: 1vw;height: 1vw;" alt="">
&nbsp; &nbsp;
昨日 {{ data.newProductCountInfo.yestertodayNewProductCount }} 昨日新增总 {{ data.newProductCountInfo.yestertodayNewProductCount }}
</div> </div>
<div style="font-size: 9px;position: relative;display: flex;align-items: center;"> <div style="font-size: 9px;position: relative;display: flex;align-items: center;">
<img src="/static/index/ZHB.png" style="width: 1vw;height: 1vw;" alt=""> <img src="/static/index/ZHB.png" style="width: 1vw;height: 1vw;" alt="">
@ -68,7 +68,7 @@
<div class="bubble2"></div> <div class="bubble2"></div>
</div> </div>
<div class="product-content"> <div class="product-content">
<div style="margin-top: 14vh;position: relative;"> </div> <div style="margin-top: 14vh;position: relative;"> </div>
<div style="font-size: 10px;">ACCUMULATED NUMBERS OF SHOPS</div> <div style="font-size: 10px;">ACCUMULATED NUMBERS OF SHOPS</div>
<img src="/static/index/JR.png" style="width: 75%;position: relative;margin-top: 3.7vh;" alt=""> <img src="/static/index/JR.png" style="width: 75%;position: relative;margin-top: 3.7vh;" alt="">
<div style="margin-top: 2vh;position: relative;"> <span style="color: #9DD2E0;font-size: 16px;"> <div style="margin-top: 2vh;position: relative;"> <span style="color: #9DD2E0;font-size: 16px;">
@ -82,7 +82,7 @@
<div style="font-size: 9px;position: relative;display: flex;align-items: center;"> <div style="font-size: 9px;position: relative;display: flex;align-items: center;">
<img src="/static/index/ZRSJ.png" style="width: 1vw;height: 1vw;" alt=""> <img src="/static/index/ZRSJ.png" style="width: 1vw;height: 1vw;" alt="">
&nbsp; &nbsp;
总数 累计店铺总数
{{ data.merchantCountInfo.yestertodayMerchantCount }} {{ data.merchantCountInfo.yestertodayMerchantCount }}
</div> </div>
<div style="font-size: 9px;position: relative;display: flex;align-items: center;"> <div style="font-size: 9px;position: relative;display: flex;align-items: center;">
@ -111,7 +111,7 @@
<script setup> <script setup>
import { getProductCountApi, getViewCountApi } from "@/api.js" import { getProductCountApi, getDemoViewCountApi } from "@/api.js"
import mitt from '@/view/utils/mitt.js' import mitt from '@/view/utils/mitt.js'
import { ref, reactive, defineProps, onUnmounted } from "vue" import { ref, reactive, defineProps, onUnmounted } from "vue"
import Remake from "@/components/Remake.vue" import Remake from "@/components/Remake.vue"
@ -165,7 +165,7 @@ getProductCountApi({
}) })
const initViewCount = ()=>{ const initViewCount = ()=>{
getViewCountApi({ getDemoViewCountApi({
...areaStore.area, date: areaStore.date,sotre_id:route.query.sotre_id ...areaStore.area, date: areaStore.date,sotre_id:route.query.sotre_id
}).then(res => { }).then(res => {
for (let key in res.data) { for (let key in res.data) {

View File

@ -24,6 +24,7 @@ import { ref, reactive, defineProps } from "vue"
import { getUserNumApi, getSotreCountApi } from "@/api.js" import { getUserNumApi, getSotreCountApi } from "@/api.js"
import { areaObj } from '@/store/index.js' import { areaObj } from '@/store/index.js'
const areaStore = areaObj() const areaStore = areaObj()
let dates=ref([])
@ -56,105 +57,6 @@ const initCharts = (tag, option) => {
} }
// option // option
const userChartOption = {
color: [
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 1, color: 'transparent' },
{ offset: 0, color: '#0081C3' },
])
, new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 1, color: 'transparent' },
{ offset: 0, color: '#3E54BF' },
]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 1, color: 'transparent' },
{ offset: 0, color: '#4DBFD9' },
]),
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
},
// color: transparent;
legend: {
textStyle: {
color: "white"
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: getFiveDaysFn()
}
],
yAxis: [
{
type: 'value',
splitLine: {
show: true,
lineStyle: {
type: 'dashed',//线,
color: '#256980'
}
}
},
],
series: [
{
name: '新增会员数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: [],
itemStyle: {
borderWidth: 1,
borderColor: "#384FB4",
},
backgroundStyle: {
color: ['red']
}
},
{
name: '访问用户数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: [],
itemStyle: {
borderWidth: 1,
borderColor: "#3E54BF",
},
},
{
name: '累计会员数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: [],
itemStyle: {
borderWidth: 1,
borderColor: "#4EC1DB",
},
},
]
}
// option // option
let angle = 0;// let angle = 0;//
@ -295,11 +197,12 @@ getUserNumApi(
// merchatCountList = res.data.merchatCountList // merchatCountList = res.data.merchatCountList
// merchantTotalCount = res.data.merchantTotalCount // merchantTotalCount = res.data.merchantTotalCount
// let { userCountlist } = res.data // let { userCountlist } = res.data
res.data.forEach(item => { res.data.list.forEach(item => {
userChartOption.series[0].data.push(item.newUserCount) userChartOption.series[0].data.push(item.newUserCount)
userChartOption.series[1].data.push(item.viewUserCount) userChartOption.series[1].data.push(item.viewUserCount)
userChartOption.series[2].data.push(item.totalUserCount) userChartOption.series[2].data.push(item.totalUserCount)
}); });
dates.value=res.date
initCharts('user', userChartOption) initCharts('user', userChartOption)
}) })
getSotreCountApi().then(res => { getSotreCountApi().then(res => {
@ -309,6 +212,105 @@ getSotreCountApi().then(res => {
initStoreOption(merchatCountList.slice(0, 8), merchantTotalCount) initStoreOption(merchatCountList.slice(0, 8), merchantTotalCount)
}) })
const userChartOption = {
color: [
new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 1, color: 'transparent' },
{ offset: 0, color: '#0081C3' },
])
, new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 1, color: 'transparent' },
{ offset: 0, color: '#3E54BF' },
]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{ offset: 1, color: 'transparent' },
{ offset: 0, color: '#4DBFD9' },
]),
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
},
// color: transparent;
legend: {
textStyle: {
color: "white"
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: dates
}
],
yAxis: [
{
type: 'value',
splitLine: {
show: true,
lineStyle: {
type: 'dashed',//线,
color: '#256980'
}
}
},
],
series: [
{
name: '新增会员数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: [],
itemStyle: {
borderWidth: 1,
borderColor: "#384FB4",
},
backgroundStyle: {
color: ['red']
}
},
{
name: '访问用户数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: [],
itemStyle: {
borderWidth: 1,
borderColor: "#3E54BF",
},
},
{
name: '累计会员数量',
type: 'bar',
emphasis: {
focus: 'series'
},
data: [],
itemStyle: {
borderWidth: 1,
borderColor: "#4EC1DB",
},
},
]
}
const pageFN = (Num) => { const pageFN = (Num) => {

View File

@ -31,7 +31,8 @@
import { ref, reactive, defineProps } from "vue" import { ref, reactive, defineProps } from "vue"
import { getProductCountSotreCountApi } from "@/api.js" import { getProductCountSotreCountApi } from "@/api.js"
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { areaObj } from '../../store';
let areaStore = areaObj()
const props = defineProps({ const props = defineProps({
areaCodes: Object, areaCodes: Object,
@ -223,7 +224,7 @@ const initStoreOption = (pageData, total) => {
let pageNum = ref(0) let pageNum = ref(0)
getProductCountSotreCountApi(props.areaCodes).then(res => { getProductCountSotreCountApi({...props.areaCodes,date: areaStore.date}).then(res => {
productRankingList = res.data.productRankingList productRankingList = res.data.productRankingList
// merchantRankingList = res.data.merchantRankingList // merchantRankingList = res.data.merchantRankingList
townProductCountList = res.data.townProductCountList townProductCountList = res.data.townProductCountList

View File

@ -20,11 +20,11 @@ const areaStore = areaObj()
const route=useRoute() const route=useRoute()
getSalesRankApi({ getSalesRankApi({
...areaStore.area, ...areaStore.area,
sotre_id:route.query.sotre_id sotre_id:route.query.sotre_id,
// date: areaStore.date date: areaStore.date
}).then(res => { }).then(res => {
console.log("===", res); // console.log("===", res);
// townProductCount = res.data.townProductCount // townProductCount = res.data.townProductCount
// productRankingTotal = res.data.productRankingTotal // productRankingTotal = res.data.productRankingTotal
aa(config3, res.data) aa(config3, res.data)

View File

@ -23,7 +23,7 @@
<script setup> <script setup>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { onMounted } from "vue" import { onMounted } from "vue"
import { defineProps } from "vue" import { defineProps,ref } from "vue"
import { areaObj } from '@/store/index.js' import { areaObj } from '@/store/index.js'
import { getUserNumApi } from "@/api.js" import { getUserNumApi } from "@/api.js"
@ -31,7 +31,7 @@ import { useRoute } from 'vue-router';
const areaStore = areaObj() const areaStore = areaObj()
const route=useRoute() const route=useRoute()
let dates=ref([])
const props = defineProps({ const props = defineProps({
code: Object, code: Object,
}) })
@ -42,11 +42,14 @@ getUserNumApi({
}).then(res => { }).then(res => {
res.data.forEach(item => { res.data.list.forEach(item => {
userChartOption.series[0].data.push(item.newUserCount) userChartOption.series[0].data.push(item.newUserCount)
userChartOption.series[1].data.push(item.viewUserCount) userChartOption.series[1].data.push(item.viewUserCount)
userChartOption.series[2].data.push(item.totalUserCount) userChartOption.series[2].data.push(item.totalUserCount)
// userChartOption.xAxis[0].data.push(item.date)
}) })
// userChartOption.xAxis[0].data.push(getFiveDaysFn())
dates.value=res.date
initCharts('user', userChartOption) initCharts('user', userChartOption)
}) })
@ -64,7 +67,6 @@ const getFiveDaysFn = () => {
var formattedDate = month + "." + day; var formattedDate = month + "." + day;
dateArray.unshift(formattedDate); dateArray.unshift(formattedDate);
} }
return dateArray return dateArray
} }
@ -106,7 +108,7 @@ const userChartOption = {
xAxis: [ xAxis: [
{ {
type: 'category', type: 'category',
data: getFiveDaysFn() data: dates
} }
], ],
yAxis: [ yAxis: [

View File

@ -6,31 +6,31 @@
<div class="box" :style="{ opacity: showLoading ? 0 : 1 }"> <div class="box" :style="{ opacity: showLoading ? 0 : 1 }">
<div class="body"> <div class="body">
<div class="l"> <div class="l">
<topLeft :areaCodes="areaCodes" /> <topLeft :areaCodes="areaCodes" :key="key"/>
</div> </div>
<div class="c" id=""> <div class="c" id="">
<topCenter :areaCodes="areaCodes"></topCenter> <topCenter :areaCodes="areaCodes" :key="key"></topCenter>
</div> </div>
<div class="r"> <div class="r">
<topRight :areaCodes="areaCodes"></topRight> <topRight :areaCodes="areaCodes" :key="key"></topRight>
</div> </div>
</div> </div>
<div class="foot"> <div class="foot">
<div class="foot-l"> <div class="foot-l">
<bottomLeft :areaCodes="areaCodes"></bottomLeft> <bottomLeft :areaCodes="areaCodes" :key="key"></bottomLeft>
</div> </div>
<div class="c"> <div class="c">
<bottomCenter :areaCodes="areaCodes"></bottomCenter> <bottomCenter :areaCodes="areaCodes" :key="key"></bottomCenter>
</div> </div>
<div class="r"> <div class="r">
<bottomRight :areaCodes="areaCodes"></bottomRight> <bottomRight :areaCodes="areaCodes" :key="key"></bottomRight>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive } from "vue" import { ref, reactive, onMounted, onUnmounted } from "vue"
import topLeft from "@/components/index/topLeft.vue" import topLeft from "@/components/index/topLeft.vue"
import topCenter from "@/components/index/topCenter.vue" import topCenter from "@/components/index/topCenter.vue"
import topRight from "@/components/index/topRight.vue" import topRight from "@/components/index/topRight.vue"
@ -38,8 +38,7 @@ import bottomLeft from "@/components/index/bottomLeft.vue"
import bottomCenter from "@/components/index/bottomCenter.vue" import bottomCenter from "@/components/index/bottomCenter.vue"
import bottomRight from "@/components/index/bottomRight.vue" import bottomRight from "@/components/index/bottomRight.vue"
import { areaObj } from '@/store/index.js' import { areaObj } from '@/store/index.js'
import { useRouter } from "vue-router" import mitt from '@/view/utils/mitt.js'
const router = useRouter()
@ -48,11 +47,34 @@ const areaStore = areaObj()
const areaCodes = reactive({ const areaCodes = reactive({
...areaStore.area ...areaStore.area
}) })
const key = ref(1)
let timer1 = null;
onMounted(() => {
setTimeout(() => { setTimeout(() => {
showLoading.value = false showLoading.value = false
}, 1000); }, 1000)
timer1 = setInterval(() => {
key.value++
}, 1000 * 60)
mitt.on('changeOneTime', (data) => {
key.value++;
clearInterval(timer1)
timer1 = setInterval(() => {
key.value++
}, 1000 * 60)
})
})
onUnmounted(() => {
clearInterval(timer1)
mitt.off('changeOneTime')
})
</script> </script>
<style lang="scss"> <style lang="scss">
@keyframes jump { @keyframes jump {