登录注册/数据分析

This commit is contained in:
comlibmb
2026-01-22 21:15:02 +08:00
parent 75fad97d5d
commit fdbee0fa32
41 changed files with 5812 additions and 2102 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>