TDesign组件-1
工作台
<!-- 首页 -->
<template>
<div class="container home-wrapper">
<!-- 顶部 card -->
<top-panel :top-panel-data="topPanelData"/>
<!-- 经营分析 -->
<Operate
@fetchData="fetchData"
@exportStatistics="exportStatistics"
></Operate>
<!-- end -->
<!-- 分析 -->
<div class="analysis">
<div class="leftCard">
<div class="title">订单分析</div>
<div class="cardBox">
<div class="card">
<div class="body">
<div class="title">
有效订单
<div class="description">
<span class="beizhu"></span>
<span class="hover"
>在统计时间内,订单状态为已完成的订单数。(订单被取消、退款、关闭状态均不属于有效订单)</span
>
</div>
</div>
<div class="num">
{
{ data?.effectiveOrderNum }}<span>笔</span>
</div>
</div>
<div class="line"></div>
</div>
<div class="card">
<div class="body">
<div class="title">取消订单数</div>
<div class="num">
{
{ data?.cancelOrderNum || 0 }}<span>笔</span>
</div>
</div>
<div class="line"></div>
</div>
<div class="card">
<div class="body">
<div class="title">关闭订单数</div>
<div class="num">{
{ data?.closeOrderNum }}<span>笔</span></div>
</div>
<div class="line"></div>
</div>
</div>
</div>
<div class="rightCard">
<div class="title">营业分析</div>
<div class="cardBox">
<div class="card">
<div class="body">
<div class="title">
实付单均价
<div class="description">
<span class="beizhu"></span>
<span class="hover"
>在统计时间内,平均每单实际支付额(不包含失效订单)</span
>
</div>
</div>
<div class="num">
{
{ data?.realPayAveragePrice }}<span>元</span>
</div>
</div>
<div class="line"></div>
</div>
<div class="card">
<div class="body">
<div class="title">
有效订单总额
<div class="description">
<span class="beizhu"></span>
<span class="hover"
>在统计时间内,有效订单的总订单交易额</span
>
</div>
</div>
<div class="num">
{
{ data?.effectiveOrderTotalAmount }}<span>元</span>
</div>
</div>
<div class="line"></div>
</div>
</div>
</div>
</div>
<!-- 中部图表 -->
<middle-chart
class="row-container"
:middle-chart-data="data?.ordersTrend"
/>
<!-- 用户分析 -->
<div class="operate">
用户分析<span>{
{ nowMonthTime[0] }} - {
{ nowMonthTime[1] }}</span>
</div>
<!-- 用户分析 - 漏斗图和双折线图 -->
<output-overview
class="row-container"
:output-overview-data="outputOverviewData"
/>
</div>
</template>
<script setup lang="ts">
import {
onMounted, ref, watch} from 'vue'
import {
useRoute} from 'vue-router'
import dayjs from 'dayjs'
import {
getDashBoardData, exportStatisticsData} from '@/api/detail'
import TopPanel from './components/TopPanel.vue'
import MiddleChart from './components/MiddleChart.vue'
import OutputOverview from './components/OutputOverview.vue'
import Operate from './components/operate.vue'
import {
MessagePlugin} from 'tdesign-vue-next'
const route = useRoute()
// 当月时间1号到最后一天
const nowMonthTime = ref([
dayjs().startOf('month').format('YYYY-MM-DD'),
dayjs().subtract(1, 'day').format('YYYY-MM-DD')
])
const data = ref() // 日期
const dateRange = ref([
dayjs().subtract(1, 'month').format('YYYY-MM-DD') + ' 00:00:00',
dayjs().subtract(0, 'day').format('YYYY-MM-DD') + ' 23:59:59'
])
const topPanelData = ref({
evaluationCount: 0,
yesterdayEvaluationCount: 0,
replyCount: 0,
goodLevelRate: '',
yesterdayReplyCount: 0,
auditPendingCount: 0
}) // 顶部card数据
const outputOverviewData = ref([]) // 综合评分详情概览数据
// 获取tab栏切换数据
// 生命周期
onMounted(() => {
fetchData(dateRange.value)
})
// 刷新
watch(
() => route.query,
() => {
fetchData(dateRange.value)
}
)
// 获取数据
const fetchData = async (val) => {
if (val[0].length <= 10) {
val[0] = val[0] + ' 00:00:00'
val[1] = val[1] + ' 23:59:59'
}
await getDashBoardData({
maxTime: val[1],
minTime: val[0]
})
.then((res) => {
if (res.code === 200) {
data.value = res.data
} else {
MessagePlugin.error(res.msg)
}
})
.catch((err) => {
console.log(err)
})
}
// 导出统计数据
const exportStatistics = async (val) => {
if (val[0].length <= 10) {
val[0] = val[0] + ' 00:00:00'
val[1] = val[1] + ' 23:59:59'
}
await exportStatisticsData({
maxTime: val[1],
minTime: val[0]
}).then((res) => {
let text = decodeURIComponent(res.headers['content-disposition'])
const parts = text.split("utf-8''")
const blob = new Blob([res.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const objectUrl = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = objectUrl
link.download = parts[1] // 设置文件名
link.click()
URL.revokeObjectURL(objectUrl)
})
}
</script>
<style lang="less" scoped src="./index.less"></style>
总结
1. 模板部分 (
<template>
)页面结构
- 顶部卡片 (
top-panel
):展示顶部面板数据。- 经营分析 (
Operate
):包含操作按钮,如获取数据和导出统计数据。- 订单分析:
- 左侧卡片 (
leftCard
):展示有效订单数、取消订单数和关闭订单数。- 右侧卡片 (
rightCard
):展示实付单均价和有效订单总额。- 中部图表 (
middle-chart
):展示订单趋势图。- 用户分析:展示用户分析的时间范围。
- 漏斗图和双折线图 (
output-overview
):展示综合评分详情概览数据。数据绑定
- 使用了
v-bind
和v-on
指令来传递数据和事件处理函数。- 动态绑定数据到模板中的元素,例如
{ { data?.effectiveOrderNum }}
。2. 脚本部分 (
<script setup lang="ts">
)导入依赖
import { onMounted, ref, watch } from 'vue' import { useRoute } from 'vue-router' import dayjs from 'dayjs' import { getDashBoardData, exportStatisticsData } from '@/api/detail' import TopPanel from './components/TopPanel.vue' import MiddleChart from './components/MiddleChart.vue' import OutputOverview from './components/OutputOverview.vue' import Operate from './components/operate.vue' import { MessagePlugin } from 'tdesign-vue-next'
定义响应式数据
nowMonthTime
: 当月时间范围(从月初到最后一天)。data
: 存储从 API 获取的数据。dateRange
: 默认日期范围(上个月初到今天)。topPanelData
: 顶部卡片的数据。outputOverviewData
: 综合评分详情概览数据。生命周期钩子
onMounted
: 页面加载时调用fetchData
方法获取初始数据。watch
: 监听路由查询参数的变化,当变化时重新获取数据。方法
fetchData
:
- 根据传入的日期范围格式化时间字符串。
- 调用
getDashBoardData
API 获取数据并更新data
值。- 处理成功和失败的响应。
exportStatistics
:
- 格式化日期字符串。
- 调用
exportStatisticsData
API 导出统计数据。- 处理下载文件的逻辑,包括解码文件名和创建下载链接。
3. 样式部分 (
<style lang="less" scoped src="./index.less"></style>
)
- 使用 Less 作为样式预处理器,并且样式是作用域限定的(
scoped
),确保样式只应用于当前组件。- 样式文件路径为
./index.less
。
订单数趋势图表
<t-row>
和<t-col>
:<t-row>
是行容器,<t-col>
是列容器。:gutter="16"
设置了列之间的间距为 16px,:xs="12"
和:xl="12"
表示在超小屏幕(xs)和特大屏幕(xl)上都占用12个栅格宽度(总共有24个栅格)<t-card>
:卡片组件,用于包裹内容并提供标题和边框。title="订单数趋势"
设置了卡片的标题,class="dashboard-chart-card"
添加了自定义样式类。id="monitorContainer"
和ref="monitorContainer"
:给 div 分配了一个 ID:style
:使用内联样式动态设置了宽度和高度。宽度被设置为 99.6%,而高度则是根据 resizeTime 计算得出的,dashboard-chart-container
:这是一个自定义的CSS类,用于设置图表容器的样式。
使用:
import MiddleChart from './components/MiddleChart.vue'
<middle-chart
class="row-container"
:middle-chart-data="data?.ordersTrend"
/>
component组件:
<!-- 订单数趋势图表 -->
<template>
<t-row :gutter="16" class="row-container">
<t-col :xs="12" :xl="12">
<t-card title="订单数趋势" class="dashboard-chart-card">
<div
id="monitorContainer"
ref="monitorContainer"
class="dashboard-chart-container"
:style="{ width: '99.6%', height: `${resizeTime * 326}px` }"
/>
</t-card>
</t-col>
</t-row>
</template>
<script setup lang="ts">
import {
onMounted, ref, onUnmounted, nextTick, computed, watch } from 'vue'
import * as echarts from 'echarts/core'
import {
TooltipComponent,
LegendComponent,
GridComponent
} from 'echarts/components'
import {
LineChart } from 'echarts/charts'
import {
CanvasRenderer } from 'echarts/renderers'
import {
useSettingStore } from '@/store'
import {
dateTag } from '@/utils/charts'
import {
getLineChartDataSet } from '../index'
echarts.use([
TooltipComponent,
LegendComponent,
GridComponent,
LineChart,
CanvasRenderer
])
// 接收父组件传递的数据
const props = defineProps({
middleChartData: {
type: Array,
default: () => {
return []
}
}
})
// 深拷贝
const dataTime = JSON.parse(JSON.stringify(dateTag))
const store = useSettingStore()
// 触发父组件方法
const emit = defineEmits(['changeLineData'])
// 大小变化比例
const resizeTime = ref(1)
// 颜色
const chartColors = computed(() => store.chartColors)
// monitorChart
let monitorContainer: HTMLElement // 监控图表容器
let monitorChart: echarts.ECharts // 监控图表实例
// 获取图表实例,初始化图表
const renderMonitorChart = () => {
if (!monitorContainer) {
monitorContainer = document.getElementById('monitorContainer')
}
monitorChart = echarts.init(monitorContainer)
monitorChart.setOption(
getLineChartDataSet({
dateTime: dataTime[0].time, ...chartColors.value })
)
}
// chartSize update
const updateContainer = () => {
if (
document.documentElement.clientWidth >= 1400 &&
document.documentElement.clientWidth < 1920
) {
resizeTime.value = Number(
(document.documentElement.clientWidth / 2280).toFixed(2)
)
} else if (document.documentElement.clientWidth < 1080) {
resizeTime.value = Number(
(document.documentElement.clientWidth / 1080).toFixed(2)
)
} else {
resizeTime.value = 1
}
monitorChart.resize({
width: monitorContainer.clientWidth,
height: resizeTime.value * 326
})
}
// 传入的数据变化是重新渲染图表
watch(
() => props.middleChartData,
() => {
const count = (props.middleChartData as {
count: number }[]).map((item) => item.count)
const countDate = (props.middleChartData as {
dateTime: string }[]).map((item) => item.dateTime)
onCurrencyChange(count , countDate)
}
)
onMounted(() => {
const count = (props.middleChartData as {
count: number }[]).map((item) => item.count)
const countDate = (props.middleChartData as {
dateTime: string }[]).map((item) => item.dateTime)
// 初始化图表
renderMonitorChart()
onCurrencyChange(count , countDate)
nextTick(() => {
// 初始化图表大小
updateContainer()
})
// 初始化时间
// dateRange.value = dateTag[0].time
// 监听窗口大小变化
window.addEventListener('resize', updateContainer, false)
})
watch(
() => store.isSidebarCompact,
() => {
if (store.isSidebarCompact) {
monitorChart.resize({
width: monitorContainer.clientWidth + 160,
height: resizeTime.value * 326
})
} else {
monitorChart.resize({
width: monitorContainer.clientWidth - 160,
height: resizeTime.value * 326
})
}
}
)
onUnmounted(() => {
// 销毁图表
window.removeEventListener('resize', updateContainer)
})
// 内容变化
const onCurrencyChange = (count: string[] | number[], countDate: string[]) => {
monitorChart.setOption(
getLineChartDataSet({
count, countDate, ...chartColors.value })
)
}
</script>
<style lang="less" scoped>
.dashboard-chart-card {
padding: var(--td-comp-paddingTB-l) var(--td-comp-paddingLR-xl);
padding-top: 0;
padding-right: 15px;
:deep(.t-card__header) {
padding-bottom: 24px;
padding-left: 0;
}
:deep(.t-card__title) {
font-size: 20px;
font-weight: 500;
margin-right: 0;
}
}
.rightHead {
display: flex;
align-items: center;
.active {
background-color: var(--color-main) !important;
color: var(--color-white) !important;
}
.card {
margin-right: 20px;
font-size: 14px;
padding: 5px 10px;
background-color: var(--color-bk7);
border-radius: 5px;
white-space: nowrap;
color: var(--color-bk3);
cursor: pointer;
&:hover {
background-color: var(--color-main);
color: var(--color-white);
}
}
}
:deep(.t-card__body) {
padding: 0;
}
:deep(.t-card__subtitle) {
font-size: 16px;
}
</style>
经营分析(日期范围+导出按钮)
<div class="operate">经营分析</div>
:一个简单的 div 元素,用来显示“经营分析”文本。这个元素可能被样式化以用作标题或标签。<div class="statistics">
和<div class="export">
:这些是用于布局的容器元素,可能包含了与统计数据展示和导出相关的UI组件。<t-date-range-picker>
:这是一个日期范围选择器组件,可能是来自 TDesign 或其他 UI 框架。此组件允许用户选择一个日期范围,并且包含以下属性:
ref="datePicker"
:为组件创建引用,可以在 JavaScript 中通过this.$refs.datePicker
访问。v-model="dateRange"
:双向绑定到名为dateRange
的数据属性,表示所选的日期范围。class="card-date-picker-container"
:添加了自定义样式类。theme="primary"
:设置了组件的主题颜色,通常是框架提供的预设主题之一。mode="date"
:设置选择器模式为日期(相对于时间或其他模式)。allow-input
:允许用户直接在输入框中键入日期。:disable-date="{ after: endTime }"
:禁用了某些日期的选择,这里禁用了所有晚于endTime
的日期。clearable
:允许用户清除已选中的日期。@change="onCurrencyChange"
:当选择的日期变化时触发onCurrencyChange
方法。<div class="bt" @click="exportStatistics">导出明细</div>
:这是一个点击按钮,用户点击它可以触发exportStatistics
方法,从而导出统计详情。@click
是 Vue.js 的事件绑定语法,用于监听用户的点击事件。
综上所述,这段代码描述了一个具有日期范围选择功能的用户界面,以及一个可以导出统计信息的按钮。为了使该组件完全功能化,还需要相应的 JavaScript 部分来定义 dateRange
、endTime
数据属性,以及 onCurrencyChange
和 exportStatistics
方法。
使用:
import Operate from './components/operate.vue'
<Operate
@fetchData="fetchData"
@exportStatistics="exportStatistics"
></Operate>
组件component:
<!-- 经营分析(时间范围+导出) -->
<template>
<div class="operate">经营分析</div>
<div class="statistics">
<!-- <div class="tips">
<span></span
>此数据统计的订单为统计时间内,已结束订单15个自然日之后的数据(已结束订单包含已取消、已关闭、已完成订单)
</div>-->
<div class="export">
<t-date-range-picker
ref="datePicker"
v-model="dateRange"
class="card-date-picker-container"
theme="primary"
mode="date"
allow-input
:disable-date="{
after: endTime
}"
clearable
@change="onCurrencyChange"
/>
<div class="bt" @click="exportStatistics">导出明细</div>
</div>
</div>
</template>
<script setup lang="ts">
import {
ref } from 'vue'
import {
MessagePlugin } from 'tdesign-vue-next'
import dayjs from 'dayjs'
// 表格内时间
const dateRange = ref([
dayjs().subtract(1, 'month').format('YYYY-MM-DD') + ' 00:00:00',
dayjs().subtract(0, 'day').format('YYYY-MM-DD') + ' 23:59:59'
])
// 时间选择器最大时间
const endTime = ref(dayjs().subtract(0, 'day').format('YYYY-MM-DD'))
const emit = defineEmits(['fetchData', 'exportStatistics'])
// 切换时间
const onCurrencyChange = (val) => {
if (dayjs(val[1]).diff(dayjs(val[0]), 'day') > 365) {
// dateRange.value = []
MessagePlugin.error('时间间隔不能大于365天')
} else {
emit('fetchData', dateRange.value)
}
}
// 触发导出数据
const exportStatistics = () => {
emit('exportStatistics', dateRange.value)
}
</script>
<style lang="less" scoped src=