登录注册/数据分析
This commit is contained in:
60
components/analytics/AnalyticsBarMini.uvue
Normal file
60
components/analytics/AnalyticsBarMini.uvue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<view class="chart-wrap" :style="{ height: heightPx }">
|
||||
<EChartsView :option="chartOption" class="chart" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
type Item = { name: string; value: number }
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EChartsView
|
||||
},
|
||||
props: {
|
||||
items: { type: Array, default: () => [] },
|
||||
height: { type: Number, default: 300 }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
heightPx: '300px',
|
||||
chartOption: {} as any
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
items: { handler() { this.updateOption() }, deep: true },
|
||||
height: {
|
||||
handler() {
|
||||
this.heightPx = `${this.height}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.heightPx = `${this.height}px`
|
||||
this.updateOption()
|
||||
},
|
||||
methods: {
|
||||
updateOption() {
|
||||
const x = (this.items as Array<Item>).map((it) => it.name)
|
||||
const y = (this.items as Array<Item>).map((it) => {
|
||||
const n = Number(it.value)
|
||||
return isFinite(n) ? n : 0
|
||||
})
|
||||
this.chartOption = {
|
||||
grid: { left: 80, right: 24, top: 18, bottom: 18 },
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
xAxis: { type: 'value', axisLabel: { color: 'rgba(0,0,0,0.55)' }, splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)' } } },
|
||||
yAxis: { type: 'category', data: x, axisTick: { show: false }, axisLabel: { color: 'rgba(0,0,0,0.65)' } },
|
||||
series: [{ type: 'bar', data: y, barWidth: 14, itemStyle: { borderRadius: 6 } }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.chart-wrap { width: 100%; }
|
||||
.chart { width: 100%; height: 100%; }
|
||||
</style>
|
||||
116
components/analytics/AnalyticsComboChart.uvue
Normal file
116
components/analytics/AnalyticsComboChart.uvue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<view class="chart-wrap" :style="{ height: heightPx }">
|
||||
<EChartsView :option="chartOption" class="chart" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EChartsView
|
||||
},
|
||||
props: {
|
||||
xLabels: { type: Array, default: () => [] },
|
||||
gmv: { type: Array, default: () => [] },
|
||||
orders: { type: Array, default: () => [] },
|
||||
height: { type: Number, default: 320 }
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
heightPx: '320px',
|
||||
chartOption: {} as any
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
xLabels: { handler() { this.updateOption() }, deep: true },
|
||||
gmv: { handler() { this.updateOption() }, deep: true },
|
||||
orders: { handler() { this.updateOption() }, deep: true },
|
||||
height: {
|
||||
handler() {
|
||||
this.heightPx = `${this.height}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.heightPx = `${this.height}px`
|
||||
this.updateOption()
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateOption() {
|
||||
const x = (this.xLabels as Array<any>).map((s) => String(s))
|
||||
const bar = (this.gmv as Array<any>).map((v) => {
|
||||
const n = Number(v)
|
||||
return isFinite(n) ? n : 0
|
||||
})
|
||||
const line = (this.orders as Array<any>).map((v) => {
|
||||
const n = Number(v)
|
||||
return isFinite(n) ? n : 0
|
||||
})
|
||||
|
||||
this.chartOption = {
|
||||
grid: { left: 44, right: 44, top: 24, bottom: 36 },
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
legend: { top: 0, left: 0, itemWidth: 10, itemHeight: 10, textStyle: { fontSize: 12 } },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: x,
|
||||
axisTick: { show: false },
|
||||
axisLine: { lineStyle: { color: 'rgba(0,0,0,0.12)' } },
|
||||
axisLabel: { color: 'rgba(0,0,0,0.55)' }
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: 'GMV',
|
||||
axisLine: { show: false },
|
||||
splitLine: { lineStyle: { color: 'rgba(0,0,0,0.06)' } },
|
||||
axisLabel: { color: 'rgba(0,0,0,0.55)' }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '订单',
|
||||
axisLine: { show: false },
|
||||
splitLine: { show: false },
|
||||
axisLabel: { color: 'rgba(0,0,0,0.55)' }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'GMV',
|
||||
type: 'bar',
|
||||
data: bar,
|
||||
barWidth: 14,
|
||||
itemStyle: { borderRadius: [6, 6, 0, 0] }
|
||||
},
|
||||
{
|
||||
name: '订单数',
|
||||
type: 'line',
|
||||
yAxisIndex: 1,
|
||||
data: line,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: { width: 2 }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.chart-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
70
components/analytics/AnalyticsDonutChart.uvue
Normal file
70
components/analytics/AnalyticsDonutChart.uvue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<view class="chart-wrap" :style="{ height: heightPx }">
|
||||
<EChartsView :option="chartOption" class="chart" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
type Item = { name: string; value: number }
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EChartsView
|
||||
},
|
||||
props: {
|
||||
items: { type: Array, default: () => [] },
|
||||
height: { type: Number, default: 300 }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
heightPx: '300px',
|
||||
chartOption: {} as any
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
items: { handler() { this.updateOption() }, deep: true },
|
||||
height: {
|
||||
handler() {
|
||||
this.heightPx = `${this.height}px`
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.heightPx = `${this.height}px`
|
||||
this.updateOption()
|
||||
},
|
||||
methods: {
|
||||
updateOption() {
|
||||
const data = (this.items as Array<Item>).map((it) => ({
|
||||
name: it.name,
|
||||
value: (() => {
|
||||
const n = Number(it.value)
|
||||
return isFinite(n) ? n : 0
|
||||
})()
|
||||
}))
|
||||
this.chartOption = {
|
||||
tooltip: { trigger: 'item' },
|
||||
legend: { left: 0, bottom: 0, itemWidth: 10, itemHeight: 10, textStyle: { fontSize: 12 } },
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['55%', '75%'],
|
||||
center: ['50%', '45%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: { show: true, formatter: '{b}\n{d}%' },
|
||||
labelLine: { length: 10, length2: 10 },
|
||||
data
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.chart-wrap { width: 100%; }
|
||||
.chart { width: 100%; height: 100%; }
|
||||
</style>
|
||||
57
components/analytics/ChartCard.uvue
Normal file
57
components/analytics/ChartCard.uvue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<view class="card">
|
||||
<view class="hd">
|
||||
<view class="left">
|
||||
<text class="t">{{ title }}</text>
|
||||
<text class="d" v-if="desc">{{ desc }}</text>
|
||||
</view>
|
||||
<slot name="extra"></slot>
|
||||
</view>
|
||||
<view class="bd">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
desc: { type: String, default: '' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
border: 1rpx solid rgba(17, 17, 17, 0.08);
|
||||
border-radius: 16rpx;
|
||||
padding: 14rpx;
|
||||
background: #fff;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hd {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.t {
|
||||
font-size: 24rpx;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.d {
|
||||
font-size: 20rpx;
|
||||
color: rgba(17, 17, 17, 0.55);
|
||||
margin-top: 4rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bd {
|
||||
padding-top: 4rpx;
|
||||
}
|
||||
</style>
|
||||
98
components/analytics/KpiCard.uvue
Normal file
98
components/analytics/KpiCard.uvue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<view class="kpi" :class="tone">
|
||||
<text class="t">{{ title }}</text>
|
||||
<text class="v">{{ value }}</text>
|
||||
|
||||
<view class="row" v-if="!deltaHidden">
|
||||
<text class="delta" :class="delta >= 0 ? 'pos' : 'neg'">
|
||||
{{ delta >= 0 ? '+' : '' }}{{ delta.toFixed(1) }}%
|
||||
</text>
|
||||
<text class="s">{{ subtitle }}</text>
|
||||
</view>
|
||||
|
||||
<text class="s" v-else>{{ subtitle }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
export default {
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
value: { type: String, default: '' },
|
||||
subtitle: { type: String, default: '' },
|
||||
delta: { type: Number, default: 0 },
|
||||
tone: { type: String, default: 'danger' },
|
||||
deltaHidden: { type: Boolean, default: false }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.kpi {
|
||||
width: calc(50% - 7rpx);
|
||||
padding: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.t {
|
||||
font-size: 22rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.v {
|
||||
font-size: 36rpx;
|
||||
font-weight: 800;
|
||||
margin-top: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.s {
|
||||
font-size: 20rpx;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.delta {
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.delta.pos {
|
||||
}
|
||||
|
||||
.delta.neg {
|
||||
}
|
||||
|
||||
/* tones */
|
||||
.danger {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ff4d4f 100%);
|
||||
}
|
||||
|
||||
.teal {
|
||||
background: linear-gradient(135deg, #4ecdc4 0%, #44a08d 100%);
|
||||
}
|
||||
|
||||
.green {
|
||||
background: linear-gradient(135deg, #a8e6cf 0%, #7fcdbb 100%);
|
||||
}
|
||||
|
||||
.amber {
|
||||
background: linear-gradient(135deg, #ffd93d 0%, #ffa07a 100%);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.kpi {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
49
components/analytics/PeriodTabs.uvue
Normal file
49
components/analytics/PeriodTabs.uvue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="it in items"
|
||||
:key="it.value"
|
||||
class="tab"
|
||||
:class="value === it.value ? 'on' : ''"
|
||||
@click="pick(it.value)"
|
||||
>
|
||||
<text>{{ it.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
export default {
|
||||
props: {
|
||||
value: { type: String, default: '30d' },
|
||||
items: { type: Array, default: () => [] }
|
||||
},
|
||||
methods: {
|
||||
pick(v: string) {
|
||||
this.$emit('change', v)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: rgba(17, 17, 17, 0.04);
|
||||
border-radius: 999rpx;
|
||||
padding: 6rpx;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10rpx 16rpx;
|
||||
border-radius: 999rpx;
|
||||
color: rgba(17, 17, 17, 0.65);
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.tab.on {
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(135deg, #ff4d4f 0%, #ff7a45 100%);
|
||||
}
|
||||
</style>
|
||||
23
components/analytics/charts/AreaLine.uvue
Normal file
23
components/analytics/charts/AreaLine.uvue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<view class="wrap" :style="{ height: height + 'px' }">
|
||||
<EChartsView :option="option" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
export default {
|
||||
components: { EChartsView },
|
||||
props: {
|
||||
option: { type: Object, default: () => ({}) },
|
||||
height: { type: Number, default: 280 }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wrap {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
23
components/analytics/charts/ComboBarLine.uvue
Normal file
23
components/analytics/charts/ComboBarLine.uvue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<view class="wrap" :style="{ height: height + 'px' }">
|
||||
<EChartsView :option="option" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
export default {
|
||||
components: { EChartsView },
|
||||
props: {
|
||||
option: { type: Object, default: () => ({}) },
|
||||
height: { type: Number, default: 320 }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wrap {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
23
components/analytics/charts/DonutPie.uvue
Normal file
23
components/analytics/charts/DonutPie.uvue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<view class="wrap" :style="{ height: height + 'px' }">
|
||||
<EChartsView :option="option" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
export default {
|
||||
components: { EChartsView },
|
||||
props: {
|
||||
option: { type: Object, default: () => ({}) },
|
||||
height: { type: Number, default: 280 }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.wrap {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user