| | |
| | | package com.example.firstapp.ui.dashboard |
| | | |
| | | import com.example.firstapp.R |
| | | import android.os.Bundle |
| | | import android.view.LayoutInflater |
| | | import android.view.View |
| | | import android.view.ViewGroup |
| | | import android.widget.TextView |
| | | import androidx.fragment.app.Fragment |
| | | import androidx.lifecycle.ViewModelProvider |
| | | import androidx.fragment.app.viewModels |
| | | import com.example.firstapp.databinding.FragmentDashboardBinding |
| | | import com.google.android.material.tabs.TabLayout |
| | | import androidx.recyclerview.widget.LinearLayoutManager |
| | | import com.example.firstapp.adapter.PackageAdapter |
| | | import com.github.mikephil.charting.charts.BarChart |
| | | import com.github.mikephil.charting.charts.PieChart |
| | | import com.github.mikephil.charting.components.Legend |
| | | import com.github.mikephil.charting.components.XAxis |
| | | import com.github.mikephil.charting.data.* |
| | | import com.github.mikephil.charting.formatter.ValueFormatter |
| | | import java.util.* |
| | | import java.text.SimpleDateFormat |
| | | import android.graphics.Color |
| | | import android.view.Gravity |
| | | import android.widget.GridLayout |
| | | import android.widget.LinearLayout |
| | | import android.widget.Toast |
| | | import androidx.cardview.widget.CardView |
| | | import androidx.lifecycle.lifecycleScope |
| | | import com.example.firstapp.database.response.UserInfo |
| | | import com.example.firstapp.database.service.RetrofitClient |
| | | import com.example.firstapp.model.DailyStat |
| | | import com.example.firstapp.utils.PreferencesManager |
| | | import kotlinx.coroutines.launch |
| | | |
| | | class DashboardFragment : Fragment() { |
| | | |
| | | private var _binding: FragmentDashboardBinding? = null |
| | | |
| | | // This property is only valid between onCreateView and |
| | | // onDestroyView. |
| | | private val binding get() = _binding!! |
| | | private val packageAdapter = PackageAdapter() |
| | | private var currentDate = Calendar.getInstance() |
| | | private var currentDateType = DateType.DAY |
| | | private lateinit var barChart: BarChart |
| | | private lateinit var pieChart: PieChart |
| | | private lateinit var chartCourierDistriBution:CardView |
| | | private lateinit var heatmapView: View |
| | | private var currentUserInfo: UserInfo? = null // 确保使用你的实际数据类 |
| | | |
| | | private var startDateCur:String = "" |
| | | private var endDateCur:String = "" |
| | | private lateinit var bar_title:TextView |
| | | private lateinit var pie_title:TextView |
| | | |
| | | |
| | | enum class DateType { |
| | | DAY, WEEK, MONTH, YEAR |
| | | } |
| | | private val viewModel: DashboardViewModel by viewModels() |
| | | |
| | | override fun onCreateView( |
| | | inflater: LayoutInflater, |
| | | container: ViewGroup?, |
| | | savedInstanceState: Bundle? |
| | | ): View { |
| | | val dashboardViewModel = |
| | | ViewModelProvider(this).get(DashboardViewModel::class.java) |
| | | |
| | | _binding = FragmentDashboardBinding.inflate(inflater, container, false) |
| | | val root: View = binding.root |
| | | return binding.root |
| | | } |
| | | |
| | | val textView: TextView = binding.textDashboard |
| | | dashboardViewModel.text.observe(viewLifecycleOwner) { |
| | | textView.text = it |
| | | override fun onResume() { |
| | | super.onResume() |
| | | // 重新加载用户信息 |
| | | loadUserInfo() |
| | | } |
| | | |
| | | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| | | super.onViewCreated(view, savedInstanceState) |
| | | |
| | | //渲染包裹列表 |
| | | setupRecyclerView() |
| | | //初始化tab内容和数据 |
| | | setupTabLayout() |
| | | //日期调整 |
| | | setupDatePicker() |
| | | setupView(view) |
| | | updateDateDisplay() |
| | | loadPackages() |
| | | |
| | | // 遮罩层 |
| | | loadUserInfo() |
| | | |
| | | // 遮罩层点击时间 |
| | | binding.overlayContent.setOnClickListener { |
| | | // 跳转到vipActivity |
| | | val intent = android.content.Intent(requireContext(), com.example.firstapp.activity.VipActivity::class.java) |
| | | startActivity(intent) |
| | | } |
| | | return root |
| | | } |
| | | |
| | | private fun showOverlay() { |
| | | binding.viewOverlay.visibility = View.VISIBLE |
| | | binding.overlayContent.visibility = View.VISIBLE |
| | | |
| | | } |
| | | |
| | | private fun hiddleOverlay() { |
| | | binding.viewOverlay.visibility = View.GONE |
| | | binding.overlayContent.visibility = View.GONE |
| | | |
| | | |
| | | } |
| | | |
| | | |
| | | private fun setupRecyclerView() { |
| | | binding.recyclerPackages.apply { |
| | | layoutManager = LinearLayoutManager(context) |
| | | adapter = packageAdapter |
| | | } |
| | | } |
| | | |
| | | private fun setupTabLayout() { |
| | | val weekStatsView = binding.layoutWeekStats.root |
| | | bar_title = weekStatsView.findViewById(R.id.bar_title) |
| | | pie_title = weekStatsView.findViewById(R.id.pie_title) |
| | | |
| | | binding.tabDateRange.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { |
| | | override fun onTabSelected(tab: TabLayout.Tab?) { |
| | | currentDate = Calendar.getInstance() |
| | | |
| | | currentDateType = when(tab?.position) { |
| | | 0 -> DateType.DAY |
| | | 1 -> DateType.WEEK |
| | | 2 -> DateType.MONTH |
| | | 3 -> DateType.YEAR |
| | | else -> DateType.DAY |
| | | } |
| | | |
| | | when (currentDateType) { |
| | | DateType.DAY -> { |
| | | binding.cardPackageStatsTitleText.text = "本日收到包裹总数" |
| | | binding.layoutYearStatsTitleText.text = "包裹取件码记录" |
| | | |
| | | binding.cardPackageStatsTitleText.visibility = View.VISIBLE |
| | | binding.layoutYearStatsTitleText.visibility = View.VISIBLE |
| | | } |
| | | DateType.WEEK -> { |
| | | binding.cardPackageStatsTitleText.text = "本周收到包裹总数" |
| | | bar_title.text = "本周收到包裹数分布 ->" |
| | | pie_title.text = "本周包裹物流公司分布 ->" |
| | | binding.cardPackageStatsTitleText.visibility = View.VISIBLE |
| | | binding.layoutYearStatsTitleText.visibility = View.GONE |
| | | |
| | | } |
| | | DateType.MONTH -> { |
| | | binding.cardPackageStatsTitleText.text = "本月收到包裹总数" |
| | | bar_title.text = "本月收到包裹数分布 ->" |
| | | pie_title.text = "本月包裹物流公司分布 ->" |
| | | binding.cardPackageStatsTitleText.visibility = View.VISIBLE |
| | | binding.layoutYearStatsTitleText.visibility = View.GONE |
| | | } |
| | | DateType.YEAR -> { |
| | | bar_title.text = "本年收到包裹数分布 ->" |
| | | pie_title.text = "本年包裹物流公司分布 ->" |
| | | binding.cardPackageStatsTitleText.visibility = View.GONE |
| | | binding.layoutYearStatsTitleText.visibility = View.GONE |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | updateDateDisplay() |
| | | updateCharts() |
| | | loadPackages() |
| | | } |
| | | override fun onTabUnselected(tab: TabLayout.Tab?) {} |
| | | override fun onTabReselected(tab: TabLayout.Tab?) {} |
| | | }) |
| | | } |
| | | |
| | | private fun setupDatePicker() { |
| | | binding.btnPreviousDate.setOnClickListener { |
| | | adjustDate(-1) |
| | | } |
| | | binding.btnNextDate.setOnClickListener { |
| | | adjustDate(1) |
| | | } |
| | | } |
| | | |
| | | private fun adjustDate(amount: Int) { |
| | | when (currentDateType) { |
| | | DateType.DAY -> currentDate.add(Calendar.DAY_OF_MONTH, amount) |
| | | DateType.WEEK -> currentDate.add(Calendar.WEEK_OF_YEAR, amount) |
| | | DateType.MONTH -> currentDate.add(Calendar.MONTH, amount) |
| | | DateType.YEAR -> currentDate.add(Calendar.YEAR, amount) |
| | | } |
| | | updateDateDisplay() |
| | | updateCharts() |
| | | loadPackages() |
| | | } |
| | | |
| | | private fun updateDateDisplay() { |
| | | val dateFormat = when (currentDateType) { |
| | | DateType.DAY -> { |
| | | // 获取当天的起始和结束时间 |
| | | val calendar = currentDate.clone() as Calendar |
| | | // 设置为当天的 00:00:00 |
| | | calendar.set(Calendar.HOUR_OF_DAY, 0) |
| | | calendar.set(Calendar.MINUTE, 0) |
| | | calendar.set(Calendar.SECOND, 0) |
| | | calendar.set(Calendar.MILLISECOND, 0) |
| | | startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | // 设置为当天的 23:59:59 |
| | | calendar.set(Calendar.HOUR_OF_DAY, 23) |
| | | calendar.set(Calendar.MINUTE, 59) |
| | | calendar.set(Calendar.SECOND, 59) |
| | | calendar.set(Calendar.MILLISECOND, 999) |
| | | endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | // 返回当天的日期格式 |
| | | SimpleDateFormat("yyyy年MM月dd日", Locale.getDefault()).format(currentDate.time) |
| | | } |
| | | DateType.WEEK -> { |
| | | // 获取本周的起始和结束日期 |
| | | val calendar = currentDate.clone() as Calendar |
| | | calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) |
| | | val startDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) |
| | | startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | calendar.add(Calendar.DAY_OF_WEEK, 6) |
| | | val endDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) |
| | | endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | "$startDate-$endDate" |
| | | } |
| | | DateType.MONTH -> { |
| | | // 获取本月的起始和结束日期 |
| | | val calendar = currentDate.clone() as Calendar |
| | | calendar.set(Calendar.DAY_OF_MONTH, 1) |
| | | startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | calendar.add(Calendar.MONTH, 1) |
| | | calendar.set(Calendar.DAY_OF_MONTH, 0) |
| | | endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | "yyyy年MM月" |
| | | } |
| | | DateType.YEAR -> { |
| | | // 获取当前年的起始和结束日期 |
| | | val calendar = currentDate.clone() as Calendar |
| | | |
| | | // 设置为当前年份的 1 月 1 日 |
| | | calendar.set(Calendar.MONTH, Calendar.JANUARY) |
| | | calendar.set(Calendar.DAY_OF_MONTH, 1) |
| | | startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | // 设置为当前年份的 12 月 31 日 |
| | | calendar.set(Calendar.MONTH, Calendar.DECEMBER) |
| | | calendar.set(Calendar.DAY_OF_MONTH, 31) |
| | | endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | |
| | | "yyyy年" |
| | | } |
| | | } |
| | | |
| | | // 更新界面显示 |
| | | if (currentDateType == DateType.WEEK) { |
| | | binding.textCurrentDate.text = dateFormat |
| | | } else { |
| | | binding.textCurrentDate.text = SimpleDateFormat(dateFormat, Locale.getDefault()) |
| | | .format(currentDate.time) |
| | | } |
| | | } |
| | | |
| | | private fun updateDateDisplay_bak() { |
| | | val dateFormat = when (currentDateType) { |
| | | DateType.DAY -> "yyyy年MM月dd日" |
| | | DateType.WEEK -> { |
| | | // 获取本周的起始和结束日期 |
| | | val calendar = currentDate.clone() as Calendar |
| | | calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) |
| | | val startDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) |
| | | startDateCur=SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | calendar.add(Calendar.DAY_OF_WEEK, 6) |
| | | val endDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) |
| | | endDateCur=SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) |
| | | |
| | | |
| | | "$startDate-$endDate" |
| | | } |
| | | DateType.MONTH -> "yyyy年MM月" |
| | | DateType.YEAR -> "yyyy年" |
| | | } |
| | | |
| | | if (currentDateType == DateType.WEEK) { |
| | | binding.textCurrentDate.text = dateFormat |
| | | } else { |
| | | binding.textCurrentDate.text = SimpleDateFormat(dateFormat, Locale.getDefault()) |
| | | .format(currentDate.time) |
| | | } |
| | | } |
| | | private fun setupView(view: View) { |
| | | |
| | | val weekStatsView = binding.layoutWeekStats.root |
| | | barChart = weekStatsView.findViewById(R.id.chart_daily_packages) |
| | | chartCourierDistriBution=weekStatsView.findViewById(R.id.chart_courier_card_view) |
| | | pieChart = weekStatsView.findViewById(R.id.chart_courier_distribution) |
| | | heatmapView = weekStatsView.findViewById(R.id.heatmap_yearly) |
| | | |
| | | barChart.setViewPortOffsets(100f, 100f, 100f, 200f) |
| | | |
| | | // barChart.invalidate() |
| | | |
| | | // 初始化时隐藏统计视图 |
| | | weekStatsView.visibility = View.GONE |
| | | |
| | | setupBarChart() |
| | | setupPieChart() |
| | | setupHeatmap() |
| | | updateCharts() |
| | | |
| | | } |
| | | private fun setupBarChart() { |
| | | barChart.apply { |
| | | description.isEnabled = false |
| | | setDrawGridBackground(false) |
| | | legend.isEnabled = false |
| | | |
| | | // 增大图表高度 |
| | | minimumHeight = (resources.displayMetrics.density * 300).toInt() |
| | | |
| | | // X轴设置 |
| | | xAxis.apply { |
| | | position = XAxis.XAxisPosition.BOTTOM |
| | | setDrawGridLines(false) |
| | | granularity = 1f |
| | | labelRotationAngle = 0f |
| | | textSize = 10f //标签字体 |
| | | setExtraLeftOffset(5f) // 减少左侧留白 |
| | | setExtraBottomOffset(15f) |
| | | } |
| | | |
| | | // Y轴设置 |
| | | axisLeft.apply { |
| | | setDrawGridLines(true) |
| | | axisMinimum = 0f |
| | | granularity = 1f |
| | | valueFormatter = object : ValueFormatter() { |
| | | override fun getFormattedValue(value: Float): String { |
| | | return if (value > 0) value.toInt().toString() else "" |
| | | } |
| | | } |
| | | } |
| | | axisRight.isEnabled = false |
| | | |
| | | // 设置图表交互 |
| | | setTouchEnabled(true) |
| | | isDragEnabled = true |
| | | setScaleEnabled(true) |
| | | |
| | | // 设置边距 |
| | | setExtraOffsets(10f, 10f, 10f, 20f) |
| | | } |
| | | |
| | | |
| | | |
| | | updateBarChartData() |
| | | } |
| | | private fun updateBarChartData() { |
| | | val statsFlow = when (currentDateType) { |
| | | DateType.WEEK -> { |
| | | // viewModel.getWeeklyStats(currentDate.timeInMillis, 6) |
| | | viewModel.getWeeklyStatsChart(startDateCur,endDateCur) |
| | | } |
| | | DateType.MONTH -> { |
| | | viewModel.getYearMonthlyStats(currentDate.timeInMillis) |
| | | } |
| | | else -> return |
| | | } |
| | | |
| | | statsFlow.observe(viewLifecycleOwner) { stats -> |
| | | if (stats.isEmpty()) return@observe |
| | | |
| | | val entries = stats.mapIndexed { index, stat -> |
| | | val entry = BarEntry(index.toFloat(), stat.count.toFloat()) |
| | | if (stat.count == 0) { |
| | | entry.data = "hide_label" |
| | | } |
| | | entry |
| | | } |
| | | |
| | | val dataSet = BarDataSet(entries, "包裹数量") |
| | | dataSet.apply { |
| | | color = resources.getColor(R.color.purple_500) |
| | | valueTextSize = 12f |
| | | valueFormatter = object : ValueFormatter() { |
| | | override fun getFormattedValue(value: Float): String { |
| | | return if (value > 0) value.toInt().toString() else "" |
| | | } |
| | | } |
| | | } |
| | | |
| | | val barData = BarData(dataSet) |
| | | barChart.data = barData |
| | | |
| | | // 修改X轴标签显示 |
| | | barChart.xAxis.apply { |
| | | valueFormatter = object : ValueFormatter() { |
| | | override fun getFormattedValue(value: Float): String { |
| | | val position = value.toInt() |
| | | if (position >= 0 && position < stats.size) { |
| | | return when(currentDateType) { |
| | | DateType.WEEK -> { |
| | | val weekStat = stats[position] |
| | | try { |
| | | // 解析日期字符串 |
| | | val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) |
| | | val date = sdf.parse(weekStat.date) |
| | | val calendar = Calendar.getInstance() |
| | | calendar.time = date |
| | | |
| | | // 获取年份和周数 |
| | | val weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR) |
| | | val year = calendar.get(Calendar.YEAR) |
| | | |
| | | // 显示格式:第X周 |
| | | // "第${weekOfYear}周" |
| | | "${weekOfYear}" |
| | | } catch (e: Exception) { |
| | | weekStat.date |
| | | } |
| | | } |
| | | // DateType.MONTH -> "${stats[position].date}月" |
| | | DateType.MONTH -> { |
| | | stats[position].date.replaceFirst("^0+".toRegex(), "") |
| | | } |
| | | else -> "" |
| | | } |
| | | } |
| | | return "" |
| | | } |
| | | } |
| | | labelCount = stats.size |
| | | setAvoidFirstLastClipping(true) |
| | | labelRotationAngle = 0f |
| | | |
| | | // 增加标签间距 |
| | | // setExtraBottomOffset(20f) |
| | | textSize = 11f // 稍微调小字体 |
| | | } |
| | | |
| | | // 高亮显示 |
| | | if (currentDateType == DateType.MONTH) { |
| | | val currentMonth = currentDate.get(Calendar.MONTH) |
| | | dataSet.setColors(List(stats.size) { index -> |
| | | if (index == currentMonth) resources.getColor(R.color.purple_500) |
| | | else resources.getColor(R.color.purple_200) |
| | | }) |
| | | } else if (currentDateType == DateType.WEEK) { |
| | | val highlightIndex = 3 // 当前周在第4个位置 |
| | | dataSet.setColors(List(stats.size) { index -> |
| | | if (index == highlightIndex) resources.getColor(R.color.purple_500) |
| | | else resources.getColor(R.color.purple_200) |
| | | }) |
| | | } |
| | | |
| | | // 刷新图表 |
| | | barChart.notifyDataSetChanged() |
| | | barChart.invalidate() |
| | | } |
| | | } |
| | | private fun setupPieChart() { |
| | | pieChart.apply { |
| | | // 隐藏图表描述(右下角默认文字) |
| | | description.isEnabled = false |
| | | // 显示实际值而非百分比 |
| | | setUsePercentValues(false) |
| | | // 隐藏饼图区块上的标签(如数值或名称) |
| | | setDrawEntryLabels(false) |
| | | |
| | | // 设置饼图与容器的边距,参数顺序:左、上、右、下 |
| | | // 右侧留出 80f 空间,可能为图例腾出位置 |
| | | setExtraOffsets(20f, 20f, 20f, 20f) |
| | | |
| | | |
| | | |
| | | // 配置图例 |
| | | legend.apply { |
| | | isEnabled = true // 启用图例 |
| | | verticalAlignment = Legend.LegendVerticalAlignment.CENTER // 垂直居中 |
| | | horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT // 水平靠右对齐 |
| | | orientation = Legend.LegendOrientation.VERTICAL // 图例项垂直排列 |
| | | setDrawInside(false) // 图例绘制在图表外部(而非覆盖在图上) |
| | | xEntrySpace = 10f // 图例项水平间距 |
| | | yEntrySpace = 5f // 图例项垂直间距 |
| | | yOffset = 0f // 图例整体 Y 轴无偏移 |
| | | textSize = 12f // 图例文字大小 |
| | | } |
| | | |
| | | // 调整中心空白区域大小 |
| | | holeRadius = 20f // 中间空心圆的半径(占饼图比例) |
| | | transparentCircleRadius = 25f // 透明圆圈的半径(可能用于边框效果) |
| | | |
| | | } |
| | | |
| | | updatePieChartData() |
| | | } |
| | | private fun updatePieChartData() { |
| | | viewModel.getCourierStats( |
| | | currentDate.timeInMillis, |
| | | currentDateType.name |
| | | ).observe(viewLifecycleOwner) { stats -> |
| | | val entries = stats.map { stat -> |
| | | PieEntry(stat.count.toFloat(), "${stat.courierName}(${stat.count})") |
| | | } |
| | | |
| | | if (entries.isNotEmpty()) { |
| | | val dataSet = PieDataSet(entries, "快递公司分布") |
| | | dataSet.colors = listOf( |
| | | resources.getColor(R.color.light_blue_600_1), |
| | | resources.getColor(R.color.sunflower), |
| | | resources.getColor(R.color.light_blue), |
| | | resources.getColor(R.color.vermillion), |
| | | resources.getColor(R.color.fish_belly_white), |
| | | resources.getColor(R.color.light_green), |
| | | resources.getColor(R.color.crimson), |
| | | resources.getColor(R.color.sky_blue), |
| | | resources.getColor(R.color.gold), |
| | | resources.getColor(R.color.light_purple), |
| | | resources.getColor(R.color.yellow), |
| | | resources.getColor(R.color.canary_yellow), |
| | | resources.getColor(R.color.red_purple), |
| | | resources.getColor(R.color.light_cyan), |
| | | resources.getColor(R.color.orange), |
| | | resources.getColor(R.color.magenta), |
| | | resources.getColor(R.color.light_purple_2), |
| | | resources.getColor(R.color.bright_yellow), |
| | | resources.getColor(R.color.emerald_green), |
| | | resources.getColor(R.color.turmeric), |
| | | resources.getColor(R.color.red_gold), |
| | | resources.getColor(R.color.off_white), |
| | | resources.getColor(R.color.tangerine), |
| | | resources.getColor(R.color.aqua_blue), |
| | | resources.getColor(R.color.frost), |
| | | resources.getColor(R.color.wisteria), |
| | | resources.getColor(R.color.cyan) |
| | | ) |
| | | dataSet.valueTextSize = 14f // 增大数值文字大小 |
| | | |
| | | val pieData = PieData(dataSet) |
| | | pieData.setValueFormatter(object : ValueFormatter() { |
| | | override fun getFormattedValue(value: Float): String { |
| | | return value.toInt().toString() |
| | | } |
| | | }) |
| | | |
| | | pieChart.data = pieData |
| | | pieChart.invalidate() |
| | | |
| | | pieChart.visibility = View.VISIBLE // 例如:隐藏 PieChart |
| | | |
| | | pie_title.visibility = View.VISIBLE |
| | | |
| | | chartCourierDistriBution.visibility =View.VISIBLE |
| | | |
| | | } else { |
| | | // 如果 entries 为空,可以选择隐藏图表或设置一个默认显示 |
| | | pieChart.visibility = View.GONE // 例如:隐藏 PieChart |
| | | |
| | | pie_title.visibility = View.GONE |
| | | |
| | | chartCourierDistriBution.visibility =View.GONE |
| | | |
| | | } |
| | | |
| | | |
| | | // val dataSet = PieDataSet(entries, "快递公司分布") |
| | | // dataSet.colors = listOf( |
| | | // resources.getColor(R.color.purple_500), |
| | | // resources.getColor(R.color.teal_200), |
| | | // resources.getColor(R.color.purple_200), |
| | | // resources.getColor(R.color.teal_700) |
| | | // ) |
| | | // dataSet.valueTextSize = 14f // 增大数值文字大小 |
| | | // |
| | | // val pieData = PieData(dataSet) |
| | | // pieData.setValueFormatter(object : ValueFormatter() { |
| | | // override fun getFormattedValue(value: Float): String { |
| | | // return value.toInt().toString() |
| | | // } |
| | | // }) |
| | | // |
| | | // pieChart.data = pieData |
| | | // pieChart.invalidate() |
| | | } |
| | | } |
| | | private fun getDayLabels(): Array<String> { |
| | | return arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") |
| | | } |
| | | |
| | | private fun loadPackages(){ |
| | | |
| | | |
| | | |
| | | // 根据日、周、月、年 获取统计数字 |
| | | when (currentDateType) { |
| | | DateType.DAY -> { |
| | | |
| | | viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | binding.textPackageCount.text = "${stats}个" |
| | | } |
| | | |
| | | // 获取取件记录 |
| | | viewModel.getPackagesReaded(currentDate.timeInMillis, |
| | | currentDateType.name) |
| | | .observe(viewLifecycleOwner) { unpackages-> |
| | | // 只读取未取件的包裹 |
| | | packageAdapter.updatePackages(unpackages) |
| | | } |
| | | |
| | | |
| | | } |
| | | DateType.WEEK -> { |
| | | |
| | | viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | binding.textPackageCount.text = "${stats}个" |
| | | } |
| | | } |
| | | DateType.MONTH -> { |
| | | |
| | | viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | binding.textPackageCount.text = "${stats}个" |
| | | } |
| | | } |
| | | DateType.YEAR -> { |
| | | |
| | | viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | binding.layoutYearStats.textTotalPackages.text = "${stats}个" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun loadPackages_bak2(){ |
| | | |
| | | val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) |
| | | |
| | | // 根据日、周、月、年 获取统计数字 |
| | | when (currentDateType) { |
| | | DateType.DAY -> { |
| | | val today = Calendar.getInstance() |
| | | val tmpCurDateStart = formatter.format(currentDate.time) |
| | | val tmpCurDateEnd = formatter.format(currentDate.time) |
| | | viewModel.getCurrentDayStatsByType(tmpCurDateStart,tmpCurDateEnd,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | binding.textPackageCount.text = "${stats}个" |
| | | } |
| | | |
| | | // 获取本周统计 |
| | | viewModel.getPackagesReaded(currentDate.timeInMillis, |
| | | currentDateType.name) |
| | | .observe(viewLifecycleOwner) { unpackages-> |
| | | // 只读取未取件的包裹 |
| | | packageAdapter.updatePackages(unpackages) |
| | | } |
| | | |
| | | |
| | | } |
| | | DateType.WEEK -> { |
| | | val today = Calendar.getInstance() |
| | | |
| | | // 获取本周的周一(第一天) |
| | | today.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) |
| | | val firstDayOfWeek = formatter.format(today.time) |
| | | // 获取本周的周日(最后一天) |
| | | today.add(Calendar.DATE, 6) // 加6天 |
| | | val lastDayOfWeek = formatter.format(today.time) |
| | | viewModel.getCurrentDayStatsByType(firstDayOfWeek,lastDayOfWeek,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | binding.textPackageCount.text = "${stats}个" |
| | | } |
| | | } |
| | | DateType.MONTH -> { |
| | | val today = Calendar.getInstance() |
| | | today.set(Calendar.DAY_OF_MONTH, 1) // 设置为本月第一天 |
| | | val firstDayOfMonth = formatter.format(today.time) |
| | | |
| | | today.add(Calendar.MONTH, 1) // 移动到下个月 |
| | | today.set(Calendar.DAY_OF_MONTH, 0) // 设置为下个月的最后一天 |
| | | val lastDayOfMonth = formatter.format(today.time) |
| | | viewModel.getCurrentDayStatsByType(firstDayOfMonth,lastDayOfMonth,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | binding.textPackageCount.text = "${stats}个" |
| | | } |
| | | } |
| | | DateType.YEAR -> { |
| | | val today = Calendar.getInstance() |
| | | today.set(Calendar.MONTH, Calendar.JANUARY) // 设置为第一月 |
| | | today.set(Calendar.DAY_OF_MONTH, 1) // 设置为第一天 |
| | | val firstDayOfYear = formatter.format(today.time) |
| | | |
| | | today.add(Calendar.YEAR, 1) // 移动到下一年 |
| | | today.set(Calendar.MONTH, Calendar.DECEMBER) // 设置为最后一月 |
| | | today.set(Calendar.DAY_OF_MONTH, 31) // 设置为最后一天 |
| | | val lastDayOfYear = formatter.format(today.time) |
| | | viewModel.getCurrentDayStatsByType(firstDayOfYear,lastDayOfYear,"快递") .observe(viewLifecycleOwner) { stats -> |
| | | // binding.textPackageCount.text = "${stats}个" |
| | | binding.layoutYearStats.textTotalPackages.text = "${stats}个" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | private fun loadPackages_bak() { |
| | | viewModel.getPackages( |
| | | currentDate.timeInMillis, |
| | | currentDateType.name |
| | | ).observe(viewLifecycleOwner) { packages -> |
| | | when (currentDateType) { |
| | | DateType.DAY -> { |
| | | binding.textPackageCount.text = "${packages.size}个" |
| | | |
| | | // 获取本周统计 |
| | | viewModel.getPackagesReaded(currentDate.timeInMillis, |
| | | currentDateType.name) |
| | | .observe(viewLifecycleOwner) { unpackages-> |
| | | // 只读取未取件的包裹 |
| | | packageAdapter.updatePackages(unpackages) |
| | | } |
| | | |
| | | } |
| | | DateType.WEEK -> { |
| | | // 获取本周统计 |
| | | viewModel.getCurrentWeekStats2(startDateCur,endDateCur).observe(viewLifecycleOwner) { stats -> |
| | | binding.textPackageCount.text = "${stats}个" |
| | | } |
| | | // viewModel.getCurrentWeekStats(currentDate.timeInMillis) |
| | | // .observe(viewLifecycleOwner) { stats -> |
| | | // val weekTotal = stats.sumOf { it.count } |
| | | // binding.textPackageCount.text = "${weekTotal}个" |
| | | // } |
| | | } |
| | | DateType.MONTH -> { |
| | | // 获取本月统计 |
| | | viewModel.getMonthlyStats(currentDate.timeInMillis) |
| | | .observe(viewLifecycleOwner) { stats -> |
| | | val monthTotal = stats.sumOf { it.count } |
| | | binding.textPackageCount.text = "${monthTotal}个" |
| | | } |
| | | } |
| | | DateType.YEAR -> { |
| | | // 获取本年统计 |
| | | viewModel.getCurrentYearStats(currentDate.timeInMillis) |
| | | .observe(viewLifecycleOwner) { stats -> |
| | | val yearTotal = stats.sumOf { it.count } |
| | | binding.textPackageCount.text = "${yearTotal}个" |
| | | } |
| | | } |
| | | } |
| | | |
| | | // packageAdapter.updatePackages(packages) |
| | | // binding.textPackageCount.text = "${packages.size}个" |
| | | } |
| | | } |
| | | private fun setupHeatmap() { |
| | | heatmapView.visibility = View.GONE |
| | | } |
| | | |
| | | private fun updateHeatmapData() { |
| | | viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> |
| | | if (stats.isEmpty()) return@observe |
| | | |
| | | // 创建52周x7天的数据矩阵 |
| | | val heatmapMatrix = Array(7) { IntArray(52) } |
| | | |
| | | // 填充数据 |
| | | stats.forEach { stat -> |
| | | val week = stat.weekOfYear - 1 // 0-51 |
| | | val dayOfWeek = stat.dayOfWeek - 1 // 0-6 |
| | | if (week in 0..51 && dayOfWeek in 0..6) { |
| | | heatmapMatrix[dayOfWeek][week] = stat.count |
| | | } |
| | | } |
| | | |
| | | // 更新UI |
| | | binding.layoutWeekStats.heatmapYearly.apply { |
| | | removeAllViews() |
| | | |
| | | val gridLayout = GridLayout(context).apply { |
| | | rowCount = 8 // 1行月份 + 7行星期 |
| | | columnCount = 53 // 1列星期 + 52列周数 |
| | | } |
| | | |
| | | // 添加月份标签 |
| | | val months = arrayOf("1月", "2月", "3月", "4月", "5月", "6月", |
| | | "7月", "8月", "9月", "10月", "11月", "12月") |
| | | months.forEachIndexed { index, month -> |
| | | val label = TextView(context).apply { |
| | | text = month |
| | | textSize = 10f |
| | | setPadding(0, 0, 8, 4) |
| | | val weekPosition = (index * 4.3).toInt() // 估算月份起始位置 |
| | | layoutParams = GridLayout.LayoutParams().apply { |
| | | columnSpec = GridLayout.spec(weekPosition + 1) |
| | | rowSpec = GridLayout.spec(0) |
| | | } |
| | | } |
| | | gridLayout.addView(label) |
| | | } |
| | | |
| | | // 添加星期标签 |
| | | val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") |
| | | dayLabels.forEachIndexed { index, label -> |
| | | val textView = TextView(context).apply { |
| | | text = label |
| | | textSize = 10f |
| | | setPadding(4, 0, 8, 0) |
| | | layoutParams = GridLayout.LayoutParams().apply { |
| | | columnSpec = GridLayout.spec(0) |
| | | rowSpec = GridLayout.spec(index + 1) |
| | | } |
| | | } |
| | | gridLayout.addView(textView) |
| | | } |
| | | |
| | | // 添加热力图单元格 |
| | | for (day in 0..6) { |
| | | for (week in 0..51) { |
| | | val count = heatmapMatrix[day][week] |
| | | val cell = View(context).apply { |
| | | layoutParams = GridLayout.LayoutParams().apply { |
| | | width = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) |
| | | height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) |
| | | columnSpec = GridLayout.spec(week + 1) |
| | | rowSpec = GridLayout.spec(day + 1) |
| | | setMargins(1, 1, 1, 1) |
| | | } |
| | | setBackgroundColor(getHeatmapColor(count)) |
| | | } |
| | | gridLayout.addView(cell) |
| | | } |
| | | } |
| | | |
| | | // 创建图例(Legend) |
| | | val legendLayout = LinearLayout(context).apply { |
| | | orientation = LinearLayout.HORIZONTAL |
| | | gravity = Gravity.END or Gravity.CENTER_VERTICAL // 靠右对齐 |
| | | setPadding(8, 16, 8, 0) |
| | | layoutParams = LinearLayout.LayoutParams( |
| | | ViewGroup.LayoutParams.MATCH_PARENT, |
| | | ViewGroup.LayoutParams.WRAP_CONTENT |
| | | ) |
| | | } |
| | | |
| | | // 左侧“少”标签 |
| | | val labelLow = TextView(context).apply { |
| | | text = "少" |
| | | textSize = 10f |
| | | setPadding(0, 0, 8, 0) |
| | | } |
| | | legendLayout.addView(labelLow) |
| | | |
| | | // 渐变色块 + 数值 |
| | | val legendLevels = listOf(0, 5, 10, 15, 20) |
| | | legendLevels.forEach { level -> |
| | | val colorBox = View(context).apply { |
| | | setBackgroundColor(getHeatmapColor(level)) |
| | | val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) |
| | | layoutParams = LinearLayout.LayoutParams(size, size).apply { |
| | | marginEnd = 4 |
| | | } |
| | | } |
| | | |
| | | val label = TextView(context).apply { |
| | | // text = "$level" |
| | | textSize = 10f |
| | | setPadding(0, 0, 8, 0) |
| | | } |
| | | |
| | | legendLayout.addView(colorBox) |
| | | legendLayout.addView(label) |
| | | } |
| | | |
| | | // 右侧“多”标签 |
| | | val labelHigh = TextView(context).apply { |
| | | text = "多" |
| | | textSize = 10f |
| | | setPadding(8, 0, 0, 0) |
| | | } |
| | | legendLayout.addView(labelHigh) |
| | | |
| | | // 垂直组合 gridLayout + legendLayout |
| | | val container = LinearLayout(context).apply { |
| | | orientation = LinearLayout.VERTICAL |
| | | addView(gridLayout) |
| | | addView(legendLayout) |
| | | } |
| | | |
| | | addView(container) |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun getHeatmapColor(count: Int): Int { |
| | | return when { |
| | | count == 0 -> Color.parseColor("#EBECF1") // 最淡 |
| | | count < 5 -> Color.parseColor("#ACE7B1") |
| | | count < 10 -> Color.parseColor("#68C16F") |
| | | count < 15 -> Color.parseColor("#529F57") |
| | | else -> Color.parseColor("#356D40") // 最深 |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | private fun updateHeatmapData_bak() { |
| | | viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> |
| | | if (stats.isEmpty()) return@observe |
| | | |
| | | // 创建52周x7天的数据矩阵 |
| | | val heatmapMatrix = Array(7) { IntArray(52) } |
| | | |
| | | // 填充数据 |
| | | stats.forEach { stat -> |
| | | val week = stat.weekOfYear - 1 // 0-51 |
| | | val dayOfWeek = stat.dayOfWeek - 1 // 0-6 |
| | | if (week in 0..51 && dayOfWeek in 0..6) { |
| | | heatmapMatrix[dayOfWeek][week] = stat.count |
| | | } |
| | | } |
| | | |
| | | // 更新UI |
| | | binding.layoutWeekStats.heatmapYearly.apply { |
| | | removeAllViews() |
| | | |
| | | val gridLayout = GridLayout(context).apply { |
| | | rowCount = 8 // 增加一行用于显示月份 |
| | | columnCount = 53 // 增加一列用于显示星期标签 |
| | | } |
| | | |
| | | // 添加月份标签 |
| | | val months = arrayOf("1月", "2月", "3月", "4月", "5月", "6月", |
| | | "7月", "8月", "9月", "10月", "11月", "12月") |
| | | months.forEachIndexed { index, month -> |
| | | val label = TextView(context).apply { |
| | | text = month |
| | | textSize = 10f |
| | | setPadding(0, 0, 8, 4) |
| | | // 计算每个月份标签的位置 |
| | | val weekPosition = (index * 4.3).toInt() |
| | | val params = GridLayout.LayoutParams() |
| | | params.columnSpec = GridLayout.spec(weekPosition + 1) |
| | | params.rowSpec = GridLayout.spec(0) |
| | | layoutParams = params |
| | | } |
| | | gridLayout.addView(label) |
| | | } |
| | | |
| | | // 添加星期标签 |
| | | val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") |
| | | dayLabels.forEachIndexed { index, label -> |
| | | val textView = TextView(context).apply { |
| | | text = label |
| | | textSize = 10f |
| | | setPadding(4, 0, 8, 0) |
| | | val params = GridLayout.LayoutParams() |
| | | params.columnSpec = GridLayout.spec(0) |
| | | params.rowSpec = GridLayout.spec(index + 1) |
| | | layoutParams = params |
| | | } |
| | | gridLayout.addView(textView) |
| | | } |
| | | |
| | | // 添加热力图单元格 |
| | | for (day in 0..6) { |
| | | for (week in 0..51) { |
| | | val count = heatmapMatrix[day][week] |
| | | val cell = View(context).apply { |
| | | val params = GridLayout.LayoutParams().apply { |
| | | width = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) |
| | | height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) |
| | | columnSpec = GridLayout.spec(week + 1) |
| | | rowSpec = GridLayout.spec(day + 1) |
| | | setMargins(1, 1, 1, 1) |
| | | } |
| | | layoutParams = params |
| | | setBackgroundColor(getHeatmapColor(count)) |
| | | } |
| | | gridLayout.addView(cell) |
| | | } |
| | | } |
| | | |
| | | addView(gridLayout) |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun getHeatmapColor_bak(count: Int): Int { |
| | | return when { |
| | | count == 0 -> Color.parseColor("#EBEDF0") |
| | | count == 1 -> Color.parseColor("#9BE9A8") |
| | | count == 2 -> Color.parseColor("#40C463") |
| | | count <= 4 -> Color.parseColor("#30A14E") |
| | | else -> Color.parseColor("#216E39") |
| | | } |
| | | } |
| | | |
| | | private fun updateCharts() { |
| | | when (currentDateType) { |
| | | DateType.DAY -> { |
| | | // 日视图显示包裹列表,隐藏统计图表 |
| | | binding.recyclerPackages.visibility = View.VISIBLE |
| | | binding.layoutWeekStats.root.visibility = View.GONE |
| | | binding.layoutYearStats.root.visibility = View.GONE |
| | | binding.cardPackageStats.visibility = View.VISIBLE |
| | | } |
| | | DateType.WEEK, DateType.MONTH -> { |
| | | // 周和月视图显示柱状图和饼图,隐藏包裹列表 |
| | | binding.recyclerPackages.visibility = View.GONE |
| | | binding.layoutWeekStats.root.visibility = View.VISIBLE |
| | | binding.layoutYearStats.root.visibility = View.GONE |
| | | binding.layoutWeekStats.chartDailyPackages.visibility = View.VISIBLE |
| | | (binding.layoutWeekStats.chartDailyPackages.parent as? View)?.visibility = View.VISIBLE |
| | | binding.layoutWeekStats.heatmapYearly.visibility = View.GONE |
| | | binding.cardPackageStats.visibility = View.VISIBLE |
| | | updateBarChartData() |
| | | updatePieChartData() |
| | | } |
| | | DateType.YEAR -> { |
| | | // 年视图显示热力图和饼图,隐藏包裹列表和柱状图 |
| | | binding.recyclerPackages.visibility = View.GONE |
| | | binding.layoutWeekStats.root.visibility = View.VISIBLE |
| | | binding.layoutYearStats.root.visibility = View.VISIBLE |
| | | (binding.layoutWeekStats.chartDailyPackages.parent as? View)?.visibility = View.GONE |
| | | binding.layoutWeekStats.heatmapYearly.visibility = View.VISIBLE |
| | | binding.cardPackageStats.visibility = View.GONE |
| | | updateHeatmapData() |
| | | updatePieChartData() |
| | | updateYearlyStats() |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun updateYearlyStats() { |
| | | viewModel.getYearlyStats(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats: List<DailyStat> -> |
| | | if (stats.isEmpty()) return@observe |
| | | |
| | | // 更新年度包裹总数 |
| | | // binding.layoutYearStats.textTotalPackages.text = "${stats.sumOf { it.count }}个" |
| | | |
| | | // 更新平均每天包裹数 |
| | | val avgDaily = stats.sumOf { it.count }.toFloat() / 365 |
| | | binding.layoutYearStats.textDailyAverage.text = String.format("%.2f", avgDaily) |
| | | } |
| | | } |
| | | |
| | | override fun onDestroyView() { |
| | | super.onDestroyView() |
| | | _binding = null |
| | | } |
| | | private fun loadUserInfo() { |
| | | |
| | | lifecycleScope.launch { |
| | | try { |
| | | // 从本地获取保存的手机号 |
| | | val savedPhone = PreferencesManager.getPhone() |
| | | if (savedPhone.isNullOrEmpty()) { |
| | | Toast.makeText(context, "用户未登录", Toast.LENGTH_SHORT).show() |
| | | return@launch |
| | | } |
| | | |
| | | val response = RetrofitClient.apiService.getUserInfo(savedPhone) |
| | | if (response.code == "0" && response.data != null) { |
| | | // 保存用户信息 |
| | | currentUserInfo = response.data |
| | | val userInfo = response.data |
| | | if(userInfo.isMember){ |
| | | hiddleOverlay() |
| | | }else{ |
| | | // 显示遮罩层 |
| | | showOverlay() |
| | | } |
| | | |
| | | } |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | Toast.makeText(context, "获取用户信息失败", Toast.LENGTH_SHORT).show() |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |