563 lines
16 KiB
Plaintext
563 lines
16 KiB
Plaintext
<template>
|
||
<view class="product-statistic-page">
|
||
<!-- 鍟嗗搧姒傚喌澶撮儴 -->
|
||
<view class="page-header-row">
|
||
<view class="title-wrap">
|
||
<text class="page-title">鍟嗗搧姒傚喌</text>
|
||
<view class="info-icon">?</view>
|
||
</view>
|
||
<view class="header-right">
|
||
<view class="date-picker-wrap">
|
||
<text class="calendar-emoji">馃搮</text>
|
||
<text class="date-range">2026/01/04 - 2026/02/02</text>
|
||
</view>
|
||
<button class="btn-query">鏌ヨ</button>
|
||
<button class="btn-export">瀵煎嚭</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 缁熻鎸囨爣缃戞牸 (浣跨敤缁熶竴鍝嶅簲寮忕綉鏍? -->
|
||
<view class="kpi-grid">
|
||
<view v-for="(item, index) in statItems" :key="index" class="stat-card">
|
||
<view class="stat-main">
|
||
<view class="icon-box" :style="{ backgroundColor: item.bgColor }">
|
||
<text class="stat-emoji">{{ item.emoji }}</text>
|
||
</view>
|
||
<view class="stat-content">
|
||
<text class="stat-label">{{ item.label }}</text>
|
||
<text class="stat-value">{{ item.value }}</text>
|
||
<view class="stat-compare">
|
||
<text class="compare-label">鍧忔瘮澧為暱锛?/text>
|
||
<text class="compare-val" :class="item.trendClass">
|
||
{{ item.compare }}
|
||
<text v-if="item.trend === 'up'" class="arrow">鈻?/text>
|
||
<text v-else-if="item.trend === 'down'" class="arrow">鈻?/text>
|
||
<text v-else>-</text>
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 鍥捐〃鍗$墖 -->
|
||
<view class="chart-card">
|
||
<view class="chart-header">
|
||
<view class="legend-wrap">
|
||
<view class="legend-item"><view class="dot purple"></view><text>鍟嗗搧娴忚閲?/text></view>
|
||
<view class="legend-item"><view class="dot orange"></view><text>鍟嗗搧璁垮閲?/text></view>
|
||
<view class="legend-item"><view class="dot blue"></view><text>鏀粯閲戦</text></view>
|
||
<view class="legend-item"><view class="dot green"></view><text>閫€娆鹃噾棰?/text></view>
|
||
</view>
|
||
<view class="download-icon">
|
||
<text class="download-emoji">馃摜</text>
|
||
</view>
|
||
</view>
|
||
<view class="chart-main">
|
||
<EChartsView v-if="chartOption != null" :option="chartOption" class="main-chart" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 鍟嗗搧鎺掕 -->
|
||
<view class="ranking-card">
|
||
<view class="ranking-header">
|
||
<text class="ranking-title">鍟嗗搧鎺掕</text>
|
||
<view class="ranking-filters">
|
||
<view class="mock-select-wrap">
|
||
<text class="select-val">娴忚閲?/text>
|
||
<text class="select-arrow">鈻?/text>
|
||
</view>
|
||
<view class="date-picker-wrap">
|
||
<text class="calendar-emoji">馃搮</text>
|
||
<text class="date-range">2026/01/04 - 2026/02/02</text>
|
||
</view>
|
||
<button class="btn-query small">鏌ヨ</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="ranking-table">
|
||
<view class="table-header">
|
||
<text class="th col-id">ID</text>
|
||
<text class="th col-img">鍟嗗搧鍥剧墖</text>
|
||
<text class="th col-name">鍟嗗搧鍚嶇О</text>
|
||
<text class="th col-num">娴忚閲?/text>
|
||
<text class="th col-num">璁垮鏁?/text>
|
||
<text class="th col-num">鍔犺喘浠舵暟</text>
|
||
<text class="th col-num">涓嬪崟浠舵暟</text>
|
||
<text class="th col-num">鏀粯浠舵暟</text>
|
||
<text class="th col-num">鏀粯閲戦</text>
|
||
<text class="th col-num">鏀惰棌鏁?/text>
|
||
<text class="th col-num wide">璁垮-鏀粯杞寲鐜?%)</text>
|
||
</view>
|
||
<view v-for="(item, index) in rankingList" :key="index" class="table-row">
|
||
<text class="td col-id">{{ item.id }}</text>
|
||
<view class="td col-img">
|
||
<image class="product-img" :src="item.image" mode="aspectFill" />
|
||
</view>
|
||
<view class="td col-name">
|
||
<text class="product-name-txt">{{ item.name }}</text>
|
||
</view>
|
||
<text class="td col-num">{{ item.views }}</text>
|
||
<text class="td col-num">{{ item.visitors }}</text>
|
||
<text class="td col-num">{{ item.cartCount }}</text>
|
||
<text class="td col-num">{{ item.orderCount }}</text>
|
||
<text class="td col-num">{{ item.payCount }}</text>
|
||
<text class="td col-num">{{ item.payAmount }}</text>
|
||
<text class="td col-num">{{ item.favCount }}</text>
|
||
<text class="td col-num wide">{{ item.conversion }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted } from 'vue'
|
||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||
|
||
const statItems = ref([
|
||
{ label: '鍟嗗搧娴忚閲?, value: '7576', compare: '0.93%', trend: 'up', trendClass: 'up-red', bgColor: '#e6f7ff', emoji: '馃憗锔? },
|
||
{ label: '鍟嗗搧璁垮閲?, value: '765', compare: '0.79%', trend: 'up', trendClass: 'up-red', bgColor: '#f6ffed', emoji: '馃懁' },
|
||
{ label: '鏀粯浠舵暟', value: '322', compare: '-49.52%', trend: 'down', trendClass: 'down-green', bgColor: '#fff7e6', emoji: '馃泹锔? },
|
||
{ label: '鏀粯閲戦', value: '443254.62', compare: '-63.62%', trend: 'down', trendClass: 'down-green', bgColor: '#f9f0ff', emoji: '馃挵' },
|
||
{ label: '閫€娆句欢鏁?, value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#e6f7ff', emoji: '馃攧' },
|
||
{ label: '閫€娆鹃噾棰?, value: '0', compare: '0.00%', trend: 'none', trendClass: 'none-gray', bgColor: '#f6ffed', emoji: '馃挻' }
|
||
])
|
||
|
||
const rankingList = ref([
|
||
{
|
||
id: 963,
|
||
image: 'https://img1.baidu.com/it/u=254065646,3100346083&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||
name: 'UR2024澶忓鏂版濂宠澶嶅彜绾姘涘洿鎰熶竴瀛楄偐鐭T鎭よ~UWG440060',
|
||
views: 1200,
|
||
visitors: 246,
|
||
cartCount: 74,
|
||
orderCount: 214,
|
||
payCount: 180,
|
||
payAmount: '11877.49',
|
||
favCount: 13,
|
||
conversion: 18
|
||
},
|
||
{
|
||
id: 116,
|
||
image: 'https://img2.baidu.com/it/u=3775079632,546700868&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||
name: '鐖卞鑹烘櫤鑳?濂囬亣LT01 鎶曞奖浠?瀹剁敤鍗у瓒呴珮娓呮墜鏈轰究鎼烘姇褰辨満 (4K瓒呮竻 鏀寔渚ф姇 鎵嬫満鍚屽睆 鍗庝负涓€纰板嵆鎶?',
|
||
views: 959,
|
||
visitors: 376,
|
||
cartCount: 1,
|
||
orderCount: 60,
|
||
payCount: 29,
|
||
payAmount: '26.00',
|
||
favCount: 6,
|
||
conversion: 7
|
||
},
|
||
{
|
||
id: 48,
|
||
image: 'https://img0.baidu.com/it/u=1762118431,3101886131&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
|
||
name: '闃胯开杈炬柉瀹樼綉 adidas BBALL CAP COT 鐢峰コ璁粌杩愬姩甯藉瓙FQ5270 浼犲澧ㄦ按钃?浼犲澧ㄦ按钃?鐧?XL',
|
||
views: 758,
|
||
visitors: 207,
|
||
cartCount: 63,
|
||
orderCount: 67,
|
||
payCount: 17,
|
||
payAmount: '1409.30',
|
||
favCount: 4,
|
||
conversion: 7
|
||
},
|
||
{
|
||
id: 108,
|
||
image: 'https://img2.baidu.com/it/u=3033501986,2204481084&fm=253&fmt=auto&app=138&f=JPEG?w=569&h=500',
|
||
name: 'FOMIX 铔嬪3妞?杩涘彛澶村眰鐗涚毊姗欒壊鍗曚汉娌欏彂妞匛gg chair璁捐甯堝笀鍗曟鍗曟矙澶村眰鐗涚毊/鍗曟',
|
||
views: 730,
|
||
visitors: 216,
|
||
cartCount: 26999,
|
||
orderCount: 327,
|
||
payCount: 14,
|
||
payAmount: '66197.00',
|
||
favCount: 4,
|
||
conversion: 6
|
||
}
|
||
])
|
||
|
||
const chartOption = ref<any>({})
|
||
|
||
onMounted(() => {
|
||
setTimeout(() => {
|
||
initChart()
|
||
}, 300)
|
||
})
|
||
|
||
function toPlainObject(obj: any): any {
|
||
if (obj == null) return null
|
||
if (typeof obj !== 'object') return obj
|
||
if (Array.isArray(obj)) {
|
||
return obj.map((item: any) : any => toPlainObject(item))
|
||
}
|
||
const plain: any = {}
|
||
const keys = Object.keys(obj)
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const key = keys[i]
|
||
const value = obj[key]
|
||
if (typeof value === 'function' || key.startsWith('_') || key === 'toJSON') {
|
||
continue
|
||
}
|
||
if (value != null && typeof value === 'object') {
|
||
plain[key] = toPlainObject(value)
|
||
} else {
|
||
plain[key] = value
|
||
}
|
||
}
|
||
return plain
|
||
}
|
||
|
||
function initChart() {
|
||
const dates = [
|
||
'01-04', '01-05', '01-06', '01-07', '01-08', '01-09', '01-10', '01-11', '01-12', '01-13',
|
||
'01-14', '01-15', '01-16', '01-17', '01-18', '01-19', '01-20', '01-21', '01-22', '01-23',
|
||
'01-24', '01-25', '01-26', '01-27', '01-28', '01-29', '01-30', '01-31', '02-01', '02-02'
|
||
]
|
||
|
||
const option = {
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
backgroundColor: 'rgba(50, 50, 50, 0.7)',
|
||
padding: [10, 15],
|
||
textStyle: { color: '#fff' },
|
||
formatter: (params: any[]) : string => {
|
||
let res = `<div style="font-size:12px; color:#ccc; margin-bottom:5px;">${params[0].name}</div>`
|
||
params.forEach(p => {
|
||
res += `<div style="display:flex; align-items:center;">
|
||
<div style="width:8px; height:8px; border-radius:50%; background:${p.color}; margin-right:8px;"></div>
|
||
<span>${p.seriesName}: ${p.value}</span>
|
||
</div>`
|
||
})
|
||
return res
|
||
}
|
||
},
|
||
grid: {
|
||
left: '3%',
|
||
right: '4%',
|
||
bottom: '5%',
|
||
top: '10%',
|
||
containLabel: true
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: dates,
|
||
axisLine: { lineStyle: { color: '#f0f0f0' } },
|
||
axisLabel: { color: '#8c8c8c', fontSize: 11 },
|
||
axisTick: { show: false }
|
||
},
|
||
yAxis: [
|
||
{
|
||
type: 'value',
|
||
name: '閲戦',
|
||
nameTextStyle: { color: '#8c8c8c', padding: [0, 30, 0, 0] },
|
||
splitLine: { lineStyle: { type: 'dashed', color: '#f0f0f0' } },
|
||
axisLabel: { color: '#8c8c8c' }
|
||
},
|
||
{
|
||
type: 'value',
|
||
name: '鏁伴噺',
|
||
nameTextStyle: { color: '#8c8c8c', padding: [0, 0, 0, 30] },
|
||
splitLine: { show: false },
|
||
axisLabel: { color: '#8c8c8c' }
|
||
}
|
||
],
|
||
series: [
|
||
{
|
||
name: '鍟嗗搧娴忚閲?,
|
||
type: 'line',
|
||
yAxisIndex: 1,
|
||
smooth: true,
|
||
showSymbol: false,
|
||
itemStyle: { color: '#b37feb' },
|
||
lineStyle: { width: 2 },
|
||
data: [90, 110, 115, 100, 95, 80, 60, 40, 70, 85, 75, 65, 70, 80, 100, 120, 110, 90, 60, 95, 115, 110, 85, 50, 45, 55, 75]
|
||
},
|
||
{
|
||
name: '鍟嗗搧璁垮閲?,
|
||
type: 'line',
|
||
yAxisIndex: 1,
|
||
smooth: true,
|
||
showSymbol: false,
|
||
itemStyle: { color: '#ffbb96' },
|
||
lineStyle: { width: 2 },
|
||
data: [15, 12, 10, 8, 11, 14, 13, 8, 9, 11, 10, 15, 12, 11, 9, 12, 14, 15, 11, 10, 13, 15, 11, 8, 12, 10, 14]
|
||
},
|
||
{
|
||
name: '鏀粯閲戦',
|
||
type: 'bar',
|
||
barWidth: '25%',
|
||
itemStyle: { color: '#1890ff' },
|
||
data: [10, 5, 8, 0, 145, 15, 5, 0, 0, 0, 0, 5, 30, 0, 15, 20, 100, 20, 25, 5, 1, 3, 70, 5, 10, 5, 15, 10]
|
||
},
|
||
{
|
||
name: '閫€娆鹃噾棰?,
|
||
type: 'bar',
|
||
barWidth: '25%',
|
||
itemStyle: { color: '#52c41a' },
|
||
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||
}
|
||
],
|
||
markLine: {
|
||
silent: true,
|
||
symbol: ['none', 'none'],
|
||
label: { show: false },
|
||
lineStyle: { color: '#bfbfbf', type: 'dashed' },
|
||
data: [{ yAxis: 145853.16 }]
|
||
}
|
||
}
|
||
|
||
chartOption.value = toPlainObject(option)
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.product-statistic-page {
|
||
/* padding removed */
|
||
|
||
|
||
}
|
||
|
||
.page-header-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.title-wrap {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.page-title { font-size: 16px; font-weight: bold; color: #333; }
|
||
|
||
.info-icon {
|
||
width: 14px; height: 14px;
|
||
border-radius: 50%; border: 1px solid #999;
|
||
color: #999; font-size: 10px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
|
||
.header-right {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.date-picker-wrap {
|
||
background: #fff;
|
||
border: 1px solid #d9d9d9;
|
||
padding: 6px 12px;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.calendar-emoji { font-size: 14px; }
|
||
.date-range { font-size: 14px; color: #595959; }
|
||
|
||
.btn-query { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
|
||
.btn-export { background: #1890ff; color: #fff; font-size: 14px; height: 32px; padding: 0 15px; border-radius: 4px; border: none; }
|
||
|
||
/* stat-grid 宸插簾寮冿紝鐢卞叏灞€ kpi-grid 鎺ョ */
|
||
.stat-card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.stat-main {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.icon-box {
|
||
width: 48px; height: 48px;
|
||
border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
|
||
.stat-emoji { font-size: 24px; }
|
||
|
||
.stat-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.stat-label { font-size: 13px; color: #8c8c8c; margin-bottom: 4px; }
|
||
.stat-value { font-size: 24px; font-weight: bold; color: #262626; margin-bottom: 4px; }
|
||
|
||
.stat-compare {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
font-size: 12px;
|
||
}
|
||
.compare-label { color: #8c8c8c; }
|
||
.up-red { color: #ff4d4f; }
|
||
.down-green { color: #52c41a; }
|
||
.none-gray { color: #8c8c8c; }
|
||
.arrow { font-size: 10px; margin-left: 2px; }
|
||
|
||
.chart-card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
}
|
||
|
||
.chart-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: center;
|
||
align-items: center;
|
||
position: relative;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.legend-wrap {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 24px;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text { font-size: 12px; color: #8c8c8c; }
|
||
}
|
||
|
||
.dot { width: 10px; height: 10px; border-radius: 2px; }
|
||
.purple { background-color: #b37feb; }
|
||
.orange { background-color: #ffbb96; }
|
||
.blue { background-color: #1890ff; }
|
||
.green { background-color: #52c41a; }
|
||
|
||
.download-icon {
|
||
position: absolute;
|
||
right: 0;
|
||
}
|
||
.download-emoji { font-size: 18px; opacity: 0.6; }
|
||
|
||
.chart-main {
|
||
height: 400px;
|
||
width: 100%;
|
||
}
|
||
.main-chart { width: 100%; height: 100%; }
|
||
|
||
/* 鍟嗗搧鎺掕 */
|
||
.ranking-card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
padding: 24px;
|
||
margin-top: 16px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.ranking-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.ranking-title { font-size: 16px; font-weight: bold; color: #333; }
|
||
|
||
.ranking-filters {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.mock-select-wrap {
|
||
border: 1px solid #d9d9d9;
|
||
border-radius: 4px;
|
||
padding: 4px 12px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 120px;
|
||
}
|
||
.select-val { font-size: 14px; color: #595959; flex: 1; }
|
||
.select-arrow { font-size: 10px; color: #bfbfbf; }
|
||
|
||
.btn-query.small { height: 32px; font-size: 13px; margin: 0; }
|
||
|
||
.ranking-table {
|
||
border: 1px solid #f0f0f0;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.table-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
background-color: #e6f7ff;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.th {
|
||
padding: 12px 8px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: #595959;
|
||
text-align: center;
|
||
}
|
||
|
||
.table-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
align-items: stretch;
|
||
}
|
||
.table-row:last-child { border-bottom: none; }
|
||
|
||
.td {
|
||
padding: 16px 8px;
|
||
font-size: 13px;
|
||
color: #262626;
|
||
text-align: center;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 鍒楀搴﹂厤缃?*/
|
||
.col-id { width: 60px; }
|
||
.col-img { width: 100px; }
|
||
.col-name { flex: 1; text-align: left; justify-content: flex-start; }
|
||
.col-num { width: 80px; }
|
||
.col-num.wide { width: 120px; }
|
||
|
||
.product-img {
|
||
width: 48px;
|
||
height: 48px;
|
||
background: #f5f5f5;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.product-name-txt {
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
color: #262626;
|
||
}
|
||
</style>
|
||
|
||
|