本页出处
设计风格资料库
内容来自 luckymaomi 的 UI 风格库(复刻版),以 MIT 许可 提供。本站镜像收录,仅作学习参考;这一页的原始地址是 原站页面。
← 回到导览访问原站仓库
返回模板目录
dashboardneon-gradient

健身仪表盘

暗色健身数据面板,包含步数、卡路里、运动记录、营养追踪和成就徽章。

独立打开

完整模板源代码

查看 730 行代码
"use client";

export const dynamic = "force-static";

import { useState } from "react";
import {
  Activity,
  Award,
  Dumbbell,
  Flame,
  Footprints,
  Heart,
  Moon,
  Target,
  TrendingUp,
  X,
  Zap,
} from "@/components/IconShim";
import { TemplateBackLink } from "@/components/templates/TemplateBackLink";
const stats = [
  {
    label: "Steps",
    value: "8,432",
    target: "10,000",
    icon: Footprints,
    color: "text-blue-500",
    bg: "bg-blue-50",
    progress: 84,
  },
  {
    label: "Calories",
    value: "1,847",
    target: "2,200",
    icon: Flame,
    color: "text-orange-500",
    bg: "bg-orange-50",
    progress: 84,
  },
  {
    label: "Heart Rate",
    value: "72",
    target: "bpm",
    icon: Heart,
    color: "text-red-500",
    bg: "bg-red-50",
    progress: 72,
  },
  {
    label: "Sleep",
    value: "7.2",
    target: "8h goal",
    icon: Moon,
    color: "text-indigo-500",
    bg: "bg-indigo-50",
    progress: 90,
  },
];

const workouts = [
  {
    name: "Morning Run",
    type: "Cardio",
    duration: "32 min",
    calories: 320,
    date: "Today",
    intensity: "Medium",
  },
  {
    name: "Upper Body Strength",
    type: "Strength",
    duration: "45 min",
    calories: 280,
    date: "Today",
    intensity: "High",
  },
  {
    name: "Yoga Flow",
    type: "Flexibility",
    duration: "30 min",
    calories: 150,
    date: "Yesterday",
    intensity: "Low",
  },
  {
    name: "HIIT Circuit",
    type: "Cardio",
    duration: "25 min",
    calories: 380,
    date: "Yesterday",
    intensity: "High",
  },
  {
    name: "Leg Day",
    type: "Strength",
    duration: "50 min",
    calories: 310,
    date: "2 days ago",
    intensity: "High",
  },
  {
    name: "Swimming",
    type: "Cardio",
    duration: "40 min",
    calories: 400,
    date: "3 days ago",
    intensity: "Medium",
  },
];

const activityDataByPeriod = {
  "This Week": [
    { day: "Mon", value: 65 },
    { day: "Tue", value: 80 },
    { day: "Wed", value: 45 },
    { day: "Thu", value: 90 },
    { day: "Fri", value: 70 },
    { day: "Sat", value: 55 },
    { day: "Sun", value: 40 },
  ],
  "Last Week": [
    { day: "Mon", value: 52 },
    { day: "Tue", value: 68 },
    { day: "Wed", value: 72 },
    { day: "Thu", value: 58 },
    { day: "Fri", value: 84 },
    { day: "Sat", value: 62 },
    { day: "Sun", value: 48 },
  ],
  "This Month": [
    { day: "W1", value: 58 },
    { day: "W2", value: 71 },
    { day: "W3", value: 77 },
    { day: "W4", value: 86 },
  ],
} as const;

type ActivityPeriod = keyof typeof activityDataByPeriod;

const goals = [
  { name: "Run 5K under 25 min", progress: 78, deadline: "Mar 15" },
  { name: "Bench press 100kg", progress: 65, deadline: "Apr 1" },
  { name: "30-day yoga streak", progress: 43, deadline: "Feb 28" },
  { name: "Lose 3kg body fat", progress: 55, deadline: "Mar 30" },
];

const achievements = [
  {
    name: "Early Bird",
    description: "5 workouts before 7 AM",
    icon: Zap,
    earned: true,
  },
  {
    name: "Iron Will",
    description: "30-day streak",
    icon: Award,
    earned: true,
  },
  {
    name: "Cardio King",
    description: "100km total running",
    icon: Activity,
    earned: false,
  },
  {
    name: "Powerlifter",
    description: "Bench press 1.5x body weight",
    icon: Dumbbell,
    earned: false,
  },
];

export default function FitnessHealthTemplate() {
  const [activeTab, setActiveTab] = useState<
    "overview" | "workouts" | "goals" | "nutrition"
  >("overview");
  const [selectedPeriod, setSelectedPeriod] =
    useState<ActivityPeriod>("This Week");
  const [selectedWorkout, setSelectedWorkout] = useState<number | null>(null);

  const activityData = activityDataByPeriod[selectedPeriod];
  const maxActivity = Math.max(...activityData.map((d) => d.value));

  const intensityColor = (i: string) => {
    if (i === "Low") return "text-green-600 bg-green-50";
    if (i === "Medium") return "text-amber-600 bg-amber-50";
    return "text-red-600 bg-red-50";
  };

  return (
    <div className="min-h-screen bg-gray-950 text-white">
      {/* Header */}
      <header className="border border-gray-800 bg-gray-950/80 backdrop-blur-sm sticky top-0 z-30">
        <div className="mx-auto flex max-w-7xl flex-wrap items-center gap-3 px-4 py-4 md:flex-nowrap md:px-8">
          <div className="flex items-center gap-3">
            <div className="w-9 h-9 rounded-xl bg-emerald-500 flex items-center justify-center">
              <Activity className="w-5 h-5 text-white" />
            </div>
            <span className="font-bold text-lg">FitPulse</span>
          </div>
          <nav className="order-last grid w-full grid-cols-4 gap-1 rounded-lg bg-gray-900 p-1 md:order-none md:ml-auto md:flex md:w-auto md:items-center">
            {(["overview", "workouts", "goals", "nutrition"] as const).map(
              (tab) => (
                <button
                  key={tab}
                  onClick={() => setActiveTab(tab)}
                  className={`min-w-0 rounded-md px-1.5 py-2 text-xs font-medium capitalize transition-colors sm:px-4 sm:text-sm ${
                    activeTab === tab
                      ? "bg-gray-800 text-white"
                      : "text-gray-400 hover:text-gray-200"
                  }`}
                >
                  {tab}
                </button>
              ),
            )}
          </nav>
          <div className="ml-auto flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-emerald-500/20 md:ml-3">
            <span className="text-emerald-400 text-sm font-bold">A</span>
          </div>
        </div>
      </header>

      <div className="max-w-7xl mx-auto px-4 md:px-8 py-8">
        {/* Stats Cards */}
        <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
          {stats.map((stat) => (
            <div
              key={stat.label}
              className="bg-gray-900 border border-gray-800 rounded-xl p-5"
            >
              <div className="flex items-center justify-between mb-4">
                <div
                  className={`w-10 h-10 rounded-xl ${stat.bg} flex items-center justify-center`}
                >
                  <stat.icon className={`w-5 h-5 ${stat.color}`} />
                </div>
                <span className="text-xs text-gray-500">{stat.target}</span>
              </div>
              <p className="text-2xl font-bold mb-1">{stat.value}</p>
              <p className="text-sm text-gray-500 mb-3">{stat.label}</p>
              {/* Progress bar */}
              <div className="w-full h-1.5 bg-gray-800 rounded-full overflow-hidden">
                <div
                  className={`h-full rounded-full transition-all ${stat.color.replace(
                    "text-",
                    "bg-",
                  )}`}
                  style={{ width: `${stat.progress}%` }}
                />
              </div>
            </div>
          ))}
        </div>

        {/* Activity Rings */}
        {activeTab === "overview" && (
          <div className="grid lg:grid-cols-3 gap-6 mb-8">
            {/* Progress Circles */}
            <div className="bg-gray-900 border border-gray-800 rounded-xl p-6">
              <h3 className="font-semibold mb-6">Daily Progress</h3>
              <div className="flex items-center justify-center gap-8">
                {[
                  { label: "Move", pct: 84, color: "#ef4444", size: 100 },
                  { label: "Exercise", pct: 65, color: "#22c55e", size: 80 },
                  { label: "Stand", pct: 92, color: "#3b82f6", size: 60 },
                ].map((ring) => {
                  const r = ring.size / 2 - 6;
                  const circ = 2 * Math.PI * r;
                  const offset = circ - (ring.pct / 100) * circ;
                  return (
                    <div
                      key={ring.label}
                      className="relative flex flex-col items-center"
                    >
                      <svg
                        width={ring.size}
                        height={ring.size}
                        className="-rotate-90"
                      >
                        <circle
                          cx={ring.size / 2}
                          cy={ring.size / 2}
                          r={r}
                          fill="none"
                          stroke="#1f2937"
                          strokeWidth={8}
                        />
                        <circle
                          cx={ring.size / 2}
                          cy={ring.size / 2}
                          r={r}
                          fill="none"
                          stroke={ring.color}
                          strokeWidth={8}
                          strokeLinecap="round"
                          strokeDasharray={circ}
                          strokeDashoffset={offset}
                        />
                      </svg>
                      <span className="text-xs text-gray-400 mt-2">
                        {ring.label}
                      </span>
                      <span className="text-xs font-bold">{ring.pct}%</span>
                    </div>
                  );
                })}
              </div>
            </div>

            {/* Weekly Chart */}
            <div className="lg:col-span-2 bg-gray-900 border border-gray-800 rounded-xl p-6">
              <div className="flex items-center justify-between mb-6">
                <h3 className="font-semibold">Weekly Activity</h3>
                <select
                  value={selectedPeriod}
                  onChange={(event) =>
                    setSelectedPeriod(event.target.value as ActivityPeriod)
                  }
                  className="bg-gray-800 text-sm text-gray-300 rounded-lg px-3 py-1.5 border-none"
                >
                  {(Object.keys(activityDataByPeriod) as ActivityPeriod[]).map(
                    (period) => (
                      <option key={period}>{period}</option>
                    ),
                  )}
                </select>
              </div>
              <div className="flex items-stretch justify-between gap-3 h-40">
                {activityData.map((d) => (
                  <div
                    key={d.day}
                    className="flex-1 flex flex-col items-center gap-2"
                  >
                    <span className="text-xs text-gray-400">{d.value}%</span>
                    <div className="w-full flex-1 bg-gray-800 rounded-t-lg relative">
                      <div
                        className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-emerald-500 to-emerald-400 rounded-t-lg transition-all"
                        style={{ height: `${(d.value / maxActivity) * 100}%` }}
                      />
                    </div>
                    <span className="text-xs text-gray-500">{d.day}</span>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}

        {/* Workout History */}
        {(activeTab === "overview" || activeTab === "workouts") && (
          <div className="bg-gray-900 border border-gray-800 rounded-xl p-6 mb-8">
            <div className="flex items-center justify-between mb-6">
              <h3 className="font-semibold">Workout History</h3>
              <div className="flex items-center gap-1 text-emerald-400 text-sm">
                <TrendingUp className="w-4 h-4" />
                <span>+12% this week</span>
              </div>
            </div>
            <div className="space-y-3">
              {workouts.map((w, i) => (
                <button
                  key={i}
                  onClick={() => setSelectedWorkout(i)}
                  className="w-full flex flex-col sm:flex-row sm:items-center justify-between gap-3 p-4 bg-gray-800/50 rounded-xl hover:bg-gray-800 transition-colors text-left"
                >
                  <div className="flex items-center gap-4">
                    <div className="w-10 h-10 rounded-xl bg-gray-800 flex items-center justify-center">
                      <Dumbbell className="w-5 h-5 text-gray-400" />
                    </div>
                    <div>
                      <p className="font-medium text-sm">{w.name}</p>
                      <p className="text-xs text-gray-500">
                        {w.type} / {w.date}
                      </p>
                    </div>
                  </div>
                  <div className="flex items-center gap-4 text-sm">
                    <span
                      className={`px-2 py-0.5 rounded text-xs font-medium ${intensityColor(w.intensity)}`}
                    >
                      {w.intensity}
                    </span>
                    <span className="text-gray-400">{w.duration}</span>
                    <span className="text-orange-400 flex items-center gap-1">
                      <Flame className="w-3.5 h-3.5" />
                      {w.calories} cal
                    </span>
                  </div>
                </button>
              ))}
            </div>
          </div>
        )}

        {/* Goals */}
        {(activeTab === "overview" || activeTab === "goals") && (
          <div className="grid lg:grid-cols-2 gap-6">
            <div className="bg-gray-900 border border-gray-800 rounded-xl p-6">
              <div className="flex items-center justify-between mb-6">
                <h3 className="font-semibold flex items-center gap-2">
                  <Target className="w-5 h-5 text-emerald-400" />
                  Goals
                </h3>
              </div>
              <div className="space-y-5">
                {goals.map((g) => (
                  <div key={g.name}>
                    <div className="flex items-center justify-between mb-2">
                      <span className="text-sm font-medium">{g.name}</span>
                      <span className="text-xs text-gray-500">
                        by {g.deadline}
                      </span>
                    </div>
                    <div className="flex items-center gap-3">
                      <div className="flex-1 h-2 bg-gray-800 rounded-full overflow-hidden">
                        <div
                          className="h-full bg-emerald-500 rounded-full"
                          style={{ width: `${g.progress}%` }}
                        />
                      </div>
                      <span className="text-xs font-bold text-emerald-400 w-10 text-right">
                        {g.progress}%
                      </span>
                    </div>
                  </div>
                ))}
              </div>
            </div>

            {/* Achievements */}
            <div className="bg-gray-900 border border-gray-800 rounded-xl p-6">
              <h3 className="font-semibold mb-6 flex items-center gap-2">
                <Award className="w-5 h-5 text-amber-400" />
                Achievements
              </h3>
              <div className="grid grid-cols-2 gap-4">
                {achievements.map((a) => (
                  <div
                    key={a.name}
                    className={`p-4 rounded-xl border text-center ${
                      a.earned
                        ? "bg-gray-800 border-amber-500/30"
                        : "bg-gray-800/30 border-gray-800 opacity-50"
                    }`}
                  >
                    <div
                      className={`w-12 h-12 rounded-full mx-auto mb-3 flex items-center justify-center ${
                        a.earned ? "bg-amber-500/20" : "bg-gray-800"
                      }`}
                    >
                      <a.icon
                        className={`w-6 h-6 ${a.earned ? "text-amber-400" : "text-gray-600"}`}
                      />
                    </div>
                    <p className="text-sm font-semibold mb-1">{a.name}</p>
                    <p className="text-xs text-gray-500">{a.description}</p>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}

        {/* Nutrition Tracker */}
        {activeTab === "nutrition" && (
          <div className="space-y-6 mb-8">
            <div className="grid lg:grid-cols-3 gap-6">
              {/* Macros Summary */}
              <div className="bg-gray-900 border border-gray-800 rounded-xl p-6">
                <h3 className="font-semibold mb-6">Today&apos;s Macros</h3>
                <div className="space-y-5">
                  {[
                    {
                      label: "Protein",
                      current: 95,
                      target: 140,
                      color: "bg-blue-500",
                      unit: "g",
                    },
                    {
                      label: "Carbs",
                      current: 180,
                      target: 250,
                      color: "bg-amber-500",
                      unit: "g",
                    },
                    {
                      label: "Fat",
                      current: 52,
                      target: 70,
                      color: "bg-red-500",
                      unit: "g",
                    },
                    {
                      label: "Fiber",
                      current: 18,
                      target: 30,
                      color: "bg-green-500",
                      unit: "g",
                    },
                  ].map((macro) => (
                    <div key={macro.label}>
                      <div className="flex items-center justify-between mb-2">
                        <span className="text-sm">{macro.label}</span>
                        <span className="text-xs text-gray-400">
                          {macro.current}
                          {macro.unit} / {macro.target}
                          {macro.unit}
                        </span>
                      </div>
                      <div className="h-2 bg-gray-800 rounded-full overflow-hidden">
                        <div
                          className={`h-full rounded-full ${macro.color}`}
                          style={{
                            width: `${Math.min(100, (macro.current / macro.target) * 100)}%`,
                          }}
                        />
                      </div>
                    </div>
                  ))}
                </div>
              </div>

              {/* Meal Log */}
              <div className="lg:col-span-2 bg-gray-900 border border-gray-800 rounded-xl p-6">
                <h3 className="font-semibold mb-6">Meal Log</h3>
                <div className="space-y-4">
                  {[
                    {
                      meal: "Breakfast",
                      time: "7:30 AM",
                      items: ["Oatmeal with berries", "Protein shake"],
                      cal: 420,
                    },
                    {
                      meal: "Snack",
                      time: "10:00 AM",
                      items: ["Greek yogurt", "Almonds"],
                      cal: 280,
                    },
                    {
                      meal: "Lunch",
                      time: "12:30 PM",
                      items: ["Grilled chicken salad", "Brown rice"],
                      cal: 580,
                    },
                    {
                      meal: "Post-Workout",
                      time: "3:00 PM",
                      items: ["Protein bar", "Banana"],
                      cal: 320,
                    },
                    {
                      meal: "Dinner",
                      time: "7:00 PM",
                      items: ["Salmon", "Sweet potato", "Vegetables"],
                      cal: 650,
                    },
                  ].map((entry) => (
                    <div
                      key={entry.meal}
                      className="flex items-start gap-4 p-3 bg-gray-800/50 rounded-xl"
                    >
                      <div className="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center shrink-0">
                        <Flame className="w-5 h-5 text-emerald-400" />
                      </div>
                      <div className="flex-1 min-w-0">
                        <div className="flex items-center justify-between mb-1">
                          <span className="font-medium text-sm">
                            {entry.meal}
                          </span>
                          <span className="text-xs text-gray-500">
                            {entry.time}
                          </span>
                        </div>
                        <p className="text-xs text-gray-400">
                          {entry.items.join(", ")}
                        </p>
                      </div>
                      <span className="text-sm font-semibold text-orange-400 shrink-0">
                        {entry.cal} cal
                      </span>
                    </div>
                  ))}
                </div>
                <div className="mt-4 pt-4 border border-gray-800 flex items-center justify-between">
                  <span className="text-sm text-gray-400">Total Calories</span>
                  <span className="text-lg font-bold text-emerald-400">
                    2,250 / 2,400
                  </span>
                </div>
              </div>
            </div>

            {/* Water Intake */}
            <div className="bg-gray-900 border border-gray-800 rounded-xl p-6">
              <h3 className="font-semibold mb-4">Water Intake</h3>
              <div className="flex items-center gap-3">
                {Array.from({ length: 8 }).map((_, i) => (
                  <div
                    key={i}
                    className={`flex-1 h-12 rounded-lg border-2 transition-colors ${
                      i < 5
                        ? "bg-blue-500/20 border-blue-500/40"
                        : "bg-gray-800 border-gray-700"
                    }`}
                  />
                ))}
                <span className="text-sm text-gray-400 shrink-0 ml-2">
                  5/8 glasses
                </span>
              </div>
            </div>
          </div>
        )}
      </div>

      {/* Workout Detail Modal */}
      {selectedWorkout !== null && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
          <div className="bg-gray-900 border border-gray-800 rounded-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto">
            <div className="p-6">
              <div className="flex items-start justify-between mb-6">
                <div>
                  <h2 className="text-xl font-bold mb-1">
                    {workouts[selectedWorkout].name}
                  </h2>
                  <p className="text-sm text-gray-500">
                    {workouts[selectedWorkout].type} /{" "}
                    {workouts[selectedWorkout].date}
                  </p>
                </div>
                <button
                  onClick={() => setSelectedWorkout(null)}
                  className="p-2 hover:bg-gray-800 rounded-lg transition-colors"
                  aria-label="Close"
                >
                  <X className="w-5 h-5 text-gray-400" />
                </button>
              </div>

              <div className="grid grid-cols-3 gap-4 mb-6">
                <div className="bg-gray-800 rounded-xl p-4 text-center">
                  <p className="text-xs text-gray-500 mb-1">Duration</p>
                  <p className="text-lg font-bold">
                    {workouts[selectedWorkout].duration}
                  </p>
                </div>
                <div className="bg-gray-800 rounded-xl p-4 text-center">
                  <p className="text-xs text-gray-500 mb-1">Calories</p>
                  <p className="text-lg font-bold text-orange-400">
                    {workouts[selectedWorkout].calories}
                  </p>
                </div>
                <div className="bg-gray-800 rounded-xl p-4 text-center">
                  <p className="text-xs text-gray-500 mb-1">Intensity</p>
                  <p
                    className={`text-lg font-bold ${
                      workouts[selectedWorkout].intensity === "High"
                        ? "text-red-400"
                        : workouts[selectedWorkout].intensity === "Medium"
                          ? "text-amber-400"
                          : "text-green-400"
                    }`}
                  >
                    {workouts[selectedWorkout].intensity}
                  </p>
                </div>
              </div>

              <div className="mb-6">
                <h3 className="text-sm font-semibold text-gray-400 mb-3">
                  Exercises
                </h3>
                <div className="space-y-2">
                  {[
                    { name: "Warm-up", sets: "1 x 5 min" },
                    { name: "Main Exercise A", sets: "4 x 12 reps" },
                    { name: "Main Exercise B", sets: "3 x 10 reps" },
                    { name: "Superset Circuit", sets: "3 rounds" },
                    { name: "Cool-down Stretch", sets: "1 x 5 min" },
                  ].map((ex) => (
                    <div
                      key={ex.name}
                      className="flex items-center justify-between p-3 bg-gray-800/50 rounded-lg"
                    >
                      <div className="flex items-center gap-3">
                        <div className="w-6 h-6 rounded-full bg-emerald-500/20 flex items-center justify-center">
                          <div className="w-2 h-2 rounded-full bg-emerald-500" />
                        </div>
                        <span className="text-sm">{ex.name}</span>
                      </div>
                      <span className="text-xs text-gray-500">{ex.sets}</span>
                    </div>
                  ))}
                </div>
              </div>

              <div>
                <h3 className="text-sm font-semibold text-gray-400 mb-3">
                  Heart Rate
                </h3>
                <div className="flex items-end gap-1 h-20">
                  {[45, 60, 78, 92, 88, 95, 82, 70, 65, 55, 48, 42].map(
                    (v, i) => (
                      <div
                        key={i}
                        className="flex-1 rounded-t bg-gradient-to-t from-red-500 to-orange-400"
                        style={{ height: `${v}%` }}
                      />
                    ),
                  )}
                </div>
                <div className="flex justify-between mt-2 text-[10px] text-gray-600">
                  <span>Start</span>
                  <span>Avg: 142 bpm</span>
                  <span>End</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}

      <TemplateBackLink />
    </div>
  );
}

查看关联风格