chore: initialize Next.js project baseline
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.data/
|
||||||
|
.next/
|
||||||
|
node_modules/
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
.playwright-mcp/
|
||||||
|
.serena/
|
||||||
|
.trellis/.runtime/
|
||||||
|
.trellis/.developer
|
||||||
|
__pycache__/
|
||||||
21
AGENTS.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!-- TRELLIS:START -->
|
||||||
|
# Trellis Instructions
|
||||||
|
|
||||||
|
These instructions are for AI assistants working in this project.
|
||||||
|
|
||||||
|
This project is managed by Trellis. The working knowledge you need lives under `.trellis/`:
|
||||||
|
|
||||||
|
- `.trellis/workflow.md` — development phases, when to create tasks, skill routing
|
||||||
|
- `.trellis/spec/` — package- and layer-scoped coding guidelines (read before writing code in a given layer)
|
||||||
|
- `.trellis/workspace/` — per-developer journals and session traces
|
||||||
|
- `.trellis/tasks/` — active and archived tasks (PRDs, research, jsonl context)
|
||||||
|
|
||||||
|
If a Trellis command is available on your platform (e.g. `/trellis:finish-work`, `/trellis:continue`), prefer it over manual steps. Not every platform exposes every command.
|
||||||
|
|
||||||
|
If you're using Codex or another agent-capable tool, additional project-scoped helpers may live in:
|
||||||
|
- `.agents/skills/` — reusable Trellis skills
|
||||||
|
- `.codex/agents/` — optional custom subagents
|
||||||
|
|
||||||
|
Managed by Trellis. Edits outside this block are preserved; edits inside may be overwritten by a future `trellis update`.
|
||||||
|
|
||||||
|
<!-- TRELLIS:END -->
|
||||||
21
app/(app)/app/alerts/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Radio } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function AlertsPage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="规则预警"
|
||||||
|
eyebrow="规则中心与风险识别"
|
||||||
|
description="三期引入健康阈值、任务逾期、设备故障、应急事件和风险老人识别规则。"
|
||||||
|
icon={Radio}
|
||||||
|
phase="三期预留"
|
||||||
|
metrics={[
|
||||||
|
{ label: "规则类型", value: "5", detail: "健康、用药、护理、设备、应急" },
|
||||||
|
{ label: "预警状态", value: "4", detail: "生成、确认、处理、关闭" },
|
||||||
|
{ label: "风险维度", value: "3", detail: "健康异常、事件频次、护理强度" },
|
||||||
|
]}
|
||||||
|
workflows={["配置预警规则", "匹配业务来源数据", "生成预警记录", "确认处理并关闭"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
app/(app)/app/beds/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { BedDouble } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function BedsPage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="床位房间"
|
||||||
|
eyebrow="房间与床位状态"
|
||||||
|
description="维护楼栋、楼层、房间、床位编号和空闲、占用、维修状态流转。"
|
||||||
|
icon={BedDouble}
|
||||||
|
phase="一期"
|
||||||
|
metrics={[
|
||||||
|
{ label: "总床位", value: "154", detail: "护理区 96 张,康复区 58 张" },
|
||||||
|
{ label: "占用率", value: "82%", detail: "低于阈值时支持入住调度" },
|
||||||
|
{ label: "维修床位", value: "3", detail: "维修完成后恢复可用" },
|
||||||
|
]}
|
||||||
|
workflows={["维护楼栋与房间", "新增床位编号", "入住时占用床位", "退住或维修后释放床位"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
app/(app)/app/care/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { ClipboardCheck } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function CarePage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="护理服务"
|
||||||
|
eyebrow="护理计划、任务与巡检"
|
||||||
|
description="按护理等级制定计划,生成任务并支持接收、执行、完成和逾期标记。"
|
||||||
|
icon={ClipboardCheck}
|
||||||
|
phase="一期"
|
||||||
|
metrics={[
|
||||||
|
{ label: "今日任务", value: "184", detail: "已完成 142 项" },
|
||||||
|
{ label: "逾期任务", value: "4", detail: "集中在午间巡检和复测任务" },
|
||||||
|
{ label: "巡检打卡", value: "91%", detail: "一楼护理区已全部完成" },
|
||||||
|
]}
|
||||||
|
workflows={["配置护理等级", "创建护理计划", "派发护理任务", "巡检打卡并提交记录"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
app/(app)/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { DashboardHome } from "@/modules/dashboard/components/DashboardHome";
|
||||||
|
|
||||||
|
export default function DashboardPage(): React.ReactElement {
|
||||||
|
return <DashboardHome />;
|
||||||
|
}
|
||||||
21
app/(app)/app/devices/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Wrench } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function DevicesPage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="设备运维"
|
||||||
|
eyebrow="设备台账与维修工单"
|
||||||
|
description="维护设备编号、类型、位置、状态和责任人,支持故障上报与维修归档。"
|
||||||
|
icon={Wrench}
|
||||||
|
phase="一期"
|
||||||
|
metrics={[
|
||||||
|
{ label: "设备总数", value: "312", detail: "床旁呼叫、监测设备、公共设施" },
|
||||||
|
{ label: "故障设备", value: "2", detail: "均已生成维修工单" },
|
||||||
|
{ label: "待派单", value: "1", detail: "需维修人员确认" },
|
||||||
|
]}
|
||||||
|
workflows={["登记设备台账", "上报设备故障", "派发维修工单", "记录维修结果并恢复状态"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
app/(app)/app/emergency/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { ShieldAlert } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function EmergencyPage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="安全应急"
|
||||||
|
eyebrow="紧急呼叫与事件处理"
|
||||||
|
description="支持老人或护理人员提交紧急呼叫,形成接警、派发、处理、完成、归档闭环。"
|
||||||
|
icon={ShieldAlert}
|
||||||
|
phase="一期"
|
||||||
|
metrics={[
|
||||||
|
{ label: "今日呼叫", value: "6", detail: "平均响应 2 分 18 秒" },
|
||||||
|
{ label: "处理中", value: "1", detail: "已通知护理主管与医护人员" },
|
||||||
|
{ label: "已归档", value: "21", detail: "本周事件处理率 100%" },
|
||||||
|
]}
|
||||||
|
workflows={["提交紧急呼叫", "生成应急事件", "派发责任人员", "记录处理结果并通知相关人员"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
app/(app)/app/family/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { TabletSmartphone } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function FamilyPage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="家属服务"
|
||||||
|
eyebrow="家属端与探访反馈"
|
||||||
|
description="二期补齐家属查看老人动态、健康记录、护理记录、探访预约和服务反馈能力。"
|
||||||
|
icon={TabletSmartphone}
|
||||||
|
phase="二期预留"
|
||||||
|
metrics={[
|
||||||
|
{ label: "绑定家属", value: "214", detail: "一个老人可绑定多个授权家属" },
|
||||||
|
{ label: "探访预约", value: "12", detail: "待审核 4 条" },
|
||||||
|
{ label: "服务反馈", value: "7", detail: "2 条需要机构回复" },
|
||||||
|
]}
|
||||||
|
workflows={["绑定老人和家属账号", "查看授权范围内数据", "提交探访预约", "反馈处理与归档"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
app/(app)/app/health/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { HeartPulse } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function HealthPage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="健康照护"
|
||||||
|
eyebrow="健康档案与生命体征"
|
||||||
|
description="记录体温、血压、心率、血氧、慢病信息和异常复核建议。"
|
||||||
|
icon={HeartPulse}
|
||||||
|
phase="一期"
|
||||||
|
metrics={[
|
||||||
|
{ label: "今日记录", value: "236", detail: "覆盖重点老人 100%" },
|
||||||
|
{ label: "异常体征", value: "9", detail: "血压异常 5 条,血氧异常 1 条" },
|
||||||
|
{ label: "待医护复核", value: "3", detail: "需在 30 分钟内处理" },
|
||||||
|
]}
|
||||||
|
workflows={["录入生命体征", "维护健康摘要", "识别异常指标", "形成处理建议并归档"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
app/(app)/app/notices/page.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Bell } from "lucide-react";
|
||||||
|
|
||||||
|
import { ModulePage } from "@/modules/shared/components/ModulePage";
|
||||||
|
|
||||||
|
export default function NoticesPage(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<ModulePage
|
||||||
|
title="公告通知"
|
||||||
|
eyebrow="公告发布与阅读状态"
|
||||||
|
description="按角色和范围发布公告,区分草稿、发布、未读和已读状态。"
|
||||||
|
icon={Bell}
|
||||||
|
phase="一期"
|
||||||
|
metrics={[
|
||||||
|
{ label: "已发布公告", value: "18", detail: "本月面向全员 5 条" },
|
||||||
|
{ label: "未读消息", value: "64", detail: "护理人员未读占比最高" },
|
||||||
|
{ label: "草稿", value: "3", detail: "草稿不会对目标用户可见" },
|
||||||
|
]}
|
||||||
|
workflows={["创建公告草稿", "选择目标角色与范围", "发布通知", "跟踪阅读状态"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
app/(app)/app/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default function AppIndexPage(): never {
|
||||||
|
redirect("/app/dashboard");
|
||||||
|
}
|
||||||
5
app/(auth)/login/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { AuthPanel } from "@/modules/auth/components/AuthPanel";
|
||||||
|
|
||||||
|
export default function LoginPage(): React.ReactElement {
|
||||||
|
return <AuthPanel mode="login" />;
|
||||||
|
}
|
||||||
5
app/(auth)/register/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { AuthPanel } from "@/modules/auth/components/AuthPanel";
|
||||||
|
|
||||||
|
export default function RegisterPage(): React.ReactElement {
|
||||||
|
return <AuthPanel mode="register" />;
|
||||||
|
}
|
||||||
5
app/(auth)/setup/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { AuthPanel } from "@/modules/auth/components/AuthPanel";
|
||||||
|
|
||||||
|
export default function SetupPage(): React.ReactElement {
|
||||||
|
return <AuthPanel mode="setup" />;
|
||||||
|
}
|
||||||
5
app/icon.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<rect width="64" height="64" rx="14" fill="#2f7d52"/>
|
||||||
|
<path d="M32 15c7 0 13 5 13 12 0 12-13 22-13 22S19 39 19 27c0-7 6-12 13-12z" fill="#f4fff8"/>
|
||||||
|
<path d="M32 21v22M22 32h20" stroke="#2f7d52" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 310 B |
20
app/layout.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "TeaTea 康护智慧养老系统",
|
||||||
|
description: "AI赋能智慧养老系统后台管理端初步框架",
|
||||||
|
};
|
||||||
|
|
||||||
|
type RootLayoutProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: RootLayoutProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
app/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default function HomePage(): never {
|
||||||
|
redirect("/login");
|
||||||
|
}
|
||||||
36
auth-after-clear.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
- generic [active] [ref=f2e1] [box=0,0,2056,1020]:
|
||||||
|
- alert [ref=f2e3] [box=-1,-1,1,1]
|
||||||
|
- main [ref=f2e4] [box=0,0,2056,1020]:
|
||||||
|
- generic [ref=f2e5] [box=0,0,977,1020]:
|
||||||
|
- link "TeaTea 康护" [ref=f2e6] [cursor=pointer] [box=40,40,896,40]:
|
||||||
|
- /url: /login
|
||||||
|
- img [ref=f2e8] [box=50,50,20,20]
|
||||||
|
- text: TeaTea 康护
|
||||||
|
- generic [ref=f2e12] [box=40,453,448,139]:
|
||||||
|
- generic [ref=f2e13] [box=40,453,66,26]: First run
|
||||||
|
- heading "初始化机构" [level=1] [ref=f2e14] [box=40,499,448,48]
|
||||||
|
- paragraph [ref=f2e15] [box=40,567,448,24]: 简洁、稳定、面向机构照护。
|
||||||
|
- generic [ref=f2e16] [box=40,964,896,16]:
|
||||||
|
- generic [ref=f2e17] [box=40,964,291,16]: 档案
|
||||||
|
- generic [ref=f2e18] [box=343,964,291,16]: 护理
|
||||||
|
- generic [ref=f2e19] [box=645,964,291,16]: 预警
|
||||||
|
- generic [ref=f2e22] [box=1324,235,384,550]:
|
||||||
|
- generic [ref=f2e23] [box=1349,260,334,96]:
|
||||||
|
- img [ref=f2e25] [box=1361,272,20,20]
|
||||||
|
- heading "初始化机构" [level=2] [ref=f2e29] [box=1349,324,334,32]
|
||||||
|
- generic [ref=f2e30] [box=1349,380,334,380]:
|
||||||
|
- generic [ref=f2e31] [box=1349,380,334,68]:
|
||||||
|
- text: 机构名称
|
||||||
|
- textbox "机构名称" [ref=f2e32] [box=1349,404,334,44]
|
||||||
|
- generic [ref=f2e33] [box=1349,464,334,68]:
|
||||||
|
- text: 姓名
|
||||||
|
- textbox "姓名" [ref=f2e34] [box=1349,488,334,44]
|
||||||
|
- generic [ref=f2e35] [box=1349,548,334,68]:
|
||||||
|
- text: 邮箱
|
||||||
|
- textbox "邮箱" [ref=f2e36] [box=1349,572,334,44]
|
||||||
|
- generic [ref=f2e37] [box=1349,632,334,68]:
|
||||||
|
- text: 密码
|
||||||
|
- textbox "密码" [ref=f2e38] [box=1349,656,334,44]
|
||||||
|
- button "完成初始化" [ref=f2e39] [box=1349,716,334,44]:
|
||||||
|
- text: 完成初始化
|
||||||
|
- img [ref=f2e40] [box=1547,730,16,16]
|
||||||
36
auth-setup-snapshot.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
- generic [active] [ref=e1] [box=0,0,2056,1020]:
|
||||||
|
- main [ref=e2] [box=0,0,2056,1020]:
|
||||||
|
- generic [ref=e3] [box=0,0,977,1020]:
|
||||||
|
- link "TeaTea 康护" [ref=e4] [cursor=pointer] [box=40,40,896,40]:
|
||||||
|
- /url: /login
|
||||||
|
- img [ref=e6] [box=50,50,20,20]
|
||||||
|
- text: TeaTea 康护
|
||||||
|
- generic [ref=e10] [box=40,453,448,139]:
|
||||||
|
- generic [ref=e11] [box=40,453,66,26]: First run
|
||||||
|
- heading "初始化机构" [level=1] [ref=e12] [box=40,499,448,48]
|
||||||
|
- paragraph [ref=e13] [box=40,567,448,24]: 简洁、稳定、面向机构照护。
|
||||||
|
- generic [ref=e14] [box=40,964,896,16]:
|
||||||
|
- generic [ref=e15] [box=40,964,291,16]: 档案
|
||||||
|
- generic [ref=e16] [box=343,964,291,16]: 护理
|
||||||
|
- generic [ref=e17] [box=645,964,291,16]: 预警
|
||||||
|
- generic [ref=e20] [box=1324,235,384,550]:
|
||||||
|
- generic [ref=e21] [box=1349,260,334,96]:
|
||||||
|
- img [ref=e23] [box=1361,272,20,20]
|
||||||
|
- heading "初始化机构" [level=2] [ref=e27] [box=1349,324,334,32]
|
||||||
|
- generic [ref=e28] [box=1349,380,334,380]:
|
||||||
|
- generic [ref=e29] [box=1349,380,334,68]:
|
||||||
|
- text: 机构名称
|
||||||
|
- textbox "机构名称" [ref=e30] [box=1349,404,334,44]
|
||||||
|
- generic [ref=e31] [box=1349,464,334,68]:
|
||||||
|
- text: 姓名
|
||||||
|
- textbox "姓名" [ref=e32] [box=1349,488,334,44]
|
||||||
|
- generic [ref=e33] [box=1349,548,334,68]:
|
||||||
|
- text: 邮箱
|
||||||
|
- textbox "邮箱" [ref=e34] [box=1349,572,334,44]
|
||||||
|
- generic [ref=e35] [box=1349,632,334,68]:
|
||||||
|
- text: 密码
|
||||||
|
- textbox "密码" [ref=e36] [box=1349,656,334,44]
|
||||||
|
- button "完成初始化" [ref=e37] [box=1349,716,334,44]:
|
||||||
|
- text: 完成初始化
|
||||||
|
- img [ref=e38] [box=1547,730,16,16]
|
||||||
|
- alert [ref=e40] [box=-1,1019,1,1]
|
||||||
21
components.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "",
|
||||||
|
"css": "app/globals.css",
|
||||||
|
"baseColor": "neutral",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils",
|
||||||
|
"ui": "@/components/ui",
|
||||||
|
"lib": "@/lib",
|
||||||
|
"hooks": "@/hooks"
|
||||||
|
},
|
||||||
|
"iconLibrary": "lucide"
|
||||||
|
}
|
||||||
31
components/ui/badge.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"inline-flex items-center rounded-md border px-2.5 py-1 text-xs font-medium transition-colors",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "border-transparent bg-primary text-primary-foreground",
|
||||||
|
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
||||||
|
outline: "text-foreground",
|
||||||
|
success: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
||||||
|
warning: "border-amber-200 bg-amber-50 text-amber-700",
|
||||||
|
danger: "border-red-200 bg-red-50 text-red-700",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
export function Badge({ className, variant, ...props }: BadgeProps): React.ReactElement {
|
||||||
|
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
||||||
|
}
|
||||||
50
components/ui/button.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Slot } from "@radix-ui/react-slot";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex min-h-11 shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
outline: "border bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-11 px-4 py-2",
|
||||||
|
sm: "h-9 min-h-9 px-3",
|
||||||
|
lg: "h-12 px-6",
|
||||||
|
icon: "h-11 w-11",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: ButtonProps): React.ReactElement {
|
||||||
|
const Comp = asChild ? Slot : "button";
|
||||||
|
|
||||||
|
return <Comp className={cn(buttonVariants({ variant, size, className }))} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { buttonVariants };
|
||||||
43
components/ui/card.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export function Card({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardHeader({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||||
|
return <div className={cn("flex flex-col gap-1.5 p-5", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLHeadingElement>): React.ReactElement {
|
||||||
|
return <h3 className={cn("text-base font-semibold leading-none", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLParagraphElement>): React.ReactElement {
|
||||||
|
return <p className={cn("text-sm text-muted-foreground", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
||||||
|
return <div className={cn("p-5 pt-0", className)} {...props} />;
|
||||||
|
}
|
||||||
18
components/ui/input.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||||
|
|
||||||
|
export function Input({ className, type, ...props }: InputProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-11 w-full rounded-md border bg-background px-3 text-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
dashboard-after-setup.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
- generic [active] [ref=f2e1] [box=0,0,2056,1020]:
|
||||||
|
- alert [ref=f2e3] [box=-1,-1,1,1]
|
||||||
|
- generic [ref=f2e42] [box=0,0,2056,1020]:
|
||||||
|
- complementary [ref=f2e43] [box=0,0,256,1020]:
|
||||||
|
- generic [ref=f2e44] [box=0,0,255,1020]:
|
||||||
|
- link "TeaTea 康护" [ref=f2e45] [cursor=pointer] [box=0,0,255,64]:
|
||||||
|
- /url: /app/dashboard
|
||||||
|
- navigation "主导航" [ref=f2e53] [box=0,64,255,956]
|
||||||
|
- generic [ref=f2e127] [box=256,0,1800,1020]:
|
||||||
|
- banner [ref=f2e128] [box=256,0,1800,56]:
|
||||||
|
- paragraph [ref=f2e131] [box=288,18,42,20]: 工作台
|
||||||
|
- generic [ref=f2e132] [box=1864,10,160,36]
|
||||||
|
- main [ref=f2e142] [box=256,56,1800,964]:
|
||||||
|
- generic [ref=f2e143] [box=516,76,1280,728]
|
||||||
8
dashboard-mobile-final.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- generic [active] [ref=f2e1] [box=0,0,390,1886]:
|
||||||
|
- alert [ref=f2e3] [box=-1,-1,1,1]
|
||||||
|
- generic [ref=f2e419] [box=0,0,390,1886]:
|
||||||
|
- banner [ref=f2e420] [box=0,0,390,56]:
|
||||||
|
- generic [ref=f2e421] [box=16,8,94,40]
|
||||||
|
- generic [ref=f2e424] [box=250,10,124,36]
|
||||||
|
- main [ref=f2e434] [box=0,56,390,1830]:
|
||||||
|
- generic [ref=f2e435] [box=16,76,358,1790]
|
||||||
42
eslint.config.mjs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: import.meta.dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
".next/**",
|
||||||
|
"node_modules/**",
|
||||||
|
"out/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
".agents/**",
|
||||||
|
".claude/**",
|
||||||
|
".codex/**",
|
||||||
|
".cursor/**",
|
||||||
|
".pi/**",
|
||||||
|
".playwright-mcp/**",
|
||||||
|
".serena/**",
|
||||||
|
".spec-workflow/**",
|
||||||
|
".trae/**",
|
||||||
|
".trellis/**",
|
||||||
|
"output/**",
|
||||||
|
"tmp/**",
|
||||||
|
"teatea-dashboard-*.png",
|
||||||
|
"teatea-mobile-snapshot.md",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...compat.extends("next/core-web-vitals"),
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
6
lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]): string {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
166
modules/dashboard/components/DashboardHome.tsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
AlertTriangle,
|
||||||
|
BedDouble,
|
||||||
|
CheckCircle2,
|
||||||
|
Clock3,
|
||||||
|
HeartPulse,
|
||||||
|
ShieldAlert,
|
||||||
|
Users,
|
||||||
|
} from "lucide-react";
|
||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
type Metric = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
trend: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
const metrics: Metric[] = [
|
||||||
|
{ label: "在院", value: "126", trend: "+6 本周", icon: Users },
|
||||||
|
{ label: "床位", value: "82%", trend: "28 空闲", icon: BedDouble },
|
||||||
|
{ label: "任务", value: "184", trend: "142 完成", icon: CheckCircle2 },
|
||||||
|
{ label: "异常", value: "9", trend: "3 待复核", icon: HeartPulse },
|
||||||
|
];
|
||||||
|
|
||||||
|
const taskRows = [
|
||||||
|
{ name: "晨间生命体征记录", owner: "护理一组", status: "进行中", due: "09:30" },
|
||||||
|
{ name: "二楼失能老人巡检", owner: "护理二组", status: "待执行", due: "10:00" },
|
||||||
|
{ name: "高血压老人复测", owner: "医护组", status: "需复核", due: "10:30" },
|
||||||
|
{ name: "晚间护理计划生成", owner: "护理主管", status: "待派发", due: "16:00" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const alerts = [
|
||||||
|
{ label: "任务逾期", count: "4", tone: "warning" },
|
||||||
|
{ label: "设备故障", count: "2", tone: "danger" },
|
||||||
|
{ label: "应急处理中", count: "1", tone: "danger" },
|
||||||
|
{ label: "未读通知", count: "18", tone: "success" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function DashboardHome(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6">
|
||||||
|
<section className="grid gap-5 border-b pb-5 lg:grid-cols-[1.15fr_0.85fr]">
|
||||||
|
<div className="flex flex-col justify-end gap-4">
|
||||||
|
<Badge variant="success">一期</Badge>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-semibold tracking-normal md:text-4xl">运营看板</h1>
|
||||||
|
<p className="mt-2 text-sm text-muted-foreground">照护、床位、预警一屏处理。</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<Button>
|
||||||
|
<Users className="size-4" aria-hidden="true" />
|
||||||
|
老人建档
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline">
|
||||||
|
<ShieldAlert className="size-4" aria-hidden="true" />
|
||||||
|
处理应急事件
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 rounded-lg border bg-white/72 p-4 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium">今日闭环</p>
|
||||||
|
</div>
|
||||||
|
<Activity className="size-5 text-primary" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{["档案", "床位", "健康", "护理", "应急"].map((item, index) => (
|
||||||
|
<div key={item} className="flex items-center gap-3">
|
||||||
|
<span className="w-9 text-xs font-medium text-muted-foreground">{item}</span>
|
||||||
|
<div className="h-2 flex-1 rounded-full bg-muted">
|
||||||
|
<div
|
||||||
|
className="h-2 rounded-full bg-primary"
|
||||||
|
style={{ width: `${88 - index * 9}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="w-14 text-right text-xs text-muted-foreground">
|
||||||
|
{88 - index * 9}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
|
{metrics.map((metric) => (
|
||||||
|
<Card key={metric.label}>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
||||||
|
<CardDescription>{metric.label}</CardDescription>
|
||||||
|
<metric.icon className="size-4 text-primary" aria-hidden="true" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<CardTitle className="text-3xl">{metric.value}</CardTitle>
|
||||||
|
<p className="mt-2 text-xs text-muted-foreground">{metric.trend}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="grid gap-4 xl:grid-cols-[1.25fr_0.75fr]">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>今日护理任务</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="overflow-hidden rounded-md border">
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead className="bg-secondary text-left text-xs text-muted-foreground">
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 font-medium">任务</th>
|
||||||
|
<th className="px-4 py-3 font-medium">负责</th>
|
||||||
|
<th className="px-4 py-3 font-medium">状态</th>
|
||||||
|
<th className="px-4 py-3 text-right font-medium">截止</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y bg-card">
|
||||||
|
{taskRows.map((task) => (
|
||||||
|
<tr key={task.name}>
|
||||||
|
<td className="px-4 py-3 font-medium">{task.name}</td>
|
||||||
|
<td className="px-4 py-3 text-muted-foreground">{task.owner}</td>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<Badge variant={task.status === "需复核" ? "warning" : "secondary"}>
|
||||||
|
{task.status}
|
||||||
|
</Badge>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-3 text-right text-muted-foreground">{task.due}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>预警与工单</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{alerts.map((alert) => (
|
||||||
|
<div key={alert.label} className="flex items-center justify-between rounded-md border p-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{alert.tone === "danger" ? (
|
||||||
|
<AlertTriangle className="size-4 text-red-600" aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<Clock3 className="size-4 text-amber-600" aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
<span className="text-sm font-medium">{alert.label}</span>
|
||||||
|
</div>
|
||||||
|
<Badge variant={alert.tone}>{alert.count}</Badge>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
modules/dashboard/components/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { DashboardHome } from "./DashboardHome";
|
||||||
89
modules/shared/components/ModulePage.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
import { ArrowRight, CheckCircle2 } from "lucide-react";
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
type ModuleMetric = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
detail: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ModulePageProps = {
|
||||||
|
title: string;
|
||||||
|
eyebrow: string;
|
||||||
|
description: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
phase: "一期" | "二期预留" | "三期预留";
|
||||||
|
metrics: ModuleMetric[];
|
||||||
|
workflows: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ModulePage({
|
||||||
|
title,
|
||||||
|
eyebrow,
|
||||||
|
icon: Icon,
|
||||||
|
phase,
|
||||||
|
metrics,
|
||||||
|
workflows,
|
||||||
|
}: ModulePageProps): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6">
|
||||||
|
<section className="flex flex-col justify-between gap-4 border-b pb-5 lg:flex-row lg:items-end">
|
||||||
|
<div className="max-w-3xl">
|
||||||
|
<Badge variant={phase === "一期" ? "success" : "secondary"}>{phase}</Badge>
|
||||||
|
<div className="mt-4 flex items-center gap-3">
|
||||||
|
<span className="inline-flex size-11 items-center justify-center rounded-md bg-secondary text-primary">
|
||||||
|
<Icon className="size-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-medium text-muted-foreground">{eyebrow}</p>
|
||||||
|
<h1 className="text-2xl font-semibold tracking-normal md:text-3xl">{title}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline">筛选</Button>
|
||||||
|
<Button>
|
||||||
|
新增记录
|
||||||
|
<ArrowRight className="size-4" aria-hidden="true" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="grid gap-4 md:grid-cols-3">
|
||||||
|
{metrics.map((metric) => (
|
||||||
|
<Card key={metric.label}>
|
||||||
|
<CardHeader className="p-4">
|
||||||
|
<CardDescription>{metric.label}</CardDescription>
|
||||||
|
<CardTitle className="text-2xl">{metric.value}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-4 pt-0">
|
||||||
|
<p className="text-xs text-muted-foreground">{metric.detail}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>流转</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ol className="grid gap-3 md:grid-cols-2">
|
||||||
|
{workflows.map((workflow) => (
|
||||||
|
<li key={workflow} className="flex items-start gap-3 rounded-md border bg-secondary/30 p-3">
|
||||||
|
<CheckCircle2 className="mt-0.5 size-4 shrink-0 text-primary" aria-hidden="true" />
|
||||||
|
<span className="text-sm">{workflow}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
6
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
/// <reference path="./.next/types/routes.d.ts" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
7
next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
outputFileTracingRoot: process.cwd(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
BIN
output/doc/AI赋能智慧养老系统需求报告分析文档.docx
Normal file
1084
output/doc/AI赋能智慧养老系统需求报告分析文档.md
Normal file
35
package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "teatea-pension",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint app components lib modules next.config.ts eslint.config.mjs",
|
||||||
|
"type-check": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.468.0",
|
||||||
|
"next": "^15.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"tailwind-merge": "^3.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.0.0",
|
||||||
|
"@eslint/js": "^9.0.0",
|
||||||
|
"@tailwindcss/postcss": "^4.0.0",
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"eslint": "^9.0.0",
|
||||||
|
"eslint-config-next": "^15.0.0",
|
||||||
|
"tailwindcss": "^4.0.0",
|
||||||
|
"typescript-eslint": "^8.0.0",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
3893
pnpm-lock.yaml
generated
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- "."
|
||||||
7
postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
BIN
teatea-dashboard-desktop.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
BIN
teatea-dashboard-mobile.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
10
teatea-mobile-snapshot.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- generic [active] [ref=e1] [box=0,0,390,2174]:
|
||||||
|
- button "Open Next.js Dev Tools" [ref=e7] [cursor=pointer] [box=22,790,32,32]:
|
||||||
|
- img [ref=e8] [box=22,786,40,40]
|
||||||
|
- generic [ref=e119] [box=0,0,390,2174]:
|
||||||
|
- banner [ref=e120] [box=0,0,390,64]:
|
||||||
|
- generic [ref=e121] [box=16,12,146,40]
|
||||||
|
- generic [ref=e125] [box=162,14,212,36]
|
||||||
|
- main [ref=e133] [box=0,64,390,2110]:
|
||||||
|
- generic [ref=e134] [box=16,88,358,2062]
|
||||||
|
- alert [ref=e297] [box=-1,2173,1,1]
|
||||||
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-1.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-2.png
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-3.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-4.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-5.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-6.png
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-7.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档-page-8.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
tmp/docs/AI赋能智慧养老系统需求报告分析文档.pdf
Normal file
32
tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"],
|
||||||
|
"@/components/*": ["./components/*"],
|
||||||
|
"@/lib/*": ["./lib/*"],
|
||||||
|
"@/modules/*": ["./modules/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||