parent
3a7f2a4ac4
commit
fe5a5472bb
|
|
@ -1,2 +1,4 @@
|
|||
node_modules
|
||||
.next
|
||||
.next
|
||||
|
||||
data/
|
||||
|
|
|
|||
|
|
@ -1,132 +1,3 @@
|
|||
{"timestamp":"00:00:01.927","source":"Server","level":"LOG","message":""}
|
||||
{"timestamp":"00:00:05.166","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"00:02:55.137","source":"Server","level":"LOG","message":"✓ Compiled in 505ms"}
|
||||
{"timestamp":"00:02:55.338","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: taskGroups is not defined"}
|
||||
{"timestamp":"00:02:55.342","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: taskGroups is not defined\\u001b[39m\\n\\u001b[31m at DashboardPage (src/app/page.tsx:77:12)\\u001b[39m\\n \\u001b[90m75 |\\u001b[0m <h3 className=\\u001b[32m\\\"text-lg font-semibold mb-6\\\"\\u001b[0m>待处理任务</h3>\\n \\u001b[90m76 |\\u001b[0m <div className=\\u001b[32m\\\"grid grid-cols-2 gap-6\\\"\\u001b[0m>\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m77 |\\u001b[0m {taskGroups.map((group, gi) => (\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m78 |\\u001b[0m <div key={gi}>\\n \\u001b[90m79 |\\u001b[0m <div className=\\u001b[32m\\\"flex items-center justify-between mb-4\\\"\\u001b[0m>\\n \\u001b[90m80 |\\u001b[0m <h4 className=\\u001b[32m\\\"text-base font-medium\\\"\\u001b[0m>{group.title}</h4>\""}
|
||||
{"timestamp":"00:02:55.343","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: taskGroups is not defined\u001b[39m\n\u001b[31m at DashboardPage (src/app/page.tsx:77:12)\u001b[39m\n \u001b[90m75 |\u001b[0m <h3 className=\u001b[32m\"text-lg font-semibold mb-6\"\u001b[0m>待处理任务</h3>\n \u001b[90m76 |\u001b[0m <div className=\u001b[32m\"grid grid-cols-2 gap-6\"\u001b[0m>\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m77 |\u001b[0m {taskGroups.map((group, gi) => (\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m78 |\u001b[0m <div key={gi}>\n \u001b[90m79 |\u001b[0m <div className=\u001b[32m\"flex items-center justify-between mb-4\"\u001b[0m>\n \u001b[90m80 |\u001b[0m <h4 className=\u001b[32m\"text-base font-medium\"\u001b[0m>{group.title}</h4>"}
|
||||
{"timestamp":"00:03:27.339","source":"Server","level":"LOG","message":"✓ Compiled in 43ms"}
|
||||
{"timestamp":"00:03:27.342","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload due to a runtime error."}
|
||||
{"timestamp":"00:03:27.675","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"00:04:00.436","source":"Server","level":"LOG","message":"✓ Compiled in 31ms"}
|
||||
{"timestamp":"00:15:02.952","source":"Server","level":"LOG","message":"✓ Compiled in 50ms"}
|
||||
{"timestamp":"00:15:10.621","source":"Server","level":"LOG","message":"✓ Compiled in 34ms"}
|
||||
{"timestamp":"00:15:19.794","source":"Server","level":"LOG","message":"✓ Compiled in 36ms"}
|
||||
{"timestamp":"00:15:34.242","source":"Server","level":"LOG","message":"✓ Compiled in 59ms"}
|
||||
{"timestamp":"00:15:52.132","source":"Server","level":"LOG","message":"✓ Compiled in 71ms"}
|
||||
{"timestamp":"00:16:01.417","source":"Server","level":"LOG","message":"✓ Compiled in 53ms"}
|
||||
{"timestamp":"00:16:09.653","source":"Server","level":"LOG","message":"✓ Compiled in 34ms"}
|
||||
{"timestamp":"00:16:18.455","source":"Server","level":"LOG","message":"✓ Compiled in 53ms"}
|
||||
{"timestamp":"00:23:51.418","source":"Server","level":"LOG","message":"✓ Compiled in 54ms"}
|
||||
{"timestamp":"00:24:00.421","source":"Server","level":"LOG","message":"✓ Compiled in 32ms"}
|
||||
{"timestamp":"00:24:08.371","source":"Server","level":"LOG","message":"✓ Compiled in 39ms"}
|
||||
{"timestamp":"00:24:12.614","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"00:24:36.581","source":"Server","level":"LOG","message":"✓ Compiled in 32ms"}
|
||||
{"timestamp":"00:33:32.945","source":"Server","level":"LOG","message":"✓ Compiled in 104ms"}
|
||||
{"timestamp":"00:43:37.634","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"00:44:30.678","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"00:46:40.902","source":"Server","level":"LOG","message":"✓ Compiled in 106ms"}
|
||||
{"timestamp":"00:46:57.334","source":"Server","level":"LOG","message":"✓ Compiled in 46ms"}
|
||||
{"timestamp":"00:46:57.600","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: actionMenuId is not defined"}
|
||||
{"timestamp":"00:46:57.634","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: actionMenuId is not defined\\u001b[39m\\n\\u001b[31m at ModelsPage (src/app/models/page.tsx:249:8)\\u001b[39m\\n \\u001b[90m247 |\\u001b[0m\\n \\u001b[90m248 |\\u001b[0m {\\u001b[90m/* Click outside to close action menu */\\u001b[0m}\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m249 |\\u001b[0m {actionMenuId !== \\u001b[36mnull\\u001b[0m && (\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m250 |\\u001b[0m <div onClick={() => setActionMenuId(\\u001b[36mnull\\u001b[0m)} style={{ position: \\u001b[32m'fixed'\\u001b[0m, inset: \\u001b[35m0\\u001b[0m, zIndex: \\u001b[35m5\\u001b[0m }} />\\n \\u001b[90m251 |\\u001b[0m )}\\n \\u001b[90m252 |\\u001b[0m\""}
|
||||
{"timestamp":"00:46:57.635","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: actionMenuId is not defined\u001b[39m\n\u001b[31m at ModelsPage (src/app/models/page.tsx:249:8)\u001b[39m\n \u001b[90m247 |\u001b[0m\n \u001b[90m248 |\u001b[0m {\u001b[90m/* Click outside to close action menu */\u001b[0m}\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m249 |\u001b[0m {actionMenuId !== \u001b[36mnull\u001b[0m && (\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m250 |\u001b[0m <div onClick={() => setActionMenuId(\u001b[36mnull\u001b[0m)} style={{ position: \u001b[32m'fixed'\u001b[0m, inset: \u001b[35m0\u001b[0m, zIndex: \u001b[35m5\u001b[0m }} />\n \u001b[90m251 |\u001b[0m )}\n \u001b[90m252 |\u001b[0m"}
|
||||
{"timestamp":"00:47:05.547","source":"Server","level":"LOG","message":"✓ Compiled in 35ms"}
|
||||
{"timestamp":"00:47:05.554","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload due to a runtime error."}
|
||||
{"timestamp":"00:47:05.961","source":"Server","level":"ERROR","message":"⨯ ReferenceError: actionMenuId is not defined"}
|
||||
{"timestamp":"00:47:05.989","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: actionMenuId is not defined"}
|
||||
{"timestamp":"00:47:06.088","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: actionMenuId is not defined\\u001b[39m\\n\\u001b[31m at ModelsPage (src/app/models/page.tsx:248:8)\\n at Set.forEach (<anonymous>)\\u001b[39m\\n \\u001b[90m246 |\\u001b[0m\\n \\u001b[90m247 |\\u001b[0m {\\u001b[90m/* Click outside to close action menu */\\u001b[0m}\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m248 |\\u001b[0m {actionMenuId !== \\u001b[36mnull\\u001b[0m && (\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m249 |\\u001b[0m <div onClick={() => setActionMenuId(\\u001b[36mnull\\u001b[0m)} style={{ position: \\u001b[32m'fixed'\\u001b[0m, inset: \\u001b[35m0\\u001b[0m, zIndex: \\u001b[35m5\\u001b[0m }} />\\n \\u001b[90m250 |\\u001b[0m )}\\n \\u001b[90m251 |\\u001b[0m\""}
|
||||
{"timestamp":"00:47:06.091","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: actionMenuId is not defined\u001b[39m\n\u001b[31m at ModelsPage (src/app/models/page.tsx:248:8)\n at Set.forEach (<anonymous>)\u001b[39m\n \u001b[90m246 |\u001b[0m\n \u001b[90m247 |\u001b[0m {\u001b[90m/* Click outside to close action menu */\u001b[0m}\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m248 |\u001b[0m {actionMenuId !== \u001b[36mnull\u001b[0m && (\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m249 |\u001b[0m <div onClick={() => setActionMenuId(\u001b[36mnull\u001b[0m)} style={{ position: \u001b[32m'fixed'\u001b[0m, inset: \u001b[35m0\u001b[0m, zIndex: \u001b[35m5\u001b[0m }} />\n \u001b[90m250 |\u001b[0m )}\n \u001b[90m251 |\u001b[0m"}
|
||||
{"timestamp":"00:47:06.199","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"00:47:06.347","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: actionMenuId is not defined"}
|
||||
{"timestamp":"00:47:06.349","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: actionMenuId is not defined\\u001b[39m\\n\\u001b[31m at ModelsPage (src/app/models/page.tsx:248:8)\\u001b[39m\\n \\u001b[90m246 |\\u001b[0m\\n \\u001b[90m247 |\\u001b[0m {\\u001b[90m/* Click outside to close action menu */\\u001b[0m}\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m248 |\\u001b[0m {actionMenuId !== \\u001b[36mnull\\u001b[0m && (\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m249 |\\u001b[0m <div onClick={() => setActionMenuId(\\u001b[36mnull\\u001b[0m)} style={{ position: \\u001b[32m'fixed'\\u001b[0m, inset: \\u001b[35m0\\u001b[0m, zIndex: \\u001b[35m5\\u001b[0m }} />\\n \\u001b[90m250 |\\u001b[0m )}\\n \\u001b[90m251 |\\u001b[0m\""}
|
||||
{"timestamp":"00:47:06.349","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: actionMenuId is not defined\u001b[39m\n\u001b[31m at ModelsPage (src/app/models/page.tsx:248:8)\u001b[39m\n \u001b[90m246 |\u001b[0m\n \u001b[90m247 |\u001b[0m {\u001b[90m/* Click outside to close action menu */\u001b[0m}\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m248 |\u001b[0m {actionMenuId !== \u001b[36mnull\u001b[0m && (\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m249 |\u001b[0m <div onClick={() => setActionMenuId(\u001b[36mnull\u001b[0m)} style={{ position: \u001b[32m'fixed'\u001b[0m, inset: \u001b[35m0\u001b[0m, zIndex: \u001b[35m5\u001b[0m }} />\n \u001b[90m250 |\u001b[0m )}\n \u001b[90m251 |\u001b[0m"}
|
||||
{"timestamp":"00:47:33.817","source":"Server","level":"LOG","message":"✓ Compiled in 44ms"}
|
||||
{"timestamp":"00:47:33.825","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload due to a runtime error."}
|
||||
{"timestamp":"00:47:34.427","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"01:03:11.671","source":"Server","level":"LOG","message":"✓ Compiled in 35ms"}
|
||||
{"timestamp":"01:03:41.505","source":"Server","level":"LOG","message":"✓ Compiled in 50ms"}
|
||||
{"timestamp":"01:03:51.542","source":"Server","level":"LOG","message":"✓ Compiled in 66ms"}
|
||||
{"timestamp":"19:20:28.294","source":"Server","level":"LOG","message":"✓ Compiled in 44ms"}
|
||||
{"timestamp":"19:20:47.357","source":"Server","level":"LOG","message":"✓ Compiled in 39ms"}
|
||||
{"timestamp":"19:20:57.178","source":"Server","level":"LOG","message":"✓ Compiled in 33ms"}
|
||||
{"timestamp":"20:05:25.241","source":"Server","level":"LOG","message":"✓ Compiled in 29ms"}
|
||||
{"timestamp":"20:06:03.061","source":"Server","level":"LOG","message":"✓ Compiled in 28ms"}
|
||||
{"timestamp":"20:06:40.080","source":"Server","level":"LOG","message":"✓ Compiled in 34ms"}
|
||||
{"timestamp":"23:00:54.762","source":"Server","level":"LOG","message":"✓ Compiled in 92ms"}
|
||||
{"timestamp":"23:01:33.547","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: BomContent is not defined"}
|
||||
{"timestamp":"23:01:33.551","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: BomContent is not defined\\u001b[39m\\n\\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\\u001b[39m\\n \\u001b[90m40 |\\u001b[0m \\u001b[36mreturn\\u001b[0m (\\n \\u001b[90m41 |\\u001b[0m <\\u001b[33mSuspense\\u001b[0m fallback={<div style={{ padding: \\u001b[35m24\\u001b[0m }}>加载中...</div>}>\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m42 |\\u001b[0m <\\u001b[33mBomContent\\u001b[0m />\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m43 |\\u001b[0m </\\u001b[33mSuspense\\u001b[0m>\\n \\u001b[90m44 |\\u001b[0m )\\n \\u001b[90m45 |\\u001b[0m }\""}
|
||||
{"timestamp":"23:01:33.552","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: BomContent is not defined\u001b[39m\n\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\u001b[39m\n \u001b[90m40 |\u001b[0m \u001b[36mreturn\u001b[0m (\n \u001b[90m41 |\u001b[0m <\u001b[33mSuspense\u001b[0m fallback={<div style={{ padding: \u001b[35m24\u001b[0m }}>加载中...</div>}>\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m42 |\u001b[0m <\u001b[33mBomContent\u001b[0m />\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m43 |\u001b[0m </\u001b[33mSuspense\u001b[0m>\n \u001b[90m44 |\u001b[0m )\n \u001b[90m45 |\u001b[0m }"}
|
||||
{"timestamp":"23:02:05.108","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"23:02:07.692","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: BomContent is not defined\\u001b[39m\\n\\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\\u001b[39m\\n \\u001b[90m40 |\\u001b[0m \\u001b[36mreturn\\u001b[0m (\\n \\u001b[90m41 |\\u001b[0m <\\u001b[33mSuspense\\u001b[0m fallback={<div style={{ padding: \\u001b[35m24\\u001b[0m }}>加载中...</div>}>\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m42 |\\u001b[0m <\\u001b[33mBomContent\\u001b[0m />\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m43 |\\u001b[0m </\\u001b[33mSuspense\\u001b[0m>\\n \\u001b[90m44 |\\u001b[0m )\\n \\u001b[90m45 |\\u001b[0m }\""}
|
||||
{"timestamp":"23:02:07.693","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: BomContent is not defined\u001b[39m\n\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\u001b[39m\n \u001b[90m40 |\u001b[0m \u001b[36mreturn\u001b[0m (\n \u001b[90m41 |\u001b[0m <\u001b[33mSuspense\u001b[0m fallback={<div style={{ padding: \u001b[35m24\u001b[0m }}>加载中...</div>}>\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m42 |\u001b[0m <\u001b[33mBomContent\u001b[0m />\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m43 |\u001b[0m </\u001b[33mSuspense\u001b[0m>\n \u001b[90m44 |\u001b[0m )\n \u001b[90m45 |\u001b[0m }"}
|
||||
{"timestamp":"23:02:07.694","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: BomContent is not defined"}
|
||||
{"timestamp":"23:02:09.616","source":"Server","level":"ERROR","message":"⨯ ReferenceError: BomContent is not defined"}
|
||||
{"timestamp":"23:02:09.804","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"23:02:09.884","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: BomContent is not defined"}
|
||||
{"timestamp":"23:02:09.886","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: BomContent is not defined\\u001b[39m\\n\\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\\u001b[39m\\n \\u001b[90m40 |\\u001b[0m \\u001b[36mreturn\\u001b[0m (\\n \\u001b[90m41 |\\u001b[0m <\\u001b[33mSuspense\\u001b[0m fallback={<div style={{ padding: \\u001b[35m24\\u001b[0m }}>加载中...</div>}>\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m42 |\\u001b[0m <\\u001b[33mBomContent\\u001b[0m />\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m43 |\\u001b[0m </\\u001b[33mSuspense\\u001b[0m>\\n \\u001b[90m44 |\\u001b[0m )\\n \\u001b[90m45 |\\u001b[0m }\""}
|
||||
{"timestamp":"23:02:09.886","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: BomContent is not defined\u001b[39m\n\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\u001b[39m\n \u001b[90m40 |\u001b[0m \u001b[36mreturn\u001b[0m (\n \u001b[90m41 |\u001b[0m <\u001b[33mSuspense\u001b[0m fallback={<div style={{ padding: \u001b[35m24\u001b[0m }}>加载中...</div>}>\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m42 |\u001b[0m <\u001b[33mBomContent\u001b[0m />\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m43 |\u001b[0m </\u001b[33mSuspense\u001b[0m>\n \u001b[90m44 |\u001b[0m )\n \u001b[90m45 |\u001b[0m }"}
|
||||
{"timestamp":"23:02:12.140","source":"Server","level":"ERROR","message":"⨯ ReferenceError: BomContent is not defined"}
|
||||
{"timestamp":"23:02:12.297","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"23:02:12.478","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: BomContent is not defined\\u001b[39m\\n\\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\\u001b[39m\\n \\u001b[90m40 |\\u001b[0m \\u001b[36mreturn\\u001b[0m (\\n \\u001b[90m41 |\\u001b[0m <\\u001b[33mSuspense\\u001b[0m fallback={<div style={{ padding: \\u001b[35m24\\u001b[0m }}>加载中...</div>}>\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m42 |\\u001b[0m <\\u001b[33mBomContent\\u001b[0m />\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m43 |\\u001b[0m </\\u001b[33mSuspense\\u001b[0m>\\n \\u001b[90m44 |\\u001b[0m )\\n \\u001b[90m45 |\\u001b[0m }\""}
|
||||
{"timestamp":"23:02:12.478","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: BomContent is not defined\u001b[39m\n\u001b[31m at BomPage (src/app/models/bom/page.tsx:42:8)\u001b[39m\n \u001b[90m40 |\u001b[0m \u001b[36mreturn\u001b[0m (\n \u001b[90m41 |\u001b[0m <\u001b[33mSuspense\u001b[0m fallback={<div style={{ padding: \u001b[35m24\u001b[0m }}>加载中...</div>}>\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m42 |\u001b[0m <\u001b[33mBomContent\u001b[0m />\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m43 |\u001b[0m </\u001b[33mSuspense\u001b[0m>\n \u001b[90m44 |\u001b[0m )\n \u001b[90m45 |\u001b[0m }"}
|
||||
{"timestamp":"23:02:12.479","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: BomContent is not defined"}
|
||||
{"timestamp":"23:02:16.733","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"23:06:18.411","source":"Server","level":"LOG","message":"✓ Compiled in 28ms"}
|
||||
{"timestamp":"88:23:26.407","source":"Server","level":"LOG","message":"✓ Compiled in 50ms"}
|
||||
{"timestamp":"88:23:40.714","source":"Server","level":"LOG","message":"✓ Compiled in 34ms"}
|
||||
{"timestamp":"88:23:40.900","source":"Browser","level":"ERROR","message":"uncaughtError: ReferenceError: acqVersionConsistent is not defined"}
|
||||
{"timestamp":"88:23:40.939","source":"Server","level":"ERROR","message":"[browser] \"\\u001b[31mUncaught ReferenceError: acqVersionConsistent is not defined\\u001b[39m\\n\\u001b[31m at BomContent (src/app/models/bom/page.tsx:107:32)\\n at BomPage (src/app/models/bom/page.tsx:40:25)\\u001b[39m\\n \\u001b[90m105 |\\u001b[0m 要求同一台设备的采集板版本必须一致\\n \\u001b[90m106 |\\u001b[0m </label>\\n\\u001b[31m\\u001b[1m>\\u001b[0m \\u001b[90m107 |\\u001b[0m {enforceAcqVersion && !acqVersionConsistent && (\\n \\u001b[90m |\\u001b[0m \\u001b[31m\\u001b[1m^\\u001b[0m\\n \\u001b[90m108 |\\u001b[0m <span style={{ display: \\u001b[32m'flex'\\u001b[0m, alignItems: \\u001b[32m'center'\\u001b[0m, gap: \\u001b[35m4\\u001b[0m, fontSize: \\u001b[35m12\\u001b[0m, color: \\u001b[32m'#FF4D4F'\\u001b[0m }}>\\n \\u001b[90m109 |\\u001b[0m <\\u001b[33mAlertTriangle\\u001b[0m size={\\u001b[35m13\\u001b[0m} />当前采集板配置的版本列表不一致\\n \\u001b[90m110 |\\u001b[0m </span>\""}
|
||||
{"timestamp":"88:23:40.941","source":"Browser","level":"ERROR","message":"\u001b[31mUncaught ReferenceError: acqVersionConsistent is not defined\u001b[39m\n\u001b[31m at BomContent (src/app/models/bom/page.tsx:107:32)\n at BomPage (src/app/models/bom/page.tsx:40:25)\u001b[39m\n \u001b[90m105 |\u001b[0m 要求同一台设备的采集板版本必须一致\n \u001b[90m106 |\u001b[0m </label>\n\u001b[31m\u001b[1m>\u001b[0m \u001b[90m107 |\u001b[0m {enforceAcqVersion && !acqVersionConsistent && (\n \u001b[90m |\u001b[0m \u001b[31m\u001b[1m^\u001b[0m\n \u001b[90m108 |\u001b[0m <span style={{ display: \u001b[32m'flex'\u001b[0m, alignItems: \u001b[32m'center'\u001b[0m, gap: \u001b[35m4\u001b[0m, fontSize: \u001b[35m12\u001b[0m, color: \u001b[32m'#FF4D4F'\u001b[0m }}>\n \u001b[90m109 |\u001b[0m <\u001b[33mAlertTriangle\u001b[0m size={\u001b[35m13\u001b[0m} />当前采集板配置的版本列表不一致\n \u001b[90m110 |\u001b[0m </span>"}
|
||||
{"timestamp":"88:23:56.831","source":"Server","level":"WARN","message":"⚠ Fast Refresh had to perform a full reload due to a runtime error."}
|
||||
{"timestamp":"88:23:56.833","source":"Server","level":"LOG","message":"✓ Compiled in 116ms"}
|
||||
{"timestamp":"88:23:57.529","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"88:24:06.404","source":"Server","level":"LOG","message":"✓ Compiled in 55ms"}
|
||||
{"timestamp":"88:24:17.728","source":"Server","level":"LOG","message":"✓ Compiled in 58ms"}
|
||||
{"timestamp":"88:24:27.923","source":"Server","level":"LOG","message":"✓ Compiled in 48ms"}
|
||||
{"timestamp":"88:24:43.110","source":"Server","level":"LOG","message":"✓ Compiled in 43ms"}
|
||||
{"timestamp":"88:35:01.386","source":"Server","level":"LOG","message":"✓ Compiled in 105ms"}
|
||||
{"timestamp":"88:35:29.636","source":"Server","level":"LOG","message":"✓ Compiled in 142ms"}
|
||||
{"timestamp":"88:35:40.143","source":"Server","level":"LOG","message":"✓ Compiled in 64ms"}
|
||||
{"timestamp":"88:35:49.785","source":"Server","level":"LOG","message":"✓ Compiled in 56ms"}
|
||||
{"timestamp":"88:36:08.901","source":"Server","level":"LOG","message":"✓ Compiled in 88ms"}
|
||||
{"timestamp":"88:58:06.224","source":"Server","level":"LOG","message":"✓ Compiled in 36ms"}
|
||||
{"timestamp":"88:58:17.207","source":"Server","level":"LOG","message":"✓ Compiled in 74ms"}
|
||||
{"timestamp":"88:58:34.824","source":"Server","level":"LOG","message":"✓ Compiled in 34ms"}
|
||||
{"timestamp":"88:58:48.965","source":"Server","level":"LOG","message":"✓ Compiled in 38ms"}
|
||||
{"timestamp":"88:58:59.198","source":"Server","level":"LOG","message":"✓ Compiled in 53ms"}
|
||||
{"timestamp":"89:00:56.122","source":"Server","level":"LOG","message":"✓ Compiled in 56ms"}
|
||||
{"timestamp":"89:01:12.179","source":"Server","level":"LOG","message":"✓ Compiled in 43ms"}
|
||||
{"timestamp":"89:01:26.485","source":"Server","level":"LOG","message":"✓ Compiled in 64ms"}
|
||||
{"timestamp":"89:01:33.256","source":"Server","level":"LOG","message":"✓ Compiled in 34ms"}
|
||||
{"timestamp":"89:02:11.783","source":"Server","level":"LOG","message":"✓ Compiled in 41ms"}
|
||||
{"timestamp":"89:02:29.234","source":"Server","level":"LOG","message":"✓ Compiled in 49ms"}
|
||||
{"timestamp":"89:02:38.925","source":"Server","level":"LOG","message":"✓ Compiled in 41ms"}
|
||||
{"timestamp":"89:26:07.880","source":"Server","level":"LOG","message":"✓ Compiled in 64ms"}
|
||||
{"timestamp":"89:26:16.444","source":"Server","level":"LOG","message":"✓ Compiled in 33ms"}
|
||||
{"timestamp":"89:26:28.187","source":"Server","level":"LOG","message":"✓ Compiled in 35ms"}
|
||||
{"timestamp":"89:26:41.392","source":"Server","level":"LOG","message":"✓ Compiled in 41ms"}
|
||||
{"timestamp":"89:26:51.328","source":"Server","level":"LOG","message":"✓ Compiled in 51ms"}
|
||||
{"timestamp":"89:50:00.224","source":"Server","level":"LOG","message":"✓ Compiled in 30ms"}
|
||||
{"timestamp":"89:50:02.783","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"89:51:22.343","source":"Server","level":"LOG","message":"✓ Compiled in 57ms"}
|
||||
{"timestamp":"91:29:09.929","source":"Server","level":"LOG","message":"✓ Compiled in 37ms"}
|
||||
{"timestamp":"91:29:22.593","source":"Server","level":"LOG","message":"✓ Compiled in 36ms"}
|
||||
{"timestamp":"91:30:09.323","source":"Server","level":"LOG","message":"✓ Compiled in 38ms"}
|
||||
{"timestamp":"91:32:54.635","source":"Server","level":"LOG","message":"✓ Compiled in 54ms"}
|
||||
{"timestamp":"91:33:04.580","source":"Server","level":"LOG","message":"✓ Compiled in 115ms"}
|
||||
{"timestamp":"91:33:16.061","source":"Server","level":"LOG","message":"✓ Compiled in 50ms"}
|
||||
{"timestamp":"91:33:16.230","source":"Browser","level":"ERROR","message":"A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components"}
|
||||
{"timestamp":"91:33:16.255","source":"Server","level":"ERROR","message":"[browser] \"A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components\" \"\""}
|
||||
{"timestamp":"91:33:16.256","source":"Browser","level":"ERROR","message":"A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components \"\""}
|
||||
{"timestamp":"91:33:25.029","source":"Server","level":"LOG","message":"✓ Compiled in 44ms"}
|
||||
{"timestamp":"91:33:35.115","source":"Server","level":"LOG","message":"✓ Compiled in 51ms"}
|
||||
{"timestamp":"91:35:27.639","source":"Server","level":"LOG","message":"✓ Compiled in 57ms"}
|
||||
{"timestamp":"91:35:38.098","source":"Server","level":"LOG","message":"✓ Compiled in 41ms"}
|
||||
{"timestamp":"91:36:21.846","source":"Server","level":"LOG","message":"✓ Compiled in 134ms"}
|
||||
{"timestamp":"91:46:26.786","source":"Server","level":"LOG","message":"✓ Compiled in 54ms"}
|
||||
{"timestamp":"91:46:46.111","source":"Server","level":"LOG","message":"✓ Compiled in 116ms"}
|
||||
{"timestamp":"91:47:02.457","source":"Server","level":"LOG","message":"✓ Compiled in 58ms"}
|
||||
{"timestamp":"91:51:26.728","source":"Server","level":"LOG","message":"✓ Compiled in 128ms"}
|
||||
{"timestamp":"91:51:39.901","source":"Server","level":"LOG","message":"✓ Compiled in 40ms"}
|
||||
{"timestamp":"91:52:32.250","source":"Server","level":"LOG","message":"✓ Compiled in 43ms"}
|
||||
{"timestamp":"91:52:43.610","source":"Server","level":"LOG","message":"✓ Compiled in 34ms"}
|
||||
{"timestamp":"91:53:08.498","source":"Server","level":"LOG","message":"✓ Compiled in 38ms"}
|
||||
{"timestamp":"00:00:02.550","source":"Server","level":"LOG","message":""}
|
||||
{"timestamp":"00:00:09.331","source":"Browser","level":"INFO","message":"%cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold"}
|
||||
{"timestamp":"00:01:35.847","source":"Server","level":"LOG","message":"✓ Compiled in 898ms"}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
"dynamicRoutes": {},
|
||||
"notFoundRoutes": [],
|
||||
"preview": {
|
||||
"previewModeId": "1947d233f6bd907465d44e9106e1529f",
|
||||
"previewModeSigningKey": "19d915bf45327c04933031dcd1bf3dbb51437d192bd72e75782313759599b542",
|
||||
"previewModeEncryptionKey": "7b1b69cc42bec06b4a05f44a9eaed9da30d6309359a920dd5e4d809ba31844c1"
|
||||
"previewModeId": "c55ffe53b7d1e0d2210bb27bdcda1a52",
|
||||
"previewModeSigningKey": "29086773d8e35b0885c5db516b53db2363bab0a6a4585596e30d4b3fc76aec56",
|
||||
"previewModeEncryptionKey": "0bac8c20a0a44a84b1dbe2603c546b8176144734ca42089bab1ebc967f277a1a"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
{
|
||||
"/board-cards/page": "app/board-cards/page.js",
|
||||
"/board-cards/register/page": "app/board-cards/register/page.js",
|
||||
"/boards/page": "app/boards/page.js",
|
||||
"/api/devices/route": "app/api/devices/route.js",
|
||||
"/api/material-types/route": "app/api/material-types/route.js",
|
||||
"/api/material-versions/route": "app/api/material-versions/route.js",
|
||||
"/api/materials/route": "app/api/materials/route.js",
|
||||
"/api/models/bom/route": "app/api/models/bom/route.js",
|
||||
"/api/models/checklist/route": "app/api/models/checklist/route.js",
|
||||
"/api/models/route": "app/api/models/route.js",
|
||||
"/api/repair/route": "app/api/repair/route.js",
|
||||
"/api/scrap/route": "app/api/scrap/route.js",
|
||||
"/config-files/page": "app/config-files/page.js",
|
||||
"/devices/[sn]/page": "app/devices/[sn]/page.js",
|
||||
"/devices/page": "app/devices/page.js",
|
||||
"/firmware/page": "app/firmware/page.js",
|
||||
"/licenses/page": "app/licenses/page.js",
|
||||
"/materials/manage/page": "app/materials/manage/page.js",
|
||||
"/materials/page": "app/materials/page.js",
|
||||
"/materials/register/page": "app/materials/register/page.js",
|
||||
"/models/bom/page": "app/models/bom/page.js",
|
||||
"/models/page": "app/models/page.js",
|
||||
"/page": "app/page.js",
|
||||
"/registration/page": "app/registration/page.js",
|
||||
"/repair/page": "app/repair/page.js",
|
||||
"/scrap/page": "app/scrap/page.js"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
375
.next/dev/trace
375
.next/dev/trace
File diff suppressed because one or more lines are too long
|
|
@ -1,24 +1,39 @@
|
|||
// This file is generated automatically by Next.js
|
||||
// Do not edit this file manually
|
||||
|
||||
type AppRoutes = "/" | "/board-cards" | "/board-cards/register" | "/boards" | "/config-files" | "/devices" | "/devices/[sn]" | "/firmware" | "/licenses" | "/models" | "/models/bom" | "/registration" | "/repair" | "/scrap"
|
||||
type AppRoutes = "/" | "/config-files" | "/devices" | "/devices/[sn]" | "/firmware" | "/licenses" | "/materials" | "/materials/manage" | "/materials/register" | "/models" | "/models/bom" | "/registration" | "/repair" | "/scrap"
|
||||
type AppRouteHandlerRoutes = "/api/board-types" | "/api/boards" | "/api/config-files" | "/api/devices" | "/api/firmware" | "/api/licenses" | "/api/material-types" | "/api/material-versions" | "/api/materials" | "/api/models" | "/api/models/bom" | "/api/models/checklist" | "/api/repair" | "/api/scrap"
|
||||
type PageRoutes = never
|
||||
type LayoutRoutes = "/"
|
||||
type RedirectRoutes = never
|
||||
type RewriteRoutes = never
|
||||
type Routes = AppRoutes | PageRoutes | LayoutRoutes | RedirectRoutes | RewriteRoutes
|
||||
type Routes = AppRoutes | PageRoutes | LayoutRoutes | RedirectRoutes | RewriteRoutes | AppRouteHandlerRoutes
|
||||
|
||||
|
||||
interface ParamMap {
|
||||
"/": {}
|
||||
"/board-cards": {}
|
||||
"/board-cards/register": {}
|
||||
"/boards": {}
|
||||
"/api/board-types": {}
|
||||
"/api/boards": {}
|
||||
"/api/config-files": {}
|
||||
"/api/devices": {}
|
||||
"/api/firmware": {}
|
||||
"/api/licenses": {}
|
||||
"/api/material-types": {}
|
||||
"/api/material-versions": {}
|
||||
"/api/materials": {}
|
||||
"/api/models": {}
|
||||
"/api/models/bom": {}
|
||||
"/api/models/checklist": {}
|
||||
"/api/repair": {}
|
||||
"/api/scrap": {}
|
||||
"/config-files": {}
|
||||
"/devices": {}
|
||||
"/devices/[sn]": { "sn": string; }
|
||||
"/firmware": {}
|
||||
"/licenses": {}
|
||||
"/materials": {}
|
||||
"/materials/manage": {}
|
||||
"/materials/register": {}
|
||||
"/models": {}
|
||||
"/models/bom": {}
|
||||
"/registration": {}
|
||||
|
|
@ -34,7 +49,7 @@ interface LayoutSlotMap {
|
|||
}
|
||||
|
||||
|
||||
export type { AppRoutes, PageRoutes, LayoutRoutes, RedirectRoutes, RewriteRoutes, ParamMap }
|
||||
export type { AppRoutes, PageRoutes, LayoutRoutes, RedirectRoutes, RewriteRoutes, ParamMap, AppRouteHandlerRoutes }
|
||||
|
||||
declare global {
|
||||
/**
|
||||
|
|
@ -67,4 +82,18 @@ declare global {
|
|||
} & {
|
||||
[K in LayoutSlotMap[LayoutRoute]]: React.ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Context for Next.js App Router route handlers
|
||||
* @example
|
||||
* ```tsx
|
||||
* export async function GET(request: NextRequest, context: RouteContext<'/api/users/[id]'>) {
|
||||
* const { id } = await context.params
|
||||
* return Response.json({ id })
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
interface RouteContext<AppRouteHandlerRoute extends AppRouteHandlerRoutes> {
|
||||
params: Promise<ParamMap[AppRouteHandlerRoute]>
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
// Do not edit this file manually
|
||||
// This file validates that all pages and layouts export the correct types
|
||||
|
||||
import type { AppRoutes, LayoutRoutes, ParamMap } from "./routes.js"
|
||||
import type { AppRoutes, LayoutRoutes, ParamMap, AppRouteHandlerRoutes } from "./routes.js"
|
||||
import type { ResolvingMetadata, ResolvingViewport } from "next/types.js"
|
||||
import type { NextRequest } from 'next/server.js'
|
||||
|
||||
type AppPageConfig<Route extends AppRoutes = AppRoutes> = {
|
||||
default: React.ComponentType<{ params: Promise<ParamMap[Route]> } & any> | ((props: { params: Promise<ParamMap[Route]> } & any) => React.ReactNode | Promise<React.ReactNode> | never | void | Promise<void>)
|
||||
|
|
@ -35,33 +36,16 @@ type LayoutConfig<Route extends LayoutRoutes = LayoutRoutes> = {
|
|||
viewport?: any
|
||||
}
|
||||
|
||||
|
||||
// Validate ../../../src/app/board-cards/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/board-cards">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/board-cards/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
type RouteHandlerConfig<Route extends AppRouteHandlerRoutes = AppRouteHandlerRoutes> = {
|
||||
GET?: (request: NextRequest, context: { params: Promise<ParamMap[Route]> }) => Promise<Response | void> | Response | void
|
||||
POST?: (request: NextRequest, context: { params: Promise<ParamMap[Route]> }) => Promise<Response | void> | Response | void
|
||||
PUT?: (request: NextRequest, context: { params: Promise<ParamMap[Route]> }) => Promise<Response | void> | Response | void
|
||||
PATCH?: (request: NextRequest, context: { params: Promise<ParamMap[Route]> }) => Promise<Response | void> | Response | void
|
||||
DELETE?: (request: NextRequest, context: { params: Promise<ParamMap[Route]> }) => Promise<Response | void> | Response | void
|
||||
HEAD?: (request: NextRequest, context: { params: Promise<ParamMap[Route]> }) => Promise<Response | void> | Response | void
|
||||
OPTIONS?: (request: NextRequest, context: { params: Promise<ParamMap[Route]> }) => Promise<Response | void> | Response | void
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/board-cards/register/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/board-cards/register">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/board-cards/register/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/boards/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/boards">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/boards/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/config-files/page.tsx
|
||||
{
|
||||
|
|
@ -108,6 +92,33 @@ type LayoutConfig<Route extends LayoutRoutes = LayoutRoutes> = {
|
|||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/materials/manage/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/materials/manage">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/materials/manage/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/materials/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/materials">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/materials/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/materials/register/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/materials/register">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/materials/register/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/models/bom/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/models/bom">> = Specific
|
||||
|
|
@ -162,7 +173,131 @@ type LayoutConfig<Route extends LayoutRoutes = LayoutRoutes> = {
|
|||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/board-types/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/board-types">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/board-types/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/boards/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/boards">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/boards/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/config-files/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/config-files">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/config-files/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/devices/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/devices">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/devices/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/firmware/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/firmware">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/firmware/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/licenses/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/licenses">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/licenses/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/material-types/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/material-types">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/material-types/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/material-versions/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/material-versions">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/material-versions/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/materials/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/materials">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/materials/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/models/bom/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/models/bom">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/models/bom/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/models/checklist/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/models/checklist">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/models/checklist/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/models/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/models">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/models/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/repair/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/repair">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/repair/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../src/app/api/scrap/route.ts
|
||||
{
|
||||
type __IsExpected<Specific extends RouteHandlerConfig<"/api/scrap">> = Specific
|
||||
const handler = {} as typeof import("../../../src/app/api/scrap/route.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
# 地空业务支撑平台 —— 生产管理子系统 流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
%% ===== 入口 =====
|
||||
HOME["首页 Dashboard\n设备统计(8项指标) · 设备状态分布\n待处理任务(4组) · 快捷导航"]
|
||||
|
||||
%% ===== 阶段零:型号管理(核心枢纽)=====
|
||||
subgraph S0["型号管理(核心枢纽)"]
|
||||
MODEL["设备型号管理\n型号列表 · 在产/停产\n新增型号抽屉"]
|
||||
MODEL -->|绑定| AUTH_FILE["授权文件\n按型号绑定授权项"]
|
||||
MODEL -->|绑定| CFG_FILE["配置文件\n按型号绑定"]
|
||||
MODEL -->|绑定| FW_FILE["固件文件\n主机/主协板/发射板/采集板"]
|
||||
MODEL -->|管理| CHECKLIST["装配Checklist模板\nTab切换型号 · 可编辑\n新增模版抽屉"]
|
||||
MODEL -->|管理| BOARD_MGR["板卡型号管理\n主协板/采集板/发射板/升压板\nTab筛选 · 详情抽屉"]
|
||||
end
|
||||
|
||||
%% ===== 阶段一:固件与校准 =====
|
||||
subgraph S1["阶段一:固件与校准"]
|
||||
FW_LIB["固件库\n主机固件 · 主协板固件\n发射板固件 · 采集板固件"]
|
||||
FW_LIB -->|上传zip| FW_UPLOAD["上传固件弹窗(Element Plus)\n版本号 · 硬件版本范围\n固件类型(5种) · 升级类型\n数字签名 · 发布说明"]
|
||||
CALIB["采集板校准管理\n校准记录列表 · 导入\n合格/不合格/待校准"]
|
||||
end
|
||||
|
||||
%% ===== 阶段二:生产装配 =====
|
||||
subgraph S2["阶段二:生产装配"]
|
||||
DEV_REG["设备登记\n装机信息表单 · 型号匹配提示\nBOM清单 · 装配Checklist"]
|
||||
DEV_REG -->|导入BOM| IMPORT_DIALOG["Excel导入弹窗\n下载模板 · 上传文件\n支持xlsx/xls/csv"]
|
||||
DEV_REG -->|拍照上传| PHOTO_DIALOG["照片上传弹窗\n多张照片 · 装配记录信息"]
|
||||
DEV_REG -->|登记完成| DEV_LIST["设备列表\n5项筛选 · 卡片式列表\n导出 · 分页"]
|
||||
DEV_LIST -->|点击详情| DEV_DETAIL["设备详情页\n基本信息 · 授权信息(含模块列表)\n装配记录(含Checklist摘要)\n子设备列表(5种板卡)\n固件信息 · 维修历史(时间线)"]
|
||||
end
|
||||
|
||||
%% ===== 阶段三:授权出厂 =====
|
||||
subgraph S3["阶段三:授权出厂"]
|
||||
LIC_MGR["授权管理\n授权文件列表 · 按型号筛选\n已发布/草稿/已停用"]
|
||||
LIC_MGR -->|选择授权项| LIC_DRAWER["授权项抽屉(640px)\n选择型号(自动预选) · 有效期\n11项功能模块勾选\n全选/清空 · 保存"]
|
||||
end
|
||||
|
||||
%% ===== 阶段四:APP使用(非平台)=====
|
||||
subgraph S4["阶段四:APP使用"]
|
||||
APP["APP端操作"]
|
||||
APP -->|读取UID/SN匹配| CALIB
|
||||
APP -->|按型号下载| AUTH_FILE
|
||||
APP -->|按型号下载| CFG_FILE
|
||||
APP -->|按型号下载| FW_FILE
|
||||
end
|
||||
|
||||
%% ===== 阶段五:配置管理 =====
|
||||
subgraph S5["阶段五:配置管理"]
|
||||
CFG_MGR["配置文件管理\n配置列表 · 筛选(型号/版本/关键字)\n生效/已停用"]
|
||||
CFG_MGR -->|新建配置抽屉| CFG_NEW["新建配置抽屉(520px)\n基本信息 · 发射参数\n采集参数 · 网络参数"]
|
||||
CFG_MGR -->|编辑详情| PARAM_CFG["参数配置页\n发射参数(电压/电流/脉宽/波形/占空比)\n采集参数(量程/采样率/通道数)\n保护参数(过压/过流/短路/高温)\n网络参数(WiFi前缀)"]
|
||||
end
|
||||
|
||||
%% ===== 阶段六:维修运维 =====
|
||||
subgraph S6["阶段六:维修运维"]
|
||||
REPAIR["维修工单列表\n6项筛选 · 卡片式列表\n状态(处理中/已处理/待处理)\n优先级(高/中/低)"]
|
||||
REPAIR -->|新建工单抽屉| NEW_ORDER["新建工单抽屉(520px)\n设备信息(SN选择+设备查找)\n故障信息(类型/描述/现象)\n工单信息(优先级/人员/时间/备注)"]
|
||||
REPAIR -->|处理抽屉| PROCESS["处理工单抽屉(520px)\n处理操作(更换板卡/固件修复/参数重置/其他)\n板卡更换(原SN→新SN)\n授权处理(重新生成/推送固件)\n报废处理(选择原因→申请报废)"]
|
||||
REPAIR -->|详情抽屉| ORDER_DRAWER["工单详情抽屉(540px)\n工单/设备/故障信息\n处理记录 · 板卡更换记录\n授权处理 · 关闭工单"]
|
||||
REPAIR -->|独立详情页| ORDER_PAGE["工单详情页(/repair/:orderId)\n工单/设备/故障信息\n处理记录 · 板卡更换记录\n授权处理\n关闭工单 · 申请报废"]
|
||||
REPAIR -->|维修统计| REPAIR_STATS["维修统计页\n时间范围筛选(5个Tab)\n4项统计指标\n故障类型分布(条形图)\n维修趋势(折线图)"]
|
||||
end
|
||||
|
||||
%% ===== 阶段七:报废回收 =====
|
||||
subgraph S7["阶段七:报废回收"]
|
||||
SCRAP["报废管理\n审批流程(5步)\n4项统计 · 筛选\n报废设备列表"]
|
||||
SCRAP -->|详情抽屉| SCRAP_DETAIL["报废详情抽屉(520px)\n设备信息 · 残值评估\n审批信息 · 可回收物料\n审批记录时间线"]
|
||||
SCRAP -->|审批抽屉| SCRAP_APPROVE["报废审批抽屉(480px)\n⚠️不可逆警告\n设备摘要 · 可回收物料\n审批意见\n驳回/审批通过"]
|
||||
SCRAP -->|回收入库抽屉| SCRAP_RECYCLE["物料回收入库抽屉(480px)\n物料检测勾选\n回收备注\n确认回收入库"]
|
||||
end
|
||||
|
||||
%% ===== 首页导航(8项指标跳转) =====
|
||||
HOME -->|设备总数/装配中/已激活| DEV_LIST
|
||||
HOME -->|有新版本/可升级| FW_LIB
|
||||
HOME -->|维修中| REPAIR
|
||||
HOME -->|报废| SCRAP
|
||||
HOME -->|授权即将到期| LIC_MGR
|
||||
HOME -->|校准即将到期| CALIB
|
||||
HOME --> MODEL
|
||||
|
||||
%% ===== 型号管理跨模块关联 =====
|
||||
MODEL -->|授权按钮| LIC_MGR
|
||||
MODEL -->|配置按钮| CFG_MGR
|
||||
MODEL -->|Checklist模板| DEV_REG
|
||||
|
||||
%% ===== 板卡管理关联 =====
|
||||
BOARD_MGR -->|固件按钮| FW_LIB
|
||||
BOARD_MGR -->|详情→服役记录| DEV_DETAIL
|
||||
BOARD_MGR -->|详情→维修记录| REPAIR
|
||||
|
||||
%% ===== 跨阶段数据流 =====
|
||||
DEV_REG -->|型号必须匹配| MODEL
|
||||
DEV_DETAIL -->|查看校准| CALIB
|
||||
DEV_DETAIL -->|查看维修历史| REPAIR
|
||||
PROCESS -->|申请报废| SCRAP
|
||||
ORDER_PAGE -->|申请报废| SCRAP
|
||||
ORDER_DRAWER -->|更换采集板需重新校准| CALIB
|
||||
SCRAP -->|关联来源工单| ORDER_PAGE
|
||||
SCRAP_RECYCLE -->|回收入库| DEV_REG
|
||||
|
||||
%% ===== 支撑模块 =====
|
||||
subgraph SUPPORT["支撑模块(Header菜单)"]
|
||||
DATA_STATS["数据报表(占位)"]
|
||||
OPS_RPT["运营报告(占位)"]
|
||||
USER_MGR["用户管理(占位)"]
|
||||
ROLE_MGR["角色权限(占位)"]
|
||||
SYS_LOG["操作日志(占位)"]
|
||||
SYS_SET["系统设置(占位)"]
|
||||
end
|
||||
|
||||
%% 样式
|
||||
style S0 fill:#F0F2F5,stroke:#8C8C8C,stroke-width:2px
|
||||
style S1 fill:#F9F0FF,stroke:#722ED1,stroke-width:2px
|
||||
style S2 fill:#eef5f0,stroke:#4a7c59,stroke-width:2px
|
||||
style S3 fill:#E6FFFB,stroke:#13C2C2,stroke-width:2px
|
||||
style S4 fill:#F6FFED,stroke:#52C41A,stroke-width:2px,stroke-dasharray: 5 5
|
||||
style S5 fill:#FFF7E6,stroke:#FA8C16,stroke-width:2px
|
||||
style S6 fill:#FFF1F0,stroke:#FF4D4F,stroke-width:2px
|
||||
style S7 fill:#FAFAFA,stroke:#D9D9D9,stroke-width:2px
|
||||
style SUPPORT fill:#FAFAFA,stroke:#D9D9D9,stroke-width:1px
|
||||
style HOME fill:#4a7c59,color:#FFFFFF,stroke:#4a7c59
|
||||
style APP fill:#F6FFED,stroke:#52C41A,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
## 页面清单
|
||||
|
||||
| 模块 | 页面 | 路由 | 交互方式 | 组件文件 |
|
||||
|------|------|------|----------|----------|
|
||||
| 首页 | Dashboard | `/` | 卡片点击跳转 | `Dashboard.vue` |
|
||||
| 设备 | 设备列表 | `/devices` | 5项筛选 · 卡片式列表 | `DeviceList.vue` |
|
||||
| 设备 | 设备详情 | `/devices/:id` | 独立页面(6个信息卡片) | `DeviceDetail.vue` |
|
||||
| 设备 | 设备登记 | `/registration` | 弹窗(Excel导入/拍照上传) | `DeviceRegistration.vue` |
|
||||
| 设备 | 设备型号管理 | `/models` | 抽屉(新增型号/新增Checklist模板) | `DeviceModelManagement.vue` |
|
||||
| 设备 | 板卡型号管理 | `/boards` | Tab筛选 · 抽屉(板卡详情) | `BoardManagement.vue` |
|
||||
| 授权 | 授权管理 | `/licenses` | 抽屉(选择授权项 640px) | `LicenseManagement.vue` |
|
||||
| 固件 | 固件库 | `/firmware` | 弹窗(上传固件 Element Plus Dialog) | `FirmwareLibrary.vue` |
|
||||
| 配置 | 配置文件管理 | `/config-files` | 抽屉(新建配置 520px) | `ConfigFileManagement.vue` |
|
||||
| 配置 | 参数配置 | `/config-files/:configId` | 独立页面(新建/编辑两种模式) | `ParameterConfiguration.vue` |
|
||||
| 校准 | 校准管理 | `/calibration` | 表格列表 · 导入 | `CalibrationRecords.vue` |
|
||||
| 维修 | 维修工单 | `/repair` | 抽屉(新建520px/处理520px/详情540px) | `RepairOrders.vue` |
|
||||
| 维修 | 维修统计 | `/repair/stats` | 独立页面(统计+图表) | `RepairStats.vue` |
|
||||
| 维修 | 工单详情 | `/repair/:orderId` | 独立页面(含底部操作栏) | `RepairOrderDetail.vue` |
|
||||
| 报废 | 报废管理 | `/scrap` | 抽屉(详情520px/审批480px/回收480px) | `ScrapManagement.vue` |
|
||||
| 系统 | 数据报表 | `/reports` | 占位页面 | `PlaceholderPage.vue` |
|
||||
| 系统 | 操作日志 | `/logs` | 占位页面 | `PlaceholderPage.vue` |
|
||||
| 系统 | 系统设置 | `/settings` | 占位页面 | `PlaceholderPage.vue` |
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 MiB |
|
|
@ -0,0 +1,285 @@
|
|||
# 地空业务支撑平台 —— 生产管理子系统 流程与讨论文档
|
||||
|
||||
> 版本:v3.0
|
||||
> 更新日期:2026-04-27
|
||||
> 状态:会议评审稿
|
||||
|
||||
---
|
||||
|
||||
## 一、系统模块总览
|
||||
|
||||
| 模块 | 路由 | 核心功能 | 当前状态 |
|
||||
|------|------|----------|----------|
|
||||
| 首页 Dashboard | `/` | 全局数据概览、待处理任务快捷入口 | ✅ 已实现 |
|
||||
| 设备列表 | `/devices` | 设备筛选、卡片列表、设备详情 | ✅ 已实现 |
|
||||
| 设备详情 | `/devices/[sn]` | 基本信息、授权、装配记录、子设备、固件、维修历史 | ✅ 已实现 |
|
||||
| 设备型号管理 | `/models` | 型号CRUD、Checklist模板、BOM表管理 | ✅ 已实现 |
|
||||
| 设备登记 | `/registration` | 装机信息、BOM清单、装配Checklist | ✅ 已实现 |
|
||||
| 物料列表 | `/materials` | 物料实例管理、状态跟踪、校准信息 | ✅ 已实现 |
|
||||
| 物料管理 | `/materials/manage` | 物料类型定义、版本管理、固件关联 | ✅ 已实现 |
|
||||
| 物料登记 | `/materials/register` | 单个/批量登记物料、校准文件导入 | ✅ 已实现 |
|
||||
| 授权管理 | `/licenses` | 按型号管理授权模块配置 | ✅ 已实现 |
|
||||
| 配置文件管理 | `/config-files` | 按型号绑定配置文件(发射/采集/网络参数) | ✅ 已实现 |
|
||||
| 固件库 | `/firmware` | 固件版本管理、上传、下载 | ✅ 已实现 |
|
||||
| 维修工单 | `/repair` | 工单全生命周期(新建→处理→关闭) | ✅ 已实现 |
|
||||
| 报废回收 | `/scrap` | 报废审批流程、物料回收入库 | ✅ 已实现 |
|
||||
|
||||
---
|
||||
|
||||
## 二、核心业务流程
|
||||
|
||||
### 2.1 设备生产装配流程
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[设备型号管理] -->|定义BOM表| B[物料准备]
|
||||
B -->|物料登记入库| C[物料列表]
|
||||
C -->|选择物料| D[设备登记]
|
||||
A -->|Checklist模板| D
|
||||
D -->|装配完成| E[设备列表]
|
||||
E -->|待激活出厂| F[客户在线激活使用]
|
||||
```
|
||||
|
||||
### 2.2 物料全生命周期
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[物料管理\n定义物料类型/版本] --> B[物料登记\n录入SN/版本/校准文件]
|
||||
B --> C{物料状态}
|
||||
C -->|在库| D[等待装配]
|
||||
C -->|已装配| E[绑定设备]
|
||||
C -->|故障| F[维修工单]
|
||||
C -->|报废| G[报废回收]
|
||||
D --> E
|
||||
F -->|修复| D
|
||||
F -->|无法修复| G
|
||||
G -->|可回收物料| D
|
||||
```
|
||||
|
||||
### 2.3 物料分类体系
|
||||
|
||||
| 物料分类 | 物料名称示例 | 物料版本示例 | 有固件 | 需校准 |
|
||||
|----------|-------------|-------------|--------|--------|
|
||||
| 主协板 | GD30 主协板 | MB-GD-V1.0 | ✅ GD系列 | ❌ |
|
||||
| 采集板 | GD30 采集板 | RX-GD-V1.0 | ✅ GD系列 | ✅ |
|
||||
| 发射板 | GD30 发射板 | TX-GD-V1.0 | ✅ GD系列 | ❌ |
|
||||
| 升压板 | GD30 升压板 | B0-600-V1.0 | ❌ | ❌ |
|
||||
| 电缆头 | 通用电缆头 SR10/SR20 | SR10 | ❌ | ❌ |
|
||||
| 电缆 | 通用电缆 | CBL-60M | ❌ | ❌ |
|
||||
| 机箱 | 通用机箱 | GD30-CASE-B | ❌ | ❌ |
|
||||
| 电源 | BP150/BP300/BP600 电源 | BP150-V1.0 等 | ❌ | ❌ |
|
||||
|
||||
> 固件管理仅限 GD-10/20/30 Supreme 的主协板、采集板、发射板。
|
||||
|
||||
### 2.4 设备型号与BOM关系
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph GD30["GD-30 Supreme"]
|
||||
GD30_MCB[主协板 ×1]
|
||||
GD30_ACB[采集板 ×2\n版本须一致 · 需校准]
|
||||
GD30_TXB[发射板 ×1]
|
||||
GD30_BST[升压板 ×1]
|
||||
GD30_CBH[通用电缆头 ×7]
|
||||
GD30_CBL[通用电缆 ×6]
|
||||
GD30_CHS[通用机箱 ×1]
|
||||
GD30_BP[BP600 电源 ×1]
|
||||
end
|
||||
|
||||
subgraph GD20["GD-20 Supreme"]
|
||||
GD20_MCB[主协板 ×1]
|
||||
GD20_ACB[采集板 ×1\n需校准]
|
||||
GD20_TXB[发射板 ×1]
|
||||
GD20_BST[升压板 ×1]
|
||||
GD20_CBH[通用电缆头 ×7]
|
||||
GD20_CBL[通用电缆 ×6]
|
||||
GD20_CHS2[通用机箱 ×1]
|
||||
GD20_BP[BP600 电源 ×1]
|
||||
end
|
||||
|
||||
subgraph GD10["GD-10 Supreme"]
|
||||
GD10_MCB[主协板 ×1]
|
||||
GD10_ACB[采集板 ×1\n需校准]
|
||||
GD10_TXB[发射板 ×1]
|
||||
GD10_BST[升压板 ×1]
|
||||
GD10_CBH[通用电缆头 ×7]
|
||||
GD10_CBL[通用电缆 ×6]
|
||||
GD10_CHS3[通用机箱 ×1]
|
||||
GD10_BP[BP600 电源 ×1]
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
### 2.5 维修与报废流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[设备故障] --> B[新建维修工单]
|
||||
B --> C{故障诊断}
|
||||
C -->|更换板卡| D[板卡更换\n原板卡SN → 新板卡SN]
|
||||
C -->|固件修复| E[固件修复\n推送适配固件]
|
||||
C -->|参数重置| F[参数重置\n重新下发配置]
|
||||
C -->|无法修复| G[申请报废]
|
||||
|
||||
D --> H{是否需要重新授权}
|
||||
E --> H
|
||||
F --> H
|
||||
H -->|是| I[重新生成授权文件]
|
||||
H -->|否| J[工单关闭]
|
||||
I --> J
|
||||
|
||||
G --> K[报废审批流程]
|
||||
K --> L{审批结果}
|
||||
L -->|通过| M[物料检测]
|
||||
L -->|驳回| N[重新申请/返回维修]
|
||||
M --> O[可回收物料入库]
|
||||
O --> P[报废完成\n设备注销]
|
||||
```
|
||||
|
||||
### 2.6 授权文件生成与激活流程
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[设备型号管理] -->|绑定| B[授权模块配置]
|
||||
A -->|绑定| C[配置文件]
|
||||
B --> D[授权文件生成\n授权项 + 配置文件]
|
||||
C --> D
|
||||
|
||||
D --> E[APP激活流程]
|
||||
E --> E1[1. APP连接主机\n检索授权文件]
|
||||
E1 -->|无授权文件| E2[2. 外网连接平台\nSN号匹配下载]
|
||||
E2 --> E3[3. 重新连接设备\n传输授权文件激活]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、数据模型关系
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
DEVICE_MODELS ||--o{ BOM_TEMPLATES : "定义BOM"
|
||||
DEVICE_MODELS ||--o{ CHECKLIST_TEMPLATES : "定义Checklist"
|
||||
DEVICE_MODELS ||--o{ LICENSES : "绑定授权"
|
||||
DEVICE_MODELS ||--o{ CONFIG_FILES : "绑定配置"
|
||||
DEVICE_MODELS ||--o{ DEVICES : "生产设备"
|
||||
|
||||
BOARD_TYPES ||--o{ BOARD_VERSIONS : "定义版本"
|
||||
BOARD_TYPES ||--o{ MATERIALS : "生产物料"
|
||||
BOARD_VERSIONS ||--o{ FIRMWARE : "关联固件"
|
||||
|
||||
DEVICES ||--o{ MATERIALS : "装配物料"
|
||||
DEVICES ||--o{ REPAIR_ORDERS : "维修工单"
|
||||
DEVICES ||--o{ SCRAP_RECORDS : "报废记录"
|
||||
|
||||
REPAIR_ORDERS ||--o| SCRAP_RECORDS : "发起报废"
|
||||
|
||||
DEVICE_MODELS {
|
||||
string name "型号名称"
|
||||
string code "型号编码"
|
||||
string status "在产/停产"
|
||||
}
|
||||
BOARD_TYPES {
|
||||
string name "物料名称"
|
||||
string category "物料分类"
|
||||
string device_models "适配设备型号"
|
||||
}
|
||||
MATERIALS {
|
||||
string sn "物料SN"
|
||||
string name "物料名称"
|
||||
string type "物料类型"
|
||||
string status "在库/已装配/故障/报废"
|
||||
}
|
||||
DEVICES {
|
||||
string sn "设备SN"
|
||||
string model "设备型号"
|
||||
string status "装配中/已激活/已出厂"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、待讨论事项
|
||||
|
||||
### 4.1 维修与报废
|
||||
|
||||
| 序号 | 议题 | 说明 | 建议 |
|
||||
|------|------|------|------|
|
||||
| 8 | 维修工单状态流转 | 当前:待处理 → 处理中 → 已处理 | 是否需要增加"已验收"状态 |
|
||||
| 9 | 报废审批层级 | 当前:申请 → 主管审批 → 物料检测 → 回收入库 → 完成 | 确认审批权限和层级 |
|
||||
| 10 | 板卡更换后的授权 | 更换板卡后可选重新生成授权文件 | 确认哪些板卡更换需要强制重新授权 |
|
||||
|
||||
### 4.2 系统演进
|
||||
|
||||
| 序号 | 议题 | 说明 | 建议 |
|
||||
|------|------|------|------|
|
||||
| 11 | 数据报表 | 当前为占位页面 | 确认优先需要哪些报表 |
|
||||
| 12 | 用户权限 | 当前无权限控制 | 确认角色划分(管理员/装配员/维修员/审批员) |
|
||||
| 13 | 操作日志 | 当前为占位页面 | 确认需要记录哪些关键操作 |
|
||||
|
||||
---
|
||||
|
||||
## 五、系统全局流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
HOME["🏠 首页 Dashboard\n设备统计 · 待处理任务"]
|
||||
|
||||
subgraph S0["⚙️ 型号管理(核心枢纽)"]
|
||||
MODEL["设备型号管理\n型号CRUD · 在产/停产"]
|
||||
MODEL -->|定义| BOM["BOM表管理\n物料名称 · 物料分类 · 物料类型\n兼容版本 · 数量 · 版本约束"]
|
||||
MODEL -->|定义| CKL["Checklist模板\nTab切换型号 · 可编辑"]
|
||||
end
|
||||
|
||||
subgraph S1["📦 物料管理"]
|
||||
MAT_MGR["物料管理\n物料类型定义 · 版本管理"]
|
||||
MAT_MGR -->|版本抽屉| VER["版本管理抽屉\n状态切换 · 固件管理"]
|
||||
MAT_REG["物料登记\n单个/批量 · 校准文件"]
|
||||
MAT_LIST["物料列表\n物料名称 · 物料类型 · 适配型号\n版本 · 描述 · 状态"]
|
||||
MAT_REG --> MAT_LIST
|
||||
end
|
||||
|
||||
subgraph S2["🖥️ 设备管理"]
|
||||
DEV_REG["设备登记\n装机信息 · BOM清单 · Checklist"]
|
||||
DEV_LIST["设备列表\n筛选 · 卡片列表"]
|
||||
DEV_DETAIL["设备详情\n基本信息 · 授权 · 装配\n子设备 · 固件 · 维修历史"]
|
||||
DEV_REG --> DEV_LIST
|
||||
DEV_LIST --> DEV_DETAIL
|
||||
end
|
||||
|
||||
subgraph S3["🔑 授权与配置"]
|
||||
LIC["授权管理\n按型号配置授权模块"]
|
||||
CFG["配置文件管理\n发射/采集/网络参数"]
|
||||
FW["固件库\n主协板/采集板/发射板固件"]
|
||||
end
|
||||
|
||||
subgraph S4["🔧 维修运维"]
|
||||
REPAIR["维修工单\n新建 · 处理 · 详情"]
|
||||
SCRAP["报废回收\n审批流程 · 物料回收"]
|
||||
REPAIR -->|无法修复| SCRAP
|
||||
SCRAP -->|回收物料| MAT_LIST
|
||||
end
|
||||
|
||||
HOME --> MODEL
|
||||
HOME --> DEV_LIST
|
||||
HOME --> REPAIR
|
||||
HOME --> SCRAP
|
||||
|
||||
MODEL -->|授权| LIC
|
||||
MODEL -->|配置| CFG
|
||||
MODEL -->|固件| FW
|
||||
MODEL -->|BOM定义物料需求| MAT_MGR
|
||||
|
||||
MAT_LIST -->|装配使用| DEV_REG
|
||||
BOM -->|装配依据| DEV_REG
|
||||
CKL -->|装配检查| DEV_REG
|
||||
|
||||
DEV_DETAIL -->|发起维修| REPAIR
|
||||
VER -->|固件管理| FW
|
||||
|
||||
style S0 fill:#F0F2F5,stroke:#8C8C8C,stroke-width:2px
|
||||
style S1 fill:#F0F5FF,stroke:#597EF7,stroke-width:2px
|
||||
style S2 fill:#eef5f0,stroke:#4a7c59,stroke-width:2px
|
||||
style S3 fill:#E6FFFB,stroke:#13C2C2,stroke-width:2px
|
||||
style S4 fill:#FFF1F0,stroke:#FF4D4F,stroke-width:2px
|
||||
style HOME fill:#4a7c59,color:#FFFFFF,stroke:#4a7c59
|
||||
```
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {}
|
||||
const nextConfig: NextConfig = {
|
||||
serverExternalPackages: ['better-sqlite3'],
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@tanstack/react-query": "^5.96.2",
|
||||
"better-sqlite3": "^12.9.0",
|
||||
"lucide-react": "^1.7.0",
|
||||
"next": "16.2",
|
||||
"react": "19.2",
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.2.2",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^25.5.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
|
|
@ -1249,6 +1251,16 @@
|
|||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/better-sqlite3": {
|
||||
"version": "7.6.13",
|
||||
"resolved": "https://registry.npmmirror.com/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
||||
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.5.2",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.5.2.tgz",
|
||||
|
|
@ -1279,6 +1291,26 @@
|
|||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.16",
|
||||
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.16.tgz",
|
||||
|
|
@ -1291,6 +1323,64 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "12.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/better-sqlite3/-/better-sqlite3-12.9.0.tgz",
|
||||
"integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001786",
|
||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001786.tgz",
|
||||
|
|
@ -1311,6 +1401,12 @@
|
|||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz",
|
||||
|
|
@ -1324,16 +1420,48 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.20.1",
|
||||
"resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
|
||||
|
|
@ -1348,6 +1476,33 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
|
|
@ -1355,6 +1510,38 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
|
||||
|
|
@ -1426,6 +1613,33 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
|
||||
|
|
@ -1444,6 +1658,12 @@
|
|||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "16.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/next/-/next-16.2.3.tgz",
|
||||
|
|
@ -1525,6 +1745,27 @@
|
|||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.89.0",
|
||||
"resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.89.0.tgz",
|
||||
"integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||
|
|
@ -1560,6 +1801,57 @@
|
|||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.2.tgz",
|
||||
"integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz",
|
||||
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/react/-/react-19.2.4.tgz",
|
||||
|
|
@ -1597,6 +1889,40 @@
|
|||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz",
|
||||
|
|
@ -1608,7 +1934,6 @@
|
|||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
|
@ -1661,6 +1986,51 @@
|
|||
"@img/sharp-win32-x64": "0.34.5"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
|
|
@ -1670,6 +2040,24 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-jsx": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmmirror.com/styled-jsx/-/styled-jsx-5.1.6.tgz",
|
||||
|
|
@ -1714,12 +2102,52 @@
|
|||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.2.tgz",
|
||||
|
|
@ -1741,6 +2169,18 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@tanstack/react-query": "^5.96.2",
|
||||
"better-sqlite3": "^12.9.0",
|
||||
"lucide-react": "^1.7.0",
|
||||
"next": "16.2",
|
||||
"react": "19.2",
|
||||
|
|
@ -20,6 +21,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.2.2",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^25.5.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const items = db.prepare('SELECT * FROM board_types ORDER BY id').all() as { device_models: string; [k: string]: unknown }[]
|
||||
const parsed = items.map(i => ({ ...i, device_models: undefined, deviceModels: JSON.parse(i.device_models as string) }))
|
||||
return NextResponse.json(parsed)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { name, category, device_models, deviceModels, description, status } = body
|
||||
const models = device_models || deviceModels || []
|
||||
const result = db.prepare('INSERT INTO board_types (name, category, device_models, description, status) VALUES (?, ?, ?, ?, ?)').run(name, category, JSON.stringify(models), description || '', status || '启用')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
||||
export async function PUT(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { id, name, category, device_models, deviceModels, description, status } = body
|
||||
const models = device_models || deviceModels || []
|
||||
db.prepare('UPDATE board_types SET name=?, category=?, device_models=?, description=?, status=? WHERE id=?').run(name, category, JSON.stringify(models), description || '', status || '启用', id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
||||
export async function DELETE(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const { id } = await req.json()
|
||||
db.prepare('DELETE FROM board_types WHERE id = ?').run(id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const boards = db.prepare('SELECT * FROM board_versions ORDER BY id').all()
|
||||
return NextResponse.json(boards)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { type, version, status } = body
|
||||
const result = db.prepare('INSERT INTO board_versions (type, version, status) VALUES (?, ?, ?)').run(type, version, status || '在产')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
||||
export async function PUT(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { id, status } = body
|
||||
db.prepare('UPDATE board_versions SET status = ? WHERE id = ?').run(status, id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const items = db.prepare('SELECT * FROM config_files ORDER BY id DESC').all()
|
||||
return NextResponse.json(items)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { name, model, version, status, voltage, current, duty_cycle, pulse_width, channels, sample_rate, voltage_range, waveform, ssid } = body
|
||||
const now = new Date().toISOString().replace('T', ' ').substring(0, 16)
|
||||
const result = db.prepare('INSERT INTO config_files (name, model, version, create_time, status, voltage, current, duty_cycle, pulse_width, channels, sample_rate, voltage_range, waveform, ssid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(name, model, version, now, status || '生效', voltage || '', current || '', duty_cycle || '', pulse_width || '', channels || 0, sample_rate || '', voltage_range || '', waveform || '', ssid || '')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const devices = db.prepare('SELECT * FROM devices ORDER BY id DESC').all()
|
||||
return NextResponse.json(devices)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { sn, model, type, status, firmware, production_date, customer, batch } = body
|
||||
const result = db.prepare('INSERT INTO devices (sn, model, type, status, firmware, production_date, customer, batch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(sn, model, type, status || '装配中', firmware || '', production_date, customer || '-', batch || '')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const board = req.nextUrl.searchParams.get('board') || ''
|
||||
const model = req.nextUrl.searchParams.get('model') || ''
|
||||
let items
|
||||
if (board) {
|
||||
items = db.prepare('SELECT * FROM firmware WHERE board_version = ? ORDER BY date DESC').all(board)
|
||||
} else if (model) {
|
||||
items = db.prepare('SELECT * FROM firmware WHERE model_code = ? ORDER BY date DESC').all(model)
|
||||
} else {
|
||||
items = db.prepare('SELECT * FROM firmware ORDER BY date DESC').all()
|
||||
}
|
||||
const parsed = (items as { notes: string; [k: string]: unknown }[]).map(i => ({ ...i, notes: JSON.parse(i.notes as string) }))
|
||||
return NextResponse.json(parsed)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { version, board_version, type, date, status, size, hw_range, upgrade_type, signed, notes, model_code } = body
|
||||
const result = db.prepare('INSERT INTO firmware (version, board_version, type, date, status, size, downloads, hw_range, upgrade_type, signed, md5, sha256, notes, model_code) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?, "", "", ?, ?)').run(version, board_version || '-', type, date || new Date().toISOString().split('T')[0], status || '草稿', size || '', hw_range || '', upgrade_type || '可选', signed ? 1 : 0, JSON.stringify(notes || []), model_code || '')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const items = db.prepare('SELECT * FROM licenses ORDER BY id').all()
|
||||
return NextResponse.json(items)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { model, modules, expiry, status } = body
|
||||
const result = db.prepare('INSERT INTO licenses (model, modules, expiry, status) VALUES (?, ?, ?, ?)').run(model, modules, expiry || '', status || '生效')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const items = db.prepare('SELECT * FROM board_types ORDER BY id').all() as { device_models: string; [k: string]: unknown }[]
|
||||
const parsed = items.map(i => ({ ...i, device_models: undefined, deviceModels: JSON.parse(i.device_models as string) }))
|
||||
return NextResponse.json(parsed)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { name, category, device_models, deviceModels, description, status } = body
|
||||
const models = device_models || deviceModels || []
|
||||
const result = db.prepare('INSERT INTO board_types (name, category, device_models, description, status) VALUES (?, ?, ?, ?, ?)').run(name, category, JSON.stringify(models), description || '', status || '启用')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
||||
export async function PUT(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { id, name, category, device_models, deviceModels, description, status } = body
|
||||
const models = device_models || deviceModels || []
|
||||
db.prepare('UPDATE board_types SET name=?, category=?, device_models=?, description=?, status=? WHERE id=?').run(name, category, JSON.stringify(models), description || '', status || '启用', id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
||||
export async function DELETE(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const { id } = await req.json()
|
||||
db.prepare('DELETE FROM board_types WHERE id = ?').run(id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const items = db.prepare('SELECT * FROM board_versions ORDER BY id').all()
|
||||
return NextResponse.json(items)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { type, version, status } = body
|
||||
const result = db.prepare('INSERT INTO board_versions (type, version, status) VALUES (?, ?, ?)').run(type, version, status || '在产')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
||||
export async function PUT(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { id, status } = body
|
||||
db.prepare('UPDATE board_versions SET status = ? WHERE id = ?').run(status, id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const materials = db.prepare('SELECT * FROM materials ORDER BY id DESC').all()
|
||||
return NextResponse.json(materials)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { sn, name, category, type, device_model, version, description, firmware, status, device_sn, production_date, calib_status, calib_date } = body
|
||||
const result = db.prepare('INSERT INTO materials (sn, name, category, type, device_model, version, description, firmware, status, device_sn, production_date, calib_status, calib_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)').run(sn, name || '', category || '', type, device_model || '', version, description || '', firmware || '-', status || '在库', device_sn || '-', production_date, calib_status || '-', calib_date || '-')
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const modelCode = req.nextUrl.searchParams.get('model') || 'GD30'
|
||||
const items = db.prepare('SELECT * FROM bom_templates WHERE model_code = ? ORDER BY id').all(modelCode) as { versions: string; [k: string]: unknown }[]
|
||||
const parsed = items.map(i => ({ ...i, versions: JSON.parse(i.versions as string) }))
|
||||
return NextResponse.json(parsed)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { model_code, name, material_name, model, versions, qty, required, need_calibration, enforce_version_match } = body
|
||||
const result = db.prepare('INSERT INTO bom_templates (model_code, name, material_name, model, versions, qty, required, need_calibration, enforce_version_match) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)').run(model_code, name, material_name || '', model, JSON.stringify(versions || []), qty || 1, required ? 1 : 0, need_calibration ? 1 : 0, enforce_version_match ? 1 : 0)
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
||||
export async function DELETE(req: NextRequest) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const id = req.nextUrl.searchParams.get('id')
|
||||
if (id) db.prepare('DELETE FROM bom_templates WHERE id = ?').run(id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const modelCode = req.nextUrl.searchParams.get('model') || ''
|
||||
if (modelCode) {
|
||||
const items = db.prepare('SELECT * FROM checklist_templates WHERE model_code = ? ORDER BY sort_order').all(modelCode)
|
||||
return NextResponse.json(items)
|
||||
}
|
||||
// Return grouped by model_code
|
||||
const all = db.prepare('SELECT * FROM checklist_templates ORDER BY model_code, sort_order').all() as { model_code: string; id: number; name: string; required: number; sort_order: number }[]
|
||||
const grouped: Record<string, typeof all> = {}
|
||||
for (const item of all) {
|
||||
if (!grouped[item.model_code]) grouped[item.model_code] = []
|
||||
grouped[item.model_code].push(item)
|
||||
}
|
||||
return NextResponse.json(grouped)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const models = db.prepare('SELECT * FROM device_models ORDER BY id').all()
|
||||
return NextResponse.json(models)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { name, code, status, description } = body
|
||||
const date = new Date().toISOString().split('T')[0]
|
||||
const result = db.prepare('INSERT INTO device_models (name, code, status, description, create_date) VALUES (?, ?, ?, ?, ?)').run(name, code, status || '在产', description || '', date)
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
||||
export async function PUT(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { id, status } = body
|
||||
db.prepare('UPDATE device_models SET status = ? WHERE id = ?').run(status, id)
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const items = db.prepare('SELECT * FROM repair_orders ORDER BY create_date DESC').all()
|
||||
return NextResponse.json(items)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { id, sn, fault_type, status, priority, assignee, create_date, description } = body
|
||||
db.prepare('INSERT INTO repair_orders (id, sn, fault_type, status, priority, assignee, create_date, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(id, sn, fault_type, status || '待处理', priority || '中', assignee || '', create_date, description || '')
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { seedIfEmpty } from '@/lib/seed'
|
||||
|
||||
export async function GET() {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const items = db.prepare('SELECT * FROM scrap_records ORDER BY date DESC').all() as { materials: string; [k: string]: unknown }[]
|
||||
const parsed = items.map(i => ({ ...i, materials: JSON.parse(i.materials as string) }))
|
||||
return NextResponse.json(parsed)
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
seedIfEmpty()
|
||||
const db = getDb()
|
||||
const body = await req.json()
|
||||
const { sn, model, reason, applicant, status, order_id, date, value, materials } = body
|
||||
const result = db.prepare('INSERT INTO scrap_records (sn, model, reason, applicant, status, order_id, date, value, materials) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)').run(sn, model, reason, applicant || '', status || '待审批', order_id || '', date, value || 0, JSON.stringify(materials || []))
|
||||
return NextResponse.json({ id: result.lastInsertRowid })
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Download, Plus, X, Eye, Upload, Wrench, CheckCircle, RefreshCw } from 'lucide-react'
|
||||
|
||||
const tabs = ['全部', '主协板', '采集板', '发射板', '升压板']
|
||||
|
||||
const initialBoardsData = [
|
||||
{ id: 1, type: '主协板', version: 'MB-V1.8', status: '在产' },
|
||||
{ id: 3, type: '采集板', version: 'RX-V2.3', status: '在产' },
|
||||
{ id: 6, type: '发射板', version: 'TX-V2.1', status: '停产' },
|
||||
{ id: 8, type: '升压板', version: 'BP600-V1.2', status: '停产' },
|
||||
]
|
||||
|
||||
|
||||
function getStatusStyle(status: string) {
|
||||
switch (status) {
|
||||
case '在产': return { backgroundColor: '#eef5f0', color: '#4a7c59', border: '1px solid #a3c4ad' }
|
||||
case '停产': return { backgroundColor: '#FFFBE6', color: '#FAAD14', border: '1px solid #FFE58F' }
|
||||
default: return { backgroundColor: '#FAFAFA', color: 'rgba(0,0,0,0.45)', border: '1px solid #D9D9D9' }
|
||||
}
|
||||
}
|
||||
|
||||
export default function BoardsPage() {
|
||||
const router = useRouter()
|
||||
const [activeTab, setActiveTab] = useState('全部')
|
||||
const [boardsData, setBoardsData] = useState(initialBoardsData)
|
||||
const [detailDrawer, setDetailDrawer] = useState<typeof initialBoardsData[0] | null>(null)
|
||||
const [addDrawer, setAddDrawer] = useState(false)
|
||||
const [detailTab, setDetailTab] = useState('basic')
|
||||
const [formData, setFormData] = useState({ type: '主协板', version: '', firmwareVersion: '', productionDate: '', status: '在产' })
|
||||
|
||||
const filteredBoards = activeTab === '全部' ? boardsData : boardsData.filter(b => b.type === activeTab)
|
||||
|
||||
const handleToggleStatus = (id: number) => {
|
||||
setBoardsData(prev => prev.map(b =>
|
||||
b.id === id ? { ...b, status: b.status === '在产' ? '停产' : '在产' } : b
|
||||
))
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
|
||||
<div>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, margin: 0 }}>板卡版本管理</h2>
|
||||
<p style={{ fontSize: 14, color: 'rgba(0,0,0,0.45)', margin: '4px 0 0' }}>管理所有板卡版本及固件版本</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button onClick={() => {}} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>
|
||||
<Download size={16} />导出
|
||||
</button>
|
||||
<button onClick={() => setAddDrawer(true)} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}>
|
||||
<Plus size={16} />添加板卡
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
<div style={{ display: 'flex', gap: 0, borderBottom: '1px solid #F0F0F0', marginBottom: 24 }}>
|
||||
{tabs.map(tab => (
|
||||
<button key={tab} onClick={() => setActiveTab(tab)} style={{
|
||||
padding: '10px 20px', fontSize: 14, cursor: 'pointer', border: 'none', backgroundColor: 'transparent',
|
||||
borderBottom: activeTab === tab ? '2px solid #4a7c59' : '2px solid transparent',
|
||||
color: activeTab === tab ? '#4a7c59' : 'rgba(0,0,0,0.65)', fontWeight: activeTab === tab ? 600 : 400,
|
||||
}}>{tab}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div style={{ backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{['板卡类型', '版本', '状态', '操作'].map(h => (
|
||||
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: 14, fontWeight: 600, color: 'rgba(0,0,0,0.85)', borderBottom: '1px solid #F0F0F0' }}>{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredBoards.map(board => (
|
||||
<tr key={board.id} style={{ borderBottom: '1px solid #F0F0F0' }}>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14 }}>{board.type}</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, fontWeight: 500 }}>{board.version}</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<span style={{ ...getStatusStyle(board.status), padding: '2px 8px', borderRadius: 4, fontSize: 12 }}>{board.status}</span>
|
||||
</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button onClick={() => { setDetailDrawer(board); setDetailTab('basic') }} style={{ color: '#4a7c59', cursor: 'pointer', border: 'none', background: 'none', fontSize: 14, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<Eye size={14} />详情
|
||||
</button>
|
||||
<button onClick={() => router.push(`/firmware?board=${board.version}`)} style={{ color: '#4a7c59', cursor: 'pointer', border: 'none', background: 'none', fontSize: 14, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<Upload size={14} />固件
|
||||
</button>
|
||||
<button onClick={() => handleToggleStatus(board.id)} style={{ color: board.status === '在产' ? '#FAAD14' : '#4a7c59', cursor: 'pointer', border: 'none', background: 'none', fontSize: 14, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<RefreshCw size={14} />{board.status === '在产' ? '停产' : '恢复在产'}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Detail Drawer */}
|
||||
{detailDrawer && (
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 50 }}>
|
||||
<div onClick={() => setDetailDrawer(null)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} />
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: 560, backgroundColor: '#fff', boxShadow: '-2px 0 8px rgba(0,0,0,0.15)', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #F0F0F0' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>板卡详情 - {detailDrawer.version}</h3>
|
||||
<button onClick={() => setDetailDrawer(null)} style={{ border: 'none', background: 'none', cursor: 'pointer', padding: 4 }}><X size={20} /></button>
|
||||
</div>
|
||||
{/* Detail Tabs */}
|
||||
<div style={{ display: 'flex', borderBottom: '1px solid #F0F0F0' }}>
|
||||
{[
|
||||
{ key: 'basic', label: '基本信息' },
|
||||
].map(t => (
|
||||
<button key={t.key} onClick={() => setDetailTab(t.key)} style={{
|
||||
padding: '10px 16px', fontSize: 13, cursor: 'pointer', border: 'none', backgroundColor: 'transparent',
|
||||
borderBottom: detailTab === t.key ? '2px solid #4a7c59' : '2px solid transparent',
|
||||
color: detailTab === t.key ? '#4a7c59' : 'rgba(0,0,0,0.65)', fontWeight: detailTab === t.key ? 600 : 400,
|
||||
}}>{t.label}</button>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
{detailTab === 'basic' && (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
|
||||
{[
|
||||
{ label: '板卡类型', value: detailDrawer.type },
|
||||
{ label: '版本', value: detailDrawer.version },
|
||||
{ label: '状态', value: detailDrawer.status },
|
||||
].map(item => (
|
||||
<div key={item.label}>
|
||||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)', marginBottom: 4 }}>{item.label}</div>
|
||||
<div style={{ fontSize: 14, color: 'rgba(0,0,0,0.85)' }}>
|
||||
{item.label === '状态' ? (
|
||||
<span style={{ ...getStatusStyle(item.value), padding: '2px 8px', borderRadius: 4, fontSize: 12 }}>{item.value}</span>
|
||||
) : item.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Board Drawer */}
|
||||
{addDrawer && (
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 50 }}>
|
||||
<div onClick={() => setAddDrawer(false)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} />
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: 480, backgroundColor: '#fff', boxShadow: '-2px 0 8px rgba(0,0,0,0.15)', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #F0F0F0' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>添加板卡</h3>
|
||||
<button onClick={() => setAddDrawer(false)} style={{ border: 'none', background: 'none', cursor: 'pointer', padding: 4 }}><X size={20} /></button>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}>板卡类型</label>
|
||||
<select value={formData.type} onChange={e => setFormData({ ...formData, type: e.target.value })} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, outline: 'none' }}>
|
||||
{['主协板', '采集板', '发射板', '升压板'].map(t => <option key={t} value={t}>{t}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}>版本</label>
|
||||
<input value={formData.version} onChange={e => setFormData({ ...formData, version: e.target.value })} placeholder="请输入版本" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, outline: 'none', boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}>状态</label>
|
||||
<select value={formData.status} onChange={e => setFormData({ ...formData, status: e.target.value })} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, outline: 'none' }}>
|
||||
{['在产', '停产'].map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '16px 24px', borderTop: '1px solid #F0F0F0', display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||
<button onClick={() => setAddDrawer(false)} style={{ padding: '8px 20px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>取消</button>
|
||||
<button onClick={() => setAddDrawer(false)} style={{ padding: '8px 20px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}>确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
'use client'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Monitor, Settings2, Cpu, Gauge, Wrench, Recycle } from 'lucide-react'
|
||||
import { Monitor, Settings2, Gauge, Wrench, Recycle, Layers } from 'lucide-react'
|
||||
|
||||
const menuGroups = [
|
||||
{ title: '设备', items: [
|
||||
{ path: '/devices', label: '设备列表', icon: Monitor },
|
||||
{ path: '/models', label: '设备型号管理', icon: Settings2 },
|
||||
]},
|
||||
{ title: '板卡', items: [{ path: '/board-cards', label: '板卡列表', icon: Gauge },
|
||||
{ path: '/boards', label: '板卡版本管理', icon: Cpu },] },
|
||||
{ title: '物料', items: [
|
||||
{ path: '/materials', label: '物料列表', icon: Gauge },
|
||||
{ path: '/materials/manage', label: '物料管理', icon: Layers },
|
||||
] },
|
||||
{ title: '维修', items: [
|
||||
{ path: '/repair', label: '维修工单', icon: Wrench },
|
||||
{ path: '/scrap', label: '报废回收', icon: Recycle },
|
||||
|
|
@ -19,7 +21,7 @@ const menuGroups = [
|
|||
export function Sidebar() {
|
||||
const pathname = usePathname()
|
||||
const isActive = (path: string) =>
|
||||
path === '/' ? pathname === '/' : pathname === path || pathname.startsWith(path + '/')
|
||||
path === '/' ? pathname === '/' : pathname === path || (pathname.startsWith(path + '/') && path !== '/materials')
|
||||
|
||||
return (
|
||||
<aside className="w-[200px] h-screen flex-shrink-0 overflow-y-auto" style={{ backgroundColor: '#fff', borderRight: '1px solid #e8e8e8' }}>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import { useRouter, useSearchParams } from 'next/navigation'
|
|||
import { Plus, Search, Info, ChevronLeft, ChevronRight, X, Download, Trash2, Eye, Edit, ArrowLeft } from 'lucide-react'
|
||||
|
||||
const configData = [
|
||||
{ id: 1, name: 'CFG-GD30-v1.2.0', model: 'GD-30 Supreme', version: 'v1.2.0', createTime: '2025-01-15 10:30', status: '生效', voltage: '1500V', current: '10A', dutyCycle: '50%、100%', pulseWidth: '0.25s~64s', iterations: '1~256', channels: 12, sampleRate: '50Hz/60Hz/100Hz/1000Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD30' },
|
||||
{ id: 2, name: 'CFG-GD30-v1.1.0', model: 'GD-30 Supreme', version: 'v1.1.0', createTime: '2024-09-20 14:00', status: '已停用', voltage: '1200V', current: '10A', dutyCycle: '50%', pulseWidth: '0.25s~32s', iterations: '1~128', channels: 12, sampleRate: '50Hz/60Hz/100Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD30' },
|
||||
{ id: 3, name: 'CFG-GD20-v1.0.0', model: 'GD-20 Supreme', version: 'v1.0.0', createTime: '2024-08-10 09:15', status: '生效', voltage: '1000V', current: '8A', dutyCycle: '50%', pulseWidth: '0.25s~8s', iterations: '1~128', channels: 6, sampleRate: '50Hz/60Hz', voltageRange: '±2.5V/±80V', waveform: '不支持', ssid: 'GD20' },
|
||||
{ id: 4, name: 'CFG-GD10-v1.0.0', model: 'GD-10 Supreme', version: 'v1.0.0', createTime: '2024-06-05 16:45', status: '生效', voltage: '800V', current: '5A', dutyCycle: '50%', pulseWidth: '0.5s~8s', iterations: '1~64', channels: 1, sampleRate: '50Hz/60Hz', voltageRange: '±2.5V', waveform: '不支持', ssid: 'GD10' },
|
||||
{ id: 5, name: 'CFG-GD20-v1.1.0', model: 'GD-20 Supreme', version: 'v1.1.0', createTime: '2025-02-28 11:20', status: '生效', voltage: '1000V', current: '8A', dutyCycle: '50%、100%', pulseWidth: '0.25s~16s', iterations: '1~128', channels: 6, sampleRate: '50Hz/60Hz/100Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD20' },
|
||||
{ id: 6, name: 'CFG-GD30-v1.3.0', model: 'GD-30 Supreme', version: 'v1.3.0', createTime: '2025-03-15 08:00', status: '生效', voltage: '1500V', current: '10A', dutyCycle: '50%、100%', pulseWidth: '0.25s~64s', iterations: '1~256', channels: 12, sampleRate: '50Hz/60Hz/100Hz/1000Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD30' },
|
||||
{ id: 1, name: 'CFG-GD30-v1.2.0', model: 'GD-30 Supreme', version: 'v1.2.0', createTime: '2025-01-15 10:30', status: '生效', voltage: '1500V', current: '10A', dutyCycle: '0+0-、+0-0、+-', pulseWidth: '0.25s~64s', channels: 12, sampleRate: '50Hz/60Hz/100Hz/1000Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD30' },
|
||||
{ id: 2, name: 'CFG-GD30-v1.1.0', model: 'GD-30 Supreme', version: 'v1.1.0', createTime: '2024-09-20 14:00', status: '已停用', voltage: '1200V', current: '10A', dutyCycle: '0+0-、+0-0', pulseWidth: '0.25s~32s', channels: 12, sampleRate: '50Hz/60Hz/100Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD30' },
|
||||
{ id: 3, name: 'CFG-GD20-v1.0.0', model: 'GD-20 Supreme', version: 'v1.0.0', createTime: '2024-08-10 09:15', status: '生效', voltage: '1000V', current: '8A', dutyCycle: '0+0-、+0-0', pulseWidth: '0.25s~8s', channels: 6, sampleRate: '50Hz/60Hz', voltageRange: '±2.5V/±80V', waveform: '不支持', ssid: 'GD20' },
|
||||
{ id: 4, name: 'CFG-GD10-v1.0.0', model: 'GD-10 Supreme', version: 'v1.0.0', createTime: '2024-06-05 16:45', status: '生效', voltage: '800V', current: '5A', dutyCycle: '0+0-', pulseWidth: '0.5s~8s', channels: 1, sampleRate: '50Hz/60Hz', voltageRange: '±2.5V', waveform: '不支持', ssid: 'GD10' },
|
||||
{ id: 5, name: 'CFG-GD20-v1.1.0', model: 'GD-20 Supreme', version: 'v1.1.0', createTime: '2025-02-28 11:20', status: '生效', voltage: '1000V', current: '8A', dutyCycle: '0+0-、+0-0、+-', pulseWidth: '0.25s~16s', channels: 6, sampleRate: '50Hz/60Hz/100Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD20' },
|
||||
{ id: 6, name: 'CFG-GD30-v1.3.0', model: 'GD-30 Supreme', version: 'v1.3.0', createTime: '2025-03-15 08:00', status: '生效', voltage: '1500V', current: '10A', dutyCycle: '0+0-、+0-0、+-', pulseWidth: '0.25s~64s', channels: 12, sampleRate: '50Hz/60Hz/100Hz/1000Hz', voltageRange: '±2.5V/±80V', waveform: '支持', ssid: 'GD30' },
|
||||
]
|
||||
|
||||
const modelOptions = ['全部', 'GD-30 Supreme', 'GD-20 Supreme', 'GD-10 Supreme']
|
||||
|
|
@ -53,7 +53,7 @@ function ConfigFilesContent() {
|
|||
const [detailDrawer, setDetailDrawer] = useState<typeof configData[0] | null>(null)
|
||||
const [form, setForm] = useState({
|
||||
model: 'GD-30 Supreme', version: '', voltage: '1500V', current: '10A',
|
||||
dutyCycle: '50%', pulseWidth: '0.25s/0.5s/1s/2s/4s/8s/16s/32s/64s', iterations: '1~256',
|
||||
dutyCycle: ['0+0-'] as string[], pulseWidth: '0.25s/0.5s/1s/2s/4s/8s/16s/32s/64s',
|
||||
channels: '12', sampleRate: '50Hz、60Hz/50Hz、60Hz、100Hz、1000Hz', voltageRange: '±2.5V、±80V/±80V、±600V',
|
||||
waveform: '支持', ssid: '',
|
||||
})
|
||||
|
|
@ -192,9 +192,8 @@ function ConfigFilesContent() {
|
|||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, fontSize: 13 }}>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>最大发射电压:</span>{detailDrawer.voltage}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>最大发射电流:</span>{detailDrawer.current}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>占空比:</span>{detailDrawer.dutyCycle}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>发射波形:</span>{detailDrawer.dutyCycle}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>脉宽范围:</span>{detailDrawer.pulseWidth}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>迭代次数:</span>{detailDrawer.iterations}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 采集参数 */}
|
||||
|
|
@ -266,11 +265,15 @@ function ConfigFilesContent() {
|
|||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>占空比</label>
|
||||
<select value={form.dutyCycle} onChange={e => setForm({ ...form, dutyCycle: e.target.value })} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
<option value="50%">50%</option>
|
||||
<option value="50%、100%">50%、100%</option>
|
||||
</select>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>发射波形</label>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{['0+0-', '+0-0', '+-'].map(w => (
|
||||
<label key={w} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 14px', borderRadius: 6, fontSize: 13, cursor: 'pointer', border: form.dutyCycle.includes(w) ? '1px solid #4a7c59' : '1px solid #D9D9D9', backgroundColor: form.dutyCycle.includes(w) ? '#eef5f0' : '#fff', color: form.dutyCycle.includes(w) ? '#4a7c59' : 'rgba(0,0,0,0.65)' }}>
|
||||
<input type="checkbox" checked={form.dutyCycle.includes(w)} onChange={e => { setForm({ ...form, dutyCycle: e.target.checked ? [...form.dutyCycle, w] : form.dutyCycle.filter(x => x !== w) }) }} style={{ display: 'none' }} />
|
||||
{w}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>脉宽宽度</label>
|
||||
|
|
@ -278,10 +281,6 @@ function ConfigFilesContent() {
|
|||
{['0.25s/0.5s/1s/2s/4s/8s', '0.25s/0.5s/1s/2s/4s/8s/16s/32s/54s'].map(v => <option key={v} value={v}>{v}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>迭代次数</label>
|
||||
<input type="number" value={form.iterations} onChange={e => setForm({ ...form, iterations: e.target.value })} min={1} max={256} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 采集参数 */}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ interface ConfigDetail {
|
|||
version: string
|
||||
uploadDate: string
|
||||
params: {
|
||||
transmission: { maxVoltage: string; maxCurrent: string; dutyCycle: string; pulseWidths: string[]; waveforms: string[]; fullWaveform: boolean }
|
||||
acquisition: { channels: number; sampleRates: string[]; voltageRanges: string[]; iterationRange: string }
|
||||
transmission: { maxVoltage: string; maxCurrent: string; waveformPattern: string; pulseWidths: string[]; waveforms: string[]; fullWaveform: boolean }
|
||||
acquisition: { channels: number; sampleRates: string[]; voltageRanges: string[] }
|
||||
protection: { overVoltage: { enabled: boolean; threshold: string }; overCurrent: { enabled: boolean; threshold: string }; shortCircuit: boolean; overTemp: { enabled: boolean; threshold: string } }
|
||||
network: { wifiSsidPrefix: string }
|
||||
}
|
||||
|
|
@ -54,8 +54,8 @@ const configData: Record<string, ConfigDetail> = {
|
|||
'GD30-2025-000001': {
|
||||
name: 'CFG-GD30-v1.3.0', version: 'v1.3.0', uploadDate: '2025-01-15',
|
||||
params: {
|
||||
transmission: { maxVoltage: '1500V', maxCurrent: '10A', dutyCycle: '50%、100%', pulseWidths: ['0.25s', '0.5s', '1s', '2s', '4s', '8s', '16s', '32s', '64s'], waveforms: ['0+0-', '+0-0', '+-'], fullWaveform: true },
|
||||
acquisition: { channels: 12, sampleRates: ['50Hz', '60Hz', '100Hz', '1000Hz'], voltageRanges: ['±2.5V', '±80V', '±600V'], iterationRange: '1~256' },
|
||||
transmission: { maxVoltage: '1500V', maxCurrent: '10A', waveformPattern: '0+0-、+0-0、+-', pulseWidths: ['0.25s', '0.5s', '1s', '2s', '4s', '8s', '16s', '32s', '64s'], waveforms: ['0+0-', '+0-0', '+-'], fullWaveform: true },
|
||||
acquisition: { channels: 12, sampleRates: ['50Hz', '60Hz', '100Hz', '1000Hz'], voltageRanges: ['±2.5V', '±80V', '±600V'] },
|
||||
protection: { overVoltage: { enabled: true, threshold: '1600V' }, overCurrent: { enabled: true, threshold: '12A' }, shortCircuit: true, overTemp: { enabled: true, threshold: '75°C' } },
|
||||
network: { wifiSsidPrefix: 'GD30-Supreme' },
|
||||
},
|
||||
|
|
@ -63,8 +63,8 @@ const configData: Record<string, ConfigDetail> = {
|
|||
'GD30-2025-000002': {
|
||||
name: 'CFG-GD30-v1.3.0', version: 'v1.3.0', uploadDate: '2025-01-18',
|
||||
params: {
|
||||
transmission: { maxVoltage: '1500V', maxCurrent: '10A', dutyCycle: '50%、100%', pulseWidths: ['0.25s', '0.5s', '1s', '2s', '4s', '8s', '16s', '32s', '64s'], waveforms: ['0+0-', '+0-0', '+-'], fullWaveform: true },
|
||||
acquisition: { channels: 12, sampleRates: ['50Hz', '60Hz', '100Hz', '1000Hz'], voltageRanges: ['±2.5V', '±80V', '±600V'], iterationRange: '1~256' },
|
||||
transmission: { maxVoltage: '1500V', maxCurrent: '10A', waveformPattern: '0+0-、+0-0、+-', pulseWidths: ['0.25s', '0.5s', '1s', '2s', '4s', '8s', '16s', '32s', '64s'], waveforms: ['0+0-', '+0-0', '+-'], fullWaveform: true },
|
||||
acquisition: { channels: 12, sampleRates: ['50Hz', '60Hz', '100Hz', '1000Hz'], voltageRanges: ['±2.5V', '±80V', '±600V'] },
|
||||
protection: { overVoltage: { enabled: true, threshold: '1600V' }, overCurrent: { enabled: true, threshold: '12A' }, shortCircuit: true, overTemp: { enabled: true, threshold: '75°C' } },
|
||||
network: { wifiSsidPrefix: 'GD30-Supreme' },
|
||||
},
|
||||
|
|
@ -72,8 +72,8 @@ const configData: Record<string, ConfigDetail> = {
|
|||
'GT20-2025-000045': {
|
||||
name: 'CFG-GD20-v1.1.0', version: 'v1.1.0', uploadDate: '2025-02-10',
|
||||
params: {
|
||||
transmission: { maxVoltage: '800V', maxCurrent: '5A', dutyCycle: '50%', pulseWidths: ['0.25s', '0.5s', '1s', '2s', '4s', '8s'], waveforms: ['0+0-', '+0-0'], fullWaveform: false },
|
||||
acquisition: { channels: 6, sampleRates: ['50Hz', '60Hz'], voltageRanges: ['±2.5V', '±80V'], iterationRange: '1~128' },
|
||||
transmission: { maxVoltage: '800V', maxCurrent: '5A', waveformPattern: '0+0-、+0-0', pulseWidths: ['0.25s', '0.5s', '1s', '2s', '4s', '8s'], waveforms: ['0+0-', '+0-0'], fullWaveform: false },
|
||||
acquisition: { channels: 6, sampleRates: ['50Hz', '60Hz'], voltageRanges: ['±2.5V', '±80V'] },
|
||||
protection: { overVoltage: { enabled: true, threshold: '900V' }, overCurrent: { enabled: true, threshold: '6A' }, shortCircuit: true, overTemp: { enabled: true, threshold: '70°C' } },
|
||||
network: { wifiSsidPrefix: 'GD20' },
|
||||
},
|
||||
|
|
@ -551,8 +551,8 @@ export default function DeviceDetailPage({ params }: { params: Promise<{ sn: str
|
|||
<div style={{ fontSize: 14, fontWeight: 500 }}>{config.params.transmission.maxCurrent}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)', marginBottom: 6 }}>占空比</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 500 }}>{config.params.transmission.dutyCycle}</div>
|
||||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)', marginBottom: 6 }}>发射波形</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 500 }}>{config.params.transmission.waveformPattern}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)', marginBottom: 6 }}>支持全波形测量</div>
|
||||
|
|
@ -593,10 +593,6 @@ export default function DeviceDetailPage({ params }: { params: Promise<{ sn: str
|
|||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)', marginBottom: 6 }}>支持通道数</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 500 }}>{config.params.acquisition.channels} 通道</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)', marginBottom: 6 }}>迭代次数范围</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 500 }}>{config.params.acquisition.iterationRange}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)', marginBottom: 8 }}>采样率</div>
|
||||
|
|
@ -696,11 +692,15 @@ export default function DeviceDetailPage({ params }: { params: Promise<{ sn: str
|
|||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>占空比</label>
|
||||
<select defaultValue={config.params.transmission.dutyCycle} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
<option value="50%">50%</option>
|
||||
<option value="50%、100%">50%、100%</option>
|
||||
</select>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>发射波形</label>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{['0+0-', '+0-0', '+-'].map(w => {
|
||||
const selected = config.params.transmission.waveforms.includes(w)
|
||||
return (
|
||||
<span key={w} style={{ padding: '6px 14px', borderRadius: 6, fontSize: 13, border: selected ? '1px solid #4a7c59' : '1px solid #D9D9D9', backgroundColor: selected ? '#eef5f0' : '#fff', color: selected ? '#4a7c59' : 'rgba(0,0,0,0.65)' }}>{w}</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>
|
||||
|
|
@ -717,10 +717,6 @@ export default function DeviceDetailPage({ params }: { params: Promise<{ sn: str
|
|||
{['1', '6', '12'].map(v => <option key={v} value={v}>{v}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>迭代次数范围</label>
|
||||
<input defaultValue={config.params.acquisition.iterationRange} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid #F0F0F0' }}>保护参数</h4>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
'use client'
|
||||
import { useState, useMemo } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Download, Plus, Search, ChevronLeft, ChevronRight, Monitor, Cpu, Wifi, Power, Tag } from 'lucide-react'
|
||||
import { Download, Plus, ChevronLeft, ChevronRight, Monitor, Cpu, Wifi, Power, Tag } from 'lucide-react'
|
||||
import { useApi } from '@/lib/hooks'
|
||||
|
||||
interface Device {
|
||||
id: number; sn: string; model: string; type: string; status: string; firmware: string; production_date: string; customer: string; batch: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据日期计算 ISO 周数,返回 "YYYY-WXX" 格式
|
||||
|
|
@ -35,25 +40,7 @@ function getWeekRange(yearWeek: string): string {
|
|||
return `${fmt(monday)}-${fmt(sunday)}`
|
||||
}
|
||||
|
||||
const rawDevices = [
|
||||
{ id: 1, sn: 'GD30-2025-000001', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-15 14:30', customer: '北京地质研究院', batch: 'BATCH-2025-Q1-001' },
|
||||
{ id: 2, sn: 'GD30-2025-000002', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2025-01-18 09:15', customer: '中国地质大学', batch: 'BATCH-2025-Q1-001' },
|
||||
{ id: 3, sn: 'GD30-2024-000056', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-12-20 16:00', customer: '成都理工大学', batch: 'BATCH-2024-Q4-003' },
|
||||
{ id: 4, sn: 'GT20-2025-000045', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-02-10 11:20', customer: '武汉地质调查中心', batch: 'BATCH-2025-Q1-002' },
|
||||
{ id: 5, sn: 'GT20-2025-000046', model: 'GD-20', type: '二维电法仪', status: '装配中', firmware: 'v1.8.5', productionDate: '2025-03-01 08:45', customer: '-', batch: 'BATCH-2025-Q1-002' },
|
||||
{ id: 6, sn: 'GD30-2024-000078', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-11-05 13:30', customer: '长安大学', batch: 'BATCH-2024-Q4-002' },
|
||||
{ id: 7, sn: 'GD10-2024-000033', model: 'GD-10 Supreme', type: '入门级电法仪', status: '已激活', firmware: 'v1.5.2', productionDate: '2024-09-12 10:00', customer: '河海大学', batch: 'BATCH-2024-Q3-001' },
|
||||
{ id: 8, sn: 'GD30-2024-000089', model: 'GD-30 Supreme', type: '高密度电法仪', status: '装配中', firmware: 'v2.3.5', productionDate: '2025-03-05 15:10', customer: '-', batch: 'BATCH-2025-Q1-001' },
|
||||
{ id: 9, sn: 'GT20-2025-000012', model: 'GD-20', type: '二维电法仪', status: '已激活', firmware: 'v1.8.5', productionDate: '2025-01-22 09:30', customer: '中南大学', batch: 'BATCH-2025-Q1-002' },
|
||||
{ id: 10, sn: 'GD30-2024-000102', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已出厂', firmware: 'v2.3.4', productionDate: '2024-10-18 14:00', customer: '吉林大学', batch: 'BATCH-2024-Q4-001' },
|
||||
{ id: 11, sn: 'GD10-2024-000034', model: 'GD-10 Supreme', type: '入门级电法仪', status: '装配中', firmware: 'v1.5.2', productionDate: '2025-03-08 11:45', customer: '-', batch: 'BATCH-2025-Q1-003' },
|
||||
{ id: 12, sn: 'GD30-2024-000145', model: 'GD-30 Supreme', type: '高密度电法仪', status: '已激活', firmware: 'v2.3.5', productionDate: '2024-08-25 16:20', customer: '同济大学', batch: 'BATCH-2024-Q3-002' },
|
||||
]
|
||||
|
||||
/** 根据生产日期动态计算年周批次 */
|
||||
const devicesData = rawDevices
|
||||
|
||||
const modelOptions = ['全部', 'GD-30 Supreme', 'GD-20', 'GD-10 Supreme']
|
||||
const modelOptions = ['全部', 'GD-30 Supreme', 'GD-20', 'GD-10 Supreme', 'GM-10', 'GT-10', 'GP-10']
|
||||
const statusOptions = ['全部', '已激活', '已出厂', '装配中']
|
||||
|
||||
function getStatusStyle(status: string) {
|
||||
|
|
@ -75,6 +62,7 @@ function getStatusIcon(status: string) {
|
|||
}
|
||||
|
||||
export default function DevicesPage() {
|
||||
const { data: devicesData, loading } = useApi<Device[]>('/api/devices', [])
|
||||
const [filterModel, setFilterModel] = useState('全部')
|
||||
const [filterStatus, setFilterStatus] = useState('全部')
|
||||
const [filterDate, setFilterDate] = useState('')
|
||||
|
|
@ -104,13 +92,13 @@ export default function DevicesPage() {
|
|||
groups.set(item.year, list)
|
||||
})
|
||||
return groups
|
||||
}, [])
|
||||
}, [devicesData])
|
||||
|
||||
const filtered = devicesData.filter(d => {
|
||||
if (selectedBatch !== '全部' && d.batch !== selectedBatch) return false
|
||||
if (filterModel !== '全部' && d.model !== filterModel) return false
|
||||
if (filterStatus !== '全部' && d.status !== filterStatus) return false
|
||||
if (filterDate && !d.productionDate.startsWith(filterDate)) return false
|
||||
if (filterDate && !d.production_date.startsWith(filterDate)) return false
|
||||
if (searchText && !d.sn.toLowerCase().includes(searchText.toLowerCase())) return false
|
||||
return true
|
||||
})
|
||||
|
|
@ -122,6 +110,8 @@ export default function DevicesPage() {
|
|||
setCurrentPage(1)
|
||||
}
|
||||
|
||||
if (loading) return <div style={{ padding: 24 }}>加载中...</div>
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
{/* Header */}
|
||||
|
|
@ -241,7 +231,7 @@ export default function DevicesPage() {
|
|||
<div>型号:{device.model} {device.type}</div>
|
||||
<div>主机版本:{device.firmware}</div>
|
||||
<div>生产批次:{device.batch}</div>
|
||||
<div>生产日期:{device.productionDate}</div>
|
||||
<div>生产日期:{device.production_date}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', borderTop: '1px solid #F0F0F0' }}>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useState, Suspense } from 'react'
|
|||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { ArrowLeft, Upload, Download, ChevronDown, ChevronUp, X, Package, Shield, FileText } from 'lucide-react'
|
||||
|
||||
const firmwareTypes = ['全部', '主协板', '采集板', '发射板', '升压板', '主机固件', '计算单元固件']
|
||||
const firmwareTypes = ['全部', '主协板', '采集板', '发射板', '主机固件', '计算单元固件']
|
||||
|
||||
const firmwareData = [
|
||||
{ id: 1, version: 'v2.1.0', boardVersion: 'MB-V1.8', type: '主协板', date: '2024-03-10', status: '已发布', size: '12.5MB', downloads: 1234, hwRange: 'MB25130025 Rev.A~C', upgradeType: '可选', signed: true, md5: 'a1b2c3d4e5f6...', sha256: '9f8e7d6c5b4a...', notes: ['修复通信协议兼容性问题', '优化低功耗模式切换', '新增看门狗超时配置'] },
|
||||
|
|
@ -13,7 +13,6 @@ const firmwareData = [
|
|||
{ id: 5, version: 'v2.5.1', boardVersion: 'RX-V2.3', type: '采集板', date: '2023-09-15', status: '已发布', size: '7.1MB', downloads: 1567, hwRange: 'ACB-5000 Rev.A~C', upgradeType: '可选', signed: true, md5: 'e5f6a1b2c3d4...', sha256: '5b4a9f8e7d6c...', notes: ['优化ADC驱动', '修复温漂补偿算法'] },
|
||||
{ id: 6, version: 'v1.2.0', boardVersion: 'TX-V2.1', type: '发射板', date: '2024-02-28', status: '已发布', size: '6.3MB', downloads: 456, hwRange: 'TXB-1000 Rev.A', upgradeType: '强制', signed: true, md5: 'f6a1b2c3d4e5...', sha256: '4a9f8e7d6c5b...', notes: ['新增过流保护', '优化PWM控制算法'] },
|
||||
{ id: 7, version: 'v1.0.3', boardVersion: 'TX-V2.1', type: '发射板', date: '2023-06-20', status: '已发布', size: '5.8MB', downloads: 789, hwRange: 'TXB-800 Rev.A~B', upgradeType: '可选', signed: false, md5: 'a1c3e5b2d4f6...', sha256: '3f8e6d4b2a9c...', notes: ['修复高压输出不稳定'] },
|
||||
{ id: 8, version: 'v1.1.0', boardVersion: 'BP600-V1.2', type: '升压板', date: '2024-04-01', status: '草稿', size: '4.2MB', downloads: 0, hwRange: 'BST-500 Rev.A', upgradeType: '可选', signed: false, md5: '-', sha256: '-', notes: ['初始版本', '支持100V~600V输出'] },
|
||||
]
|
||||
|
||||
/** 设备型号固件数据:按型号 + 固件类别 */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,335 @@
|
|||
'use client'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Plus, X, Edit, Trash2, Info, Upload, RefreshCw } from 'lucide-react'
|
||||
import { useApi } from '@/lib/hooks'
|
||||
|
||||
interface MaterialType {
|
||||
id: number; name: string; category: string; deviceModels: string[]; description: string; status: string
|
||||
}
|
||||
interface MaterialVersion {
|
||||
id: number; type: string; version: string; status: string
|
||||
}
|
||||
|
||||
const categoryOptions = ['全部', '主协板', '采集板', '发射板', '升压板', '电缆头', '电缆', '机箱', '电源']
|
||||
const deviceModelOptions = ['GD-30 Supreme', 'GD-20 Supreme', 'GD-10 Supreme', 'GM-10', 'GT-10', 'GP-10']
|
||||
|
||||
function getStatusStyle(status: string) {
|
||||
switch (status) {
|
||||
case '启用': case '在产': return { backgroundColor: '#eef5f0', color: '#4a7c59', border: '1px solid #a3c4ad' }
|
||||
case '停用': case '停产': return { backgroundColor: '#FFFBE6', color: '#FAAD14', border: '1px solid #FFE58F' }
|
||||
default: return { backgroundColor: '#FAFAFA', color: 'rgba(0,0,0,0.45)', border: '1px solid #D9D9D9' }
|
||||
}
|
||||
}
|
||||
|
||||
export default function MaterialsManagePage() {
|
||||
const router = useRouter()
|
||||
const { data: materialTypes, loading: lt, refetch: refetchTypes } = useApi<MaterialType[]>('/api/material-types', [])
|
||||
const { data: versionsData, loading: lv, refetch: refetchVersions } = useApi<MaterialVersion[]>('/api/material-versions', [])
|
||||
|
||||
const [filterCategory, setFilterCategory] = useState('全部')
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [addDrawer, setAddDrawer] = useState(false)
|
||||
const [editDrawer, setEditDrawer] = useState<MaterialType | null>(null)
|
||||
const [typeForm, setTypeForm] = useState({ name: '', category: '主协板', deviceModels: [] as string[], description: '', status: '启用' })
|
||||
// Expanded row to show versions
|
||||
const [versionDrawer, setVersionDrawer] = useState<MaterialType | null>(null)
|
||||
// Add version inline
|
||||
const [addVersionFor, setAddVersionFor] = useState<string | null>(null)
|
||||
const [newVersion, setNewVersion] = useState('')
|
||||
|
||||
if (lt || lv) return <div style={{ padding: 24 }}>加载中...</div>
|
||||
|
||||
const filtered = materialTypes.filter(bt => {
|
||||
if (filterCategory !== '全部' && bt.category !== filterCategory) return false
|
||||
if (searchText && !bt.name.toLowerCase().includes(searchText.toLowerCase()) && !bt.description.toLowerCase().includes(searchText.toLowerCase())) return false
|
||||
return true
|
||||
})
|
||||
|
||||
// Group versions by category
|
||||
const versionsByCategory = (category: string) => versionsData.filter(v => v.type === category)
|
||||
|
||||
const handleAddType = async () => {
|
||||
await fetch('/api/material-types', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(typeForm) })
|
||||
refetchTypes(); setAddDrawer(false)
|
||||
setTypeForm({ name: '', category: '主协板', deviceModels: [], description: '', status: '启用' })
|
||||
}
|
||||
const handleEditType = async () => {
|
||||
if (!editDrawer) return
|
||||
await fetch('/api/material-types', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editDrawer) })
|
||||
refetchTypes(); setEditDrawer(null)
|
||||
}
|
||||
const handleDeleteType = async (id: number) => {
|
||||
await fetch('/api/material-types', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }) })
|
||||
refetchTypes()
|
||||
}
|
||||
const toggleDeviceModel = (model: string, target: 'add' | 'edit') => {
|
||||
if (target === 'add') {
|
||||
setTypeForm(prev => ({ ...prev, deviceModels: prev.deviceModels.includes(model) ? prev.deviceModels.filter(m => m !== model) : [...prev.deviceModels, model] }))
|
||||
} else if (editDrawer) {
|
||||
setEditDrawer({ ...editDrawer, deviceModels: editDrawer.deviceModels.includes(model) ? editDrawer.deviceModels.filter(m => m !== model) : [...editDrawer.deviceModels, model] })
|
||||
}
|
||||
}
|
||||
const handleToggleVersionStatus = async (id: number) => {
|
||||
const v = versionsData.find(b => b.id === id)
|
||||
if (!v) return
|
||||
await fetch('/api/material-versions', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, status: v.status === '在产' ? '停产' : '在产' }) })
|
||||
refetchVersions()
|
||||
}
|
||||
const handleAddVersion = async (category: string) => {
|
||||
if (!newVersion.trim()) return
|
||||
await fetch('/api/material-versions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: category, version: newVersion, status: '在产' }) })
|
||||
refetchVersions(); setAddVersionFor(null); setNewVersion('')
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, margin: 0 }}>物料管理</h2>
|
||||
<p style={{ fontSize: 14, color: 'rgba(0,0,0,0.45)', margin: '4px 0 0' }}>管理物料类型、适配设备型号及物料版本</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: 16, backgroundColor: '#eef5f0', borderRadius: 8, marginBottom: 24, border: '1px solid #a3c4ad' }}>
|
||||
<Info size={18} style={{ color: '#4a7c59', flexShrink: 0, marginTop: 2 }} />
|
||||
<div style={{ fontSize: 14, color: '#4a7c59', lineHeight: 1.6 }}>物料类型定义了物料与设备型号的关联关系。点击行可展开查看和管理该分类下的物料版本。</div>
|
||||
</div>
|
||||
|
||||
{/* Filter */}
|
||||
<div style={{ backgroundColor: '#fff', borderRadius: 8, padding: 20, marginBottom: 24, boxShadow: '0 1px 2px rgba(0,0,0,0.05)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 16 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>物料分类</label>
|
||||
<select value={filterCategory} onChange={e => setFilterCategory(e.target.value)} style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
{categoryOptions.map(c => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ flex: 2 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>搜索</label>
|
||||
<input type="text" value={searchText} onChange={e => setSearchText(e.target.value)} placeholder="搜索类型名称或描述" style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<div style={{ backgroundColor: '#fff', borderRadius: 8, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #F0F0F0' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>物料列表(共 {filtered.length} 项)</h3>
|
||||
<button onClick={() => setAddDrawer(true)} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}><Plus size={16} />新增物料类型</button>
|
||||
</div>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead><tr style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{['物料名称', '物料分类', '适配设备型号', '版本', '描述', '状态', '操作'].map(h => (
|
||||
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: 13, fontWeight: 600, color: 'rgba(0,0,0,0.85)', borderBottom: '1px solid #F0F0F0' }}>{h}</th>
|
||||
))}
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{filtered.map(bt => {
|
||||
const versions = versionsByCategory(bt.category)
|
||||
return (
|
||||
<tr key={bt.id} style={{ borderBottom: '1px solid #F0F0F0' }}>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, fontWeight: 500 }}>{bt.name}</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, color: 'rgba(0,0,0,0.65)' }}>{bt.category}</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
{bt.deviceModels.map(dm => (<span key={dm} style={{ padding: '2px 8px', borderRadius: 4, fontSize: 12, backgroundColor: '#F0F5FF', color: '#597EF7', border: '1px solid #ADC6FF' }}>{dm}</span>))}
|
||||
</div>
|
||||
</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
{versions.map(v => (
|
||||
<span key={v.id} style={{ padding: '2px 8px', borderRadius: 4, fontSize: 12, ...getStatusStyle(v.status) }}>{v.version}</span>
|
||||
))}
|
||||
{versions.length === 0 && <span style={{ fontSize: 12, color: 'rgba(0,0,0,0.25)' }}>暂无版本</span>}
|
||||
<button onClick={() => setVersionDrawer(bt)} style={{ padding: '1px 6px', borderRadius: 4, fontSize: 11, border: '1px solid #D9D9D9', backgroundColor: '#fff', color: 'rgba(0,0,0,0.45)', cursor: 'pointer' }}>管理</button>
|
||||
</div>
|
||||
</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 13, color: 'rgba(0,0,0,0.65)' }}>{bt.description}</td>
|
||||
<td style={{ padding: '12px 16px' }}><span style={{ ...getStatusStyle(bt.status), padding: '2px 8px', borderRadius: 4, fontSize: 12 }}>{bt.status}</span></td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button onClick={() => setEditDrawer({ ...bt })} style={{ color: '#4a7c59', cursor: 'pointer', border: 'none', background: 'none', fontSize: 13, display: 'flex', alignItems: 'center', gap: 4 }}><Edit size={14} />编辑</button>
|
||||
<button onClick={() => handleDeleteType(bt.id)} style={{ color: '#FF4D4F', cursor: 'pointer', border: 'none', background: 'none', fontSize: 13, display: 'flex', alignItems: 'center', gap: 4 }}><Trash2 size={14} />删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Version Management Drawer */}
|
||||
{versionDrawer && (() => {
|
||||
const category = versionDrawer.category
|
||||
const versions = versionsByCategory(category)
|
||||
const gdModels = ['GD-30 Supreme', 'GD-20 Supreme', 'GD-10 Supreme']
|
||||
const firmwareCategories = ['采集板', '主协板', '发射板']
|
||||
const hasFirmware = firmwareCategories.includes(category) && versionDrawer.deviceModels.some(dm => gdModels.includes(dm))
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 50 }}>
|
||||
<div onClick={() => setVersionDrawer(null)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} />
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: 560, backgroundColor: '#fff', boxShadow: '-2px 0 8px rgba(0,0,0,0.15)', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #F0F0F0' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>{versionDrawer.name} — 版本管理</h3>
|
||||
<button onClick={() => setVersionDrawer(null)} style={{ border: 'none', background: 'none', cursor: 'pointer', padding: 4 }}><X size={20} /></button>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
{/* 物料信息摘要 */}
|
||||
<div style={{ padding: 16, backgroundColor: '#FAFAFA', borderRadius: 8, marginBottom: 20, border: '1px solid #F0F0F0' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, fontSize: 13 }}>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>物料分类:</span>{category}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>状态:</span><span style={{ ...getStatusStyle(versionDrawer.status), padding: '1px 6px', borderRadius: 4, fontSize: 12 }}>{versionDrawer.status}</span></div>
|
||||
<div style={{ gridColumn: '1 / -1' }}><span style={{ color: 'rgba(0,0,0,0.45)' }}>适配型号:</span>{versionDrawer.deviceModels.join('、')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 添加版本 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<h4 style={{ fontSize: 14, fontWeight: 600, margin: 0 }}>版本列表</h4>
|
||||
<button onClick={() => { setAddVersionFor(category); setNewVersion('') }} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '4px 12px', border: '1px solid #4a7c59', borderRadius: 6, backgroundColor: '#fff', color: '#4a7c59', cursor: 'pointer', fontSize: 13 }}><Plus size={14} />添加版本</button>
|
||||
</div>
|
||||
{addVersionFor === category && (
|
||||
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
||||
<input value={newVersion} onChange={e => setNewVersion(e.target.value)} placeholder="输入版本号,如 MB-V3.0" style={{ flex: 1, padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 13, boxSizing: 'border-box' }} />
|
||||
<button onClick={() => handleAddVersion(category)} style={{ padding: '6px 16px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 13 }}>确定</button>
|
||||
<button onClick={() => setAddVersionFor(null)} style={{ padding: '6px 16px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 13 }}>取消</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 版本表格 */}
|
||||
{versions.length > 0 ? (
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', backgroundColor: '#fff', borderRadius: 6, overflow: 'hidden', border: '1px solid #F0F0F0' }}>
|
||||
<thead><tr style={{ backgroundColor: '#F5F5F5' }}>
|
||||
{['版本', '状态', '操作'].map(h => (<th key={h} style={{ padding: '10px 14px', textAlign: 'left', fontSize: 12, fontWeight: 600, color: 'rgba(0,0,0,0.65)', borderBottom: '1px solid #F0F0F0' }}>{h}</th>))}
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{versions.map(v => (
|
||||
<tr key={v.id} style={{ borderBottom: '1px solid #F0F0F0' }}>
|
||||
<td style={{ padding: '10px 14px', fontSize: 13, fontWeight: 500 }}>{v.version}</td>
|
||||
<td style={{ padding: '10px 14px' }}><span style={{ ...getStatusStyle(v.status), padding: '2px 8px', borderRadius: 4, fontSize: 11 }}>{v.status}</span></td>
|
||||
<td style={{ padding: '10px 14px' }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button onClick={() => handleToggleVersionStatus(v.id)} style={{ color: v.status === '在产' ? '#FAAD14' : '#4a7c59', cursor: 'pointer', border: 'none', background: 'none', fontSize: 12, display: 'flex', alignItems: 'center', gap: 4 }}><RefreshCw size={12} />{v.status === '在产' ? '停产' : '恢复'}</button>
|
||||
{hasFirmware ? (
|
||||
<button onClick={() => router.push(`/firmware?board=${v.version}`)} style={{ color: '#4a7c59', cursor: 'pointer', border: 'none', background: 'none', fontSize: 12, display: 'flex', alignItems: 'center', gap: 4 }}><Upload size={12} />固件管理</button>
|
||||
) : (
|
||||
<span style={{ fontSize: 12, color: 'rgba(0,0,0,0.15)', display: 'flex', alignItems: 'center', gap: 4, cursor: 'not-allowed' }}><Upload size={12} />固件管理</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div style={{ fontSize: 13, color: 'rgba(0,0,0,0.25)', textAlign: 'center', padding: 24, backgroundColor: '#FAFAFA', borderRadius: 8, border: '1px solid #F0F0F0' }}>暂无版本,点击上方按钮添加</div>
|
||||
)}
|
||||
|
||||
{/* 固件说明 */}
|
||||
{!hasFirmware && (
|
||||
<div style={{ marginTop: 16, padding: 12, backgroundColor: '#FFFBE6', borderRadius: 6, border: '1px solid #FFE58F', fontSize: 12, color: '#FAAD14', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Info size={14} />
|
||||
<span>该物料类型不支持固件管理(仅 GD-10/20/30 的主协板、采集板、发射板支持)</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ padding: '16px 24px', borderTop: '1px solid #F0F0F0', display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<button onClick={() => setVersionDrawer(null)} style={{ padding: '8px 20px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{/* Add Type Drawer */}
|
||||
{addDrawer && (
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 50 }}>
|
||||
<div onClick={() => setAddDrawer(false)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} />
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: 520, backgroundColor: '#fff', boxShadow: '-2px 0 8px rgba(0,0,0,0.15)', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #F0F0F0' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>新增物料类型</h3>
|
||||
<button onClick={() => setAddDrawer(false)} style={{ border: 'none', background: 'none', cursor: 'pointer', padding: 4 }}><X size={20} /></button>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 类型名称</label>
|
||||
<input value={typeForm.name} onChange={e => setTypeForm({ ...typeForm, name: e.target.value })} placeholder="如 GM10 采集板" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料分类</label>
|
||||
<select value={typeForm.category} onChange={e => setTypeForm({ ...typeForm, category: e.target.value })} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
{categoryOptions.filter(c => c !== '全部').map(c => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 适配设备型号</label>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{deviceModelOptions.map(dm => (
|
||||
<button key={dm} onClick={() => toggleDeviceModel(dm, 'add')} style={{ padding: '6px 14px', borderRadius: 6, fontSize: 13, cursor: 'pointer', border: typeForm.deviceModels.includes(dm) ? '1px solid #4a7c59' : '1px solid #D9D9D9', backgroundColor: typeForm.deviceModels.includes(dm) ? '#eef5f0' : '#fff', color: typeForm.deviceModels.includes(dm) ? '#4a7c59' : 'rgba(0,0,0,0.65)' }}>{dm}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}>描述</label>
|
||||
<textarea value={typeForm.description} onChange={e => setTypeForm({ ...typeForm, description: e.target.value })} placeholder="物料类型描述" rows={3} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box', resize: 'vertical' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '16px 24px', borderTop: '1px solid #F0F0F0', display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||
<button onClick={() => setAddDrawer(false)} style={{ padding: '8px 20px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>取消</button>
|
||||
<button onClick={handleAddType} disabled={!typeForm.name || typeForm.deviceModels.length === 0} style={{ padding: '8px 20px', border: 'none', borderRadius: 6, backgroundColor: typeForm.name && typeForm.deviceModels.length > 0 ? '#4a7c59' : '#D9D9D9', color: '#fff', cursor: typeForm.name && typeForm.deviceModels.length > 0 ? 'pointer' : 'not-allowed', fontSize: 14 }}>保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Edit Type Drawer */}
|
||||
{editDrawer && (
|
||||
<div style={{ position: 'fixed', inset: 0, zIndex: 50 }}>
|
||||
<div onClick={() => setEditDrawer(null)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} />
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: 520, backgroundColor: '#fff', boxShadow: '-2px 0 8px rgba(0,0,0,0.15)', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #F0F0F0' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>编辑物料类型</h3>
|
||||
<button onClick={() => setEditDrawer(null)} style={{ border: 'none', background: 'none', cursor: 'pointer', padding: 4 }}><X size={20} /></button>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 类型名称</label>
|
||||
<input value={editDrawer.name} onChange={e => setEditDrawer({ ...editDrawer, name: e.target.value })} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料分类</label>
|
||||
<select value={editDrawer.category} onChange={e => setEditDrawer({ ...editDrawer, category: e.target.value })} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
{categoryOptions.filter(c => c !== '全部').map(c => <option key={c} value={c}>{c}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 适配设备型号</label>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{deviceModelOptions.map(dm => (
|
||||
<button key={dm} onClick={() => toggleDeviceModel(dm, 'edit')} style={{ padding: '6px 14px', borderRadius: 6, fontSize: 13, cursor: 'pointer', border: editDrawer.deviceModels.includes(dm) ? '1px solid #4a7c59' : '1px solid #D9D9D9', backgroundColor: editDrawer.deviceModels.includes(dm) ? '#eef5f0' : '#fff', color: editDrawer.deviceModels.includes(dm) ? '#4a7c59' : 'rgba(0,0,0,0.65)' }}>{dm}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}>描述</label>
|
||||
<textarea value={editDrawer.description} onChange={e => setEditDrawer({ ...editDrawer, description: e.target.value })} rows={3} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box', resize: 'vertical' }} />
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}>状态</label>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
{['启用', '停用'].map(s => (
|
||||
<button key={s} onClick={() => setEditDrawer({ ...editDrawer, status: s })} style={{ padding: '6px 20px', borderRadius: 6, fontSize: 14, cursor: 'pointer', border: editDrawer.status === s ? '1px solid #4a7c59' : '1px solid #D9D9D9', backgroundColor: editDrawer.status === s ? '#eef5f0' : '#fff', color: editDrawer.status === s ? '#4a7c59' : 'rgba(0,0,0,0.65)' }}>{s}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '16px 24px', borderTop: '1px solid #F0F0F0', display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||
<button onClick={() => setEditDrawer(null)} style={{ padding: '8px 20px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>取消</button>
|
||||
<button onClick={handleEditType} style={{ padding: '8px 20px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}>保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,25 +2,26 @@
|
|||
import { useState, useMemo } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { ChevronLeft, ChevronRight, Plus, Download, Eye, X, FileText, Download as DownloadIcon } from 'lucide-react'
|
||||
import { useApi } from '@/lib/hooks'
|
||||
|
||||
const boardCardsData = [
|
||||
{ id: 1, sn: 'MB25011500', type: '主协板', version: 'MB-V2.1', firmware: 'v2.1', status: '在库', deviceSn: '-', productionDate: '2025-01-15', calibStatus: '-', calibDate: '-'},
|
||||
{ id: 2, sn: 'MCB-3000-20250118002', type: '主协板', version: 'MCB-3000', firmware: 'v2.1.0', status: '已装配', deviceSn: 'GD30-2025-000001', productionDate: '2025-01-18', calibStatus: '-', calibDate: '-', },
|
||||
{ id: 3, sn: 'ACB-6000-20250110001', type: '采集板', version: 'ACB-6000', firmware: 'v3.0.2', status: '已装配', deviceSn: 'GD30-2025-000001', productionDate: '2025-01-10', calibStatus: '合格', calibDate: '2025-01-12'},
|
||||
{ id: 4, sn: 'ACB-6000-20250110002', type: '采集板', version: 'ACB-6000', firmware: 'v3.0.2', status: '已装配', deviceSn: 'GD30-2025-000001', productionDate: '2025-01-10', calibStatus: '合格', calibDate: '2025-01-12'},
|
||||
{ id: 5, sn: 'ACB-6000-20250112003', type: '采集板', version: 'ACB-6000', firmware: 'v3.0.2', status: '在库', deviceSn: '-', productionDate: '2025-01-12', calibStatus: '待校准', calibDate: '-', },
|
||||
{ id: 6, sn: 'ACB-5000-20241205001', type: '采集板', version: 'ACB-5000', firmware: 'v2.5.1', status: '已装配', deviceSn: 'GD20-2024-000045', productionDate: '2024-12-05', calibStatus: '合格', calibDate: '2024-12-08'},
|
||||
{ id: 7, sn: 'TXB-1000-20250120001', type: '发射板', version: 'TXB-1000', firmware: 'v1.2.0', status: '已装配', deviceSn: 'GD30-2025-000001', productionDate: '2025-01-20', calibStatus: '-', calibDate: '-'},
|
||||
{ id: 8, sn: 'TXB-1000-20250122002', type: '发射板', version: 'TXB-1000', firmware: 'v1.2.0', status: '在库', deviceSn: '-', productionDate: '2025-01-22', calibStatus: '-', calibDate: '-'},
|
||||
{ id: 9, sn: 'BST-500-20250201001', type: '升压板', version: 'BST-500', firmware: 'v1.1.0', status: '已装配', deviceSn: 'GD30-2025-000002', productionDate: '2025-02-01', calibStatus: '-', calibDate: '-'},
|
||||
{ id: 10, sn: 'BST-500-20250203002', type: '升压板', version: 'BST-500', firmware: 'v1.1.0', status: '在库', deviceSn: '-', productionDate: '2025-02-03', calibStatus: '-', calibDate: '-'},
|
||||
{ id: 11, sn: 'ACB-6000-20241120001', type: '采集板', version: 'ACB-6000', firmware: 'v3.0.2', status: '故障', deviceSn: '-', productionDate: '2024-11-20', calibStatus: '不合格', calibDate: '2025-02-10'},
|
||||
{ id: 12, sn: 'MCB-2000-20240915001', type: '主协板', version: 'MCB-2000', firmware: 'v1.8.5', status: '已装配', deviceSn: 'GD20-2024-000046', productionDate: '2024-09-15', calibStatus: '-', calibDate: '-'},
|
||||
{ id: 13, sn: 'ACB-6000-20250305001', type: '采集板', version: 'ACB-6000', firmware: 'v3.0.2', status: '在库', deviceSn: '-', productionDate: '2025-03-05', calibStatus: '待校准', calibDate: '-'},
|
||||
{ id: 14, sn: 'TXB-800-20240610001', type: '发射板', version: 'TXB-800', firmware: 'v1.0.3', status: '报废', deviceSn: '-', productionDate: '2024-06-10', calibStatus: '-', calibDate: '-'},
|
||||
]
|
||||
interface BoardCard {
|
||||
id: number
|
||||
sn: string
|
||||
name: string
|
||||
category: string
|
||||
type: string
|
||||
device_model: string
|
||||
version: string
|
||||
description: string
|
||||
firmware: string
|
||||
status: string
|
||||
device_sn: string
|
||||
production_date: string
|
||||
calib_status: string
|
||||
calib_date: string
|
||||
}
|
||||
|
||||
const typeOptions = ['全部', '主协板', '采集板', '发射板', '升压板']
|
||||
const typeOptions = ['全部', '主协板', '采集板', '发射板', '升压板', '电缆头', '电缆', '机箱', '电源']
|
||||
const statusOptions = ['全部', '在库', '已装配', '故障', '报废']
|
||||
const calibStatusOptions = ['全部', '合格', '不合格', '待校准']
|
||||
|
||||
|
|
@ -81,49 +82,52 @@ function getCalibStyle(status: string) {
|
|||
}
|
||||
|
||||
export default function BoardCardsPage() {
|
||||
const { data: boardCardsData, loading } = useApi<BoardCard[]>('/api/materials', [])
|
||||
const [filterType, setFilterType] = useState('全部')
|
||||
const [filterStatus, setFilterStatus] = useState('全部')
|
||||
const [filterCalib, setFilterCalib] = useState('全部')
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [detailDrawer, setDetailDrawer] = useState<typeof boardCardsData[0] | null>(null)
|
||||
const [calibFileDrawer, setCalibFileDrawer] = useState<typeof boardCardsData[0] | null>(null)
|
||||
const [detailDrawer, setDetailDrawer] = useState<BoardCard | null>(null)
|
||||
const [calibFileDrawer, setCalibFileDrawer] = useState<BoardCard | null>(null)
|
||||
const pageSize = 8
|
||||
|
||||
const filtered = useMemo(() => boardCardsData.filter(b => {
|
||||
if (filterType !== '全部' && b.type !== filterType) return false
|
||||
if (loading) return <div style={{ padding: 24 }}>加载中...</div>
|
||||
|
||||
const filtered = boardCardsData.filter(b => {
|
||||
if (filterType !== '全部' && b.category !== filterType) return false
|
||||
if (filterStatus !== '全部' && b.status !== filterStatus) return false
|
||||
if (filterCalib !== '全部' && b.calibStatus !== filterCalib) return false
|
||||
if (searchText && !b.sn.toLowerCase().includes(searchText.toLowerCase()) && !b.deviceSn.toLowerCase().includes(searchText.toLowerCase())) return false
|
||||
if (filterCalib !== '全部' && b.calib_status !== filterCalib) return false
|
||||
if (searchText && !b.sn.toLowerCase().includes(searchText.toLowerCase()) && !b.name.toLowerCase().includes(searchText.toLowerCase())) return false
|
||||
return true
|
||||
}), [filterType, filterStatus, filterCalib, searchText])
|
||||
})
|
||||
|
||||
const totalPages = Math.ceil(filtered.length / pageSize)
|
||||
const paged = filtered.slice((currentPage - 1) * pageSize, currentPage * pageSize)
|
||||
|
||||
// 统计
|
||||
const stats = useMemo(() => ({
|
||||
const stats = {
|
||||
total: boardCardsData.length,
|
||||
inStock: boardCardsData.filter(b => b.status === '在库').length,
|
||||
assembled: boardCardsData.filter(b => b.status === '已装配').length,
|
||||
faulty: boardCardsData.filter(b => b.status === '故障').length,
|
||||
pendingCalib: boardCardsData.filter(b => b.calibStatus === '待校准').length,
|
||||
}), [])
|
||||
pendingCalib: boardCardsData.filter(b => b.calib_status === '待校准').length,
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
{/* Header */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
||||
<div>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, margin: 0 }}>板卡列表</h2>
|
||||
<p style={{ fontSize: 14, color: 'rgba(0,0,0,0.45)', margin: '4px 0 0' }}>管理所有板卡实例,跟踪板卡状态与校准信息</p>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, margin: 0 }}>物料列表</h2>
|
||||
<p style={{ fontSize: 14, color: 'rgba(0,0,0,0.45)', margin: '4px 0 0' }}>管理所有物料实例,跟踪物料状态与校准信息</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>
|
||||
<Download size={16} />导出
|
||||
</button>
|
||||
<Link href="/board-cards/register" style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14, textDecoration: 'none' }}>
|
||||
<Plus size={16} />登记板卡
|
||||
<Link href="/materials/register" style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 16px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14, textDecoration: 'none' }}>
|
||||
<Plus size={16} />登记物料
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -131,7 +135,7 @@ export default function BoardCardsPage() {
|
|||
{/* Stats Cards */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 16, marginBottom: 24 }}>
|
||||
{[
|
||||
{ label: '板卡总数', value: stats.total, color: '#4a7c59', bg: '#eef5f0' },
|
||||
{ label: '物料总数', value: stats.total, color: '#4a7c59', bg: '#eef5f0' },
|
||||
{ label: '在库', value: stats.inStock, color: '#1890FF', bg: '#E6F7FF' },
|
||||
{ label: '已装配', value: stats.assembled, color: '#52C41A', bg: '#F6FFED' },
|
||||
{ label: '故障', value: stats.faulty, color: '#FF4D4F', bg: '#FFF1F0' },
|
||||
|
|
@ -148,13 +152,13 @@ export default function BoardCardsPage() {
|
|||
<div style={{ backgroundColor: '#fff', borderRadius: 8, padding: 20, marginBottom: 24, boxShadow: '0 1px 2px rgba(0,0,0,0.05)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 16 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>板卡类型</label>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>物料分类</label>
|
||||
<select value={filterType} onChange={e => { setFilterType(e.target.value); setCurrentPage(1) }} style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
{typeOptions.map(t => <option key={t} value={t}>{t}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>板卡状态</label>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>物料状态</label>
|
||||
<select value={filterStatus} onChange={e => { setFilterStatus(e.target.value); setCurrentPage(1) }} style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
{statusOptions.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
|
|
@ -166,8 +170,8 @@ export default function BoardCardsPage() {
|
|||
</select>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>SN / 设备SN</label>
|
||||
<input type="text" value={searchText} onChange={e => { setSearchText(e.target.value); setCurrentPage(1) }} placeholder="搜索板卡SN或设备SN" style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
<label style={{ display: 'block', fontSize: 13, color: 'rgba(0,0,0,0.65)', marginBottom: 6 }}>SN / 物料名称</label>
|
||||
<input type="text" value={searchText} onChange={e => { setSearchText(e.target.value); setCurrentPage(1) }} placeholder="搜索SN或物料名称" style={{ width: '100%', padding: '6px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -177,7 +181,7 @@ export default function BoardCardsPage() {
|
|||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{['板卡SN', '类型', '版本', '固件', '状态', '所属设备', '校准状态', '操作'].map(h => (
|
||||
{['物料名称', '物料类型', '适配设备型号', '物料版本', '描述', '状态', '操作'].map(h => (
|
||||
<th key={h} style={{ padding: '12px 14px', textAlign: 'left', fontSize: 13, fontWeight: 600, color: 'rgba(0,0,0,0.85)', borderBottom: '1px solid #F0F0F0', whiteSpace: 'nowrap' }}>{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
|
|
@ -185,21 +189,14 @@ export default function BoardCardsPage() {
|
|||
<tbody>
|
||||
{paged.map(row => (
|
||||
<tr key={row.id} style={{ borderBottom: '1px solid #F0F0F0' }}>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, fontWeight: 500 }}>{row.sn}</td>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, fontWeight: 500 }}>{row.name}</td>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, color: 'rgba(0,0,0,0.65)' }}>{row.type}</td>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, color: 'rgba(0,0,0,0.65)' }}>{row.device_model}</td>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, color: 'rgba(0,0,0,0.65)' }}>{row.version}</td>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, color: 'rgba(0,0,0,0.65)' }}>{row.firmware}</td>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, color: 'rgba(0,0,0,0.65)', maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{row.description || '-'}</td>
|
||||
<td style={{ padding: '12px 14px' }}>
|
||||
<span style={{ ...getStatusStyle(row.status), padding: '2px 8px', borderRadius: 4, fontSize: 12 }}>{row.status}</span>
|
||||
</td>
|
||||
<td style={{ padding: '12px 14px', fontSize: 13, color: row.deviceSn === '-' ? 'rgba(0,0,0,0.25)' : '#4a7c59', fontWeight: row.deviceSn === '-' ? 400 : 500 }}>{row.deviceSn}</td>
|
||||
<td style={{ padding: '12px 14px' }}>
|
||||
{row.calibStatus !== '-' ? (
|
||||
<span style={{ ...getCalibStyle(row.calibStatus), padding: '2px 8px', borderRadius: 4, fontSize: 12 }}>{row.calibStatus}</span>
|
||||
) : (
|
||||
<span style={{ fontSize: 12, color: 'rgba(0,0,0,0.25)' }}>-</span>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ padding: '12px 14px' }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button onClick={() => setDetailDrawer(row)} style={{ color: '#4a7c59', cursor: 'pointer', border: 'none', background: 'none', fontSize: 13, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
|
|
@ -238,7 +235,7 @@ export default function BoardCardsPage() {
|
|||
<div onClick={() => setDetailDrawer(null)} style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.45)' }} />
|
||||
<div style={{ position: 'absolute', right: 0, top: 0, bottom: 0, width: 520, backgroundColor: '#fff', boxShadow: '-2px 0 8px rgba(0,0,0,0.15)', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 24px', borderBottom: '1px solid #F0F0F0' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>板卡详情</h3>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, margin: 0 }}>物料详情</h3>
|
||||
<button onClick={() => setDetailDrawer(null)} style={{ border: 'none', background: 'none', cursor: 'pointer', padding: 4 }}><X size={20} /></button>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
|
|
@ -246,11 +243,11 @@ export default function BoardCardsPage() {
|
|||
<div style={{ padding: 16, backgroundColor: '#FAFAFA', borderRadius: 8, marginBottom: 16, border: '1px solid #F0F0F0' }}>
|
||||
<h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 12, marginTop: 0 }}>基本信息</h4>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, fontSize: 13 }}>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>板卡SN:</span>{detailDrawer.sn}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>板卡类型:</span>{detailDrawer.type}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>物料SN:</span>{detailDrawer.sn}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>物料类型:</span>{detailDrawer.type}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>版本:</span>{detailDrawer.version}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>固件版本:</span>{detailDrawer.firmware}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>生产日期:</span>{detailDrawer.productionDate}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>生产日期:</span>{detailDrawer.production_date}</div>
|
||||
<div>
|
||||
<span style={{ color: 'rgba(0,0,0,0.45)' }}>状态:</span>
|
||||
<span style={{ ...getStatusStyle(detailDrawer.status), padding: '1px 6px', borderRadius: 4, fontSize: 12 }}>{detailDrawer.status}</span>
|
||||
|
|
@ -263,10 +260,10 @@ export default function BoardCardsPage() {
|
|||
<h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 12, marginTop: 0 }}>装配信息</h4>
|
||||
<div style={{ fontSize: 13 }}>
|
||||
<span style={{ color: 'rgba(0,0,0,0.45)' }}>所属设备:</span>
|
||||
{detailDrawer.deviceSn === '-' ? (
|
||||
{detailDrawer.device_sn === '-' ? (
|
||||
<span style={{ color: 'rgba(0,0,0,0.25)' }}>未装配</span>
|
||||
) : (
|
||||
<span style={{ color: '#4a7c59', fontWeight: 500 }}>{detailDrawer.deviceSn}</span>
|
||||
<span style={{ color: '#4a7c59', fontWeight: 500 }}>{detailDrawer.device_sn}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -278,13 +275,13 @@ export default function BoardCardsPage() {
|
|||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, fontSize: 13 }}>
|
||||
<div>
|
||||
<span style={{ color: 'rgba(0,0,0,0.45)' }}>校准状态:</span>
|
||||
{detailDrawer.calibStatus !== '-' ? (
|
||||
<span style={{ ...getCalibStyle(detailDrawer.calibStatus), padding: '1px 6px', borderRadius: 4, fontSize: 12 }}>{detailDrawer.calibStatus}</span>
|
||||
{detailDrawer.calib_status !== '-' ? (
|
||||
<span style={{ ...getCalibStyle(detailDrawer.calib_status), padding: '1px 6px', borderRadius: 4, fontSize: 12 }}>{detailDrawer.calib_status}</span>
|
||||
) : (
|
||||
<span style={{ color: 'rgba(0,0,0,0.25)' }}>-</span>
|
||||
)}
|
||||
</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>校准日期:</span>{detailDrawer.calibDate}</div>
|
||||
<div><span style={{ color: 'rgba(0,0,0,0.45)' }}>校准日期:</span>{detailDrawer.calib_date}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useRef } from 'react'
|
||||
import { ArrowLeft, Plus, Trash2, Upload, Info, CheckCircle, FileText, X } from 'lucide-react'
|
||||
import { ArrowLeft, Plus, Trash2, Upload, Info, CheckCircle, FileText, X, ScanLine } from 'lucide-react'
|
||||
|
||||
/** 板卡类型 -> 可选版本 */
|
||||
const versionsByType: Record<string, { version: string; firmware: string }[]> = {
|
||||
|
|
@ -22,6 +22,25 @@ const versionsByType: Record<string, { version: string; firmware: string }[]> =
|
|||
{ version: 'BO-V2.1', firmware: 'v1.1' },
|
||||
{ version: 'BO-V2.2', firmware: 'v0.9' },
|
||||
],
|
||||
'电缆头': [
|
||||
{ version: 'CBH-V1.0', firmware: '-' },
|
||||
],
|
||||
'电缆': [
|
||||
{ version: 'CBL-30M', firmware: '-' },
|
||||
{ version: 'CBL-60M', firmware: '-' },
|
||||
{ version: 'CBL-100M', firmware: '-' },
|
||||
],
|
||||
'机箱': [
|
||||
{ version: 'GD30-CASE-A', firmware: '-' },
|
||||
{ version: 'GD30-CASE-B', firmware: '-' },
|
||||
{ version: 'GM10-CASE-A', firmware: '-' },
|
||||
],
|
||||
'电源': [
|
||||
{ version: 'BP150-V1.0', firmware: '-' },
|
||||
{ version: 'BP300-V1.0', firmware: '-' },
|
||||
{ version: 'BP600-V1.0', firmware: '-' },
|
||||
{ version: 'BP600-V2.0', firmware: '-' },
|
||||
],
|
||||
}
|
||||
|
||||
const typeOptions = Object.keys(versionsByType)
|
||||
|
|
@ -100,12 +119,12 @@ export default function BoardRegisterPage() {
|
|||
<div style={{ flex: 1, padding: 24, paddingBottom: 80 }}>
|
||||
{/* Header */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24 }}>
|
||||
<button onClick={() => router.push('/board-cards')} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer' }}>
|
||||
<button onClick={() => router.push('/materials')} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer' }}>
|
||||
<ArrowLeft size={16} />
|
||||
</button>
|
||||
<div>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, margin: 0 }}>登记板卡</h2>
|
||||
<p style={{ fontSize: 14, color: 'rgba(0,0,0,0.45)', margin: '4px 0 0' }}>登记新板卡信息,支持单个或批量登记</p>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, margin: 0 }}>登记物料</h2>
|
||||
<p style={{ fontSize: 14, color: 'rgba(0,0,0,0.45)', margin: '4px 0 0' }}>登记新物料信息,支持单个或批量登记</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -113,7 +132,7 @@ export default function BoardRegisterPage() {
|
|||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: 16, backgroundColor: '#eef5f0', borderRadius: 8, marginBottom: 24, border: '1px solid #a3c4ad' }}>
|
||||
<Info size={18} style={{ color: '#4a7c59', flexShrink: 0, marginTop: 2 }} />
|
||||
<div style={{ fontSize: 14, color: '#4a7c59', lineHeight: 1.6 }}>
|
||||
板卡SN号为唯一标识,请确保录入正确。采集板登记后需要进行校准才能用于装配。选择版本后固件版本会自动填充。
|
||||
物料SN号为唯一标识,请确保录入正确。采集板登记后需要进行校准才能用于装配。选择版本后固件版本会自动填充。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -134,7 +153,7 @@ export default function BoardRegisterPage() {
|
|||
<div key={entry.id} style={{ backgroundColor: '#fff', borderRadius: 8, padding: 24, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', border: '1px solid #F0F0F0' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||||
<h3 style={{ fontSize: 15, fontWeight: 600, margin: 0, color: 'rgba(0,0,0,0.85)' }}>
|
||||
{batchMode ? `板卡 #${idx + 1}` : '板卡信息'}
|
||||
{batchMode ? `物料 #${idx + 1}` : '物料信息'}
|
||||
</h3>
|
||||
{batchMode && entries.length > 1 && (
|
||||
<button onClick={() => removeEntry(entry.id)} style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '4px 12px', border: '1px solid #FFCCC7', borderRadius: 6, backgroundColor: '#FFF1F0', color: '#FF4D4F', cursor: 'pointer', fontSize: 13 }}>
|
||||
|
|
@ -146,7 +165,7 @@ export default function BoardRegisterPage() {
|
|||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 20 }}>
|
||||
{/* 板卡类型 */}
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 6 }}><span style={{ color: '#FF4D4F' }}>*</span> 板卡类型</label>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 6 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料类型</label>
|
||||
<select value={entry.type} onChange={e => updateEntry(entry.id, 'type', e.target.value)} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
{typeOptions.map(t => <option key={t} value={t}>{t}</option>)}
|
||||
</select>
|
||||
|
|
@ -154,7 +173,7 @@ export default function BoardRegisterPage() {
|
|||
|
||||
{/* 板卡版本 */}
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 6 }}><span style={{ color: '#FF4D4F' }}>*</span> 板卡版本</label>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 6 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料版本</label>
|
||||
<select value={entry.version} onChange={e => updateEntry(entry.id, 'version', e.target.value)} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
{(versionsByType[entry.type] || []).map(m => <option key={m.version} value={m.version}>{m.version}</option>)}
|
||||
</select>
|
||||
|
|
@ -166,10 +185,15 @@ export default function BoardRegisterPage() {
|
|||
<input value={entry.firmware} readOnly style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, backgroundColor: '#FAFAFA', color: 'rgba(0,0,0,0.65)', boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
|
||||
{/* 板卡SN */}
|
||||
{/* 物料SN */}
|
||||
<div>
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 6 }}><span style={{ color: '#FF4D4F' }}>*</span> 板卡SN号</label>
|
||||
<input value={entry.sn} onChange={e => updateEntry(entry.id, 'sn', e.target.value)} placeholder={`如 ${entry.version}-20250401001`} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 6 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料SN号</label>
|
||||
<div style={{ display: 'flex', gap: 6 }}>
|
||||
<input value={entry.sn} onChange={e => updateEntry(entry.id, 'sn', e.target.value)} placeholder={`扫码或手动输入 如 ${entry.version}-20250401001`} style={{ flex: 1, padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
<button title="扫码录入SN" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 36, height: 36, border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', flexShrink: 0 }}>
|
||||
<ScanLine size={16} style={{ color: '#4a7c59' }} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 生产日期 */}
|
||||
|
|
@ -230,7 +254,7 @@ export default function BoardRegisterPage() {
|
|||
{/* Add More Button (batch mode) */}
|
||||
{batchMode && (
|
||||
<button onClick={addEntry} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, width: '100%', padding: '12px 0', border: '2px dashed #D9D9D9', borderRadius: 8, backgroundColor: '#FAFAFA', cursor: 'pointer', fontSize: 14, color: 'rgba(0,0,0,0.45)', marginBottom: 16 }}>
|
||||
<Plus size={16} />添加一条板卡
|
||||
<Plus size={16} />添加一条物料
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
|
@ -284,14 +308,14 @@ export default function BoardRegisterPage() {
|
|||
{/* Sticky Bottom Bar */}
|
||||
<div style={{ position: 'sticky', bottom: 0, backgroundColor: '#fff', borderTop: '1px solid #F0F0F0', padding: '12px 24px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', zIndex: 10 }}>
|
||||
<span style={{ fontSize: 13, color: 'rgba(0,0,0,0.45)' }}>
|
||||
共 {entries.length} 条板卡待登记
|
||||
共 {entries.length} 条物料待登记
|
||||
{entries.some(e => e.type === '采集板') && <span style={{ color: '#FAAD14' }}> · 含采集板需后续校准</span>}
|
||||
{entries.some(e => e.type === '采集板' && e.calibFile) && <span style={{ color: '#4a7c59' }}> · {entries.filter(e => e.calibFile).length} 个已附校准文件</span>}
|
||||
</span>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button onClick={() => router.push('/board-cards')} style={{ padding: '8px 24px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>取消</button>
|
||||
<button onClick={() => router.push('/materials')} style={{ padding: '8px 24px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>取消</button>
|
||||
<button
|
||||
onClick={() => router.push('/board-cards')}
|
||||
onClick={() => router.push('/materials')}
|
||||
disabled={!isValid}
|
||||
style={{ padding: '8px 24px', border: 'none', borderRadius: 6, backgroundColor: isValid ? '#4a7c59' : '#D9D9D9', color: '#fff', cursor: isValid ? 'pointer' : 'not-allowed', fontSize: 14 }}
|
||||
>
|
||||
|
|
@ -1,41 +1,22 @@
|
|||
'use client'
|
||||
import { useState, Suspense } from 'react'
|
||||
import { useState, Suspense, useEffect, useCallback } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { ArrowLeft, Plus, Trash2, X, Info, CheckCircle } from 'lucide-react'
|
||||
|
||||
interface BomItem {
|
||||
id: number
|
||||
name: string
|
||||
material_name: string
|
||||
model: string
|
||||
versions: string[]
|
||||
qty: number
|
||||
required: boolean
|
||||
needCalibration: boolean
|
||||
enforceVersionMatch: boolean
|
||||
required: boolean | number
|
||||
need_calibration: boolean | number
|
||||
enforce_version_match: boolean | number
|
||||
model_code: string
|
||||
}
|
||||
|
||||
const modelBomData: Record<string, BomItem[]> = {
|
||||
GD30: [
|
||||
{ id: 1, name: '主协板', model: 'MCB-3000', versions: ['MB-V2.1', 'MB-V1.8'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
{ id: 2, name: '采集板', model: 'ACB-6000', versions: ['RX-V2.3', 'RX-V1.3'], qty: 2, required: true, needCalibration: true, enforceVersionMatch: true },
|
||||
{ id: 3, name: '发射板', model: 'TXB-1000', versions: ['TX-V2.1', 'TX-V1.5'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
{ id: 4, name: '升压板', model: 'BST-500', versions: ['BP600-V1.2'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
{ id: 5, name: '外壳机箱', model: 'GD30-CASE-A', versions: ['-'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
],
|
||||
GD20: [
|
||||
{ id: 1, name: '主协板', model: 'MCB-2000', versions: ['MB-V1.8', 'MB-V1.2'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
{ id: 2, name: '采集板', model: 'ACB-5000', versions: ['RX-V2.1', 'RX-V1.3'], qty: 1, required: true, needCalibration: true, enforceVersionMatch: false },
|
||||
{ id: 3, name: '发射板', model: 'TXB-800', versions: ['TX-V1.5'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
{ id: 4, name: '外壳机箱', model: 'GD20-CASE-A', versions: ['-'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
],
|
||||
GD10: [
|
||||
{ id: 1, name: '主协板', model: 'MCB-1000', versions: ['MB-V1.2'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
{ id: 2, name: '采集板', model: 'ACB-3000', versions: ['RX-V1.3'], qty: 1, required: true, needCalibration: true, enforceVersionMatch: false },
|
||||
{ id: 3, name: '外壳机箱', model: 'GD10-CASE-A', versions: ['-'], qty: 1, required: true, needCalibration: false, enforceVersionMatch: false },
|
||||
],
|
||||
}
|
||||
|
||||
const modelNames: Record<string, string> = { GD30: 'GD-30 Supreme', GD20: 'GD-20 Supreme', GD10: 'GD-10 Supreme' }
|
||||
const modelNames: Record<string, string> = { GD30: 'GD-30 Supreme', GD20: 'GD-20 Supreme', GD10: 'GD-10 Supreme', GM10: 'GM-10 大地电磁仪', GT10: 'GT-10 瞬变电磁仪', GP10: 'GP-10 磁力仪' }
|
||||
|
||||
export default function BomPage() {
|
||||
return (
|
||||
|
|
@ -51,30 +32,37 @@ function BomContent() {
|
|||
const modelCode = searchParams.get('model') || 'GD30'
|
||||
const modelName = modelNames[modelCode] || modelCode
|
||||
|
||||
const [bomList, setBomList] = useState<BomItem[]>(modelBomData[modelCode] || [])
|
||||
const [bomList, setBomList] = useState<BomItem[]>([])
|
||||
const [addDrawer, setAddDrawer] = useState(false)
|
||||
const [addForm, setAddForm] = useState({ name: '', model: '', versions: '', qty: 1, required: true, needCalibration: false, enforceVersionMatch: false })
|
||||
const [addForm, setAddForm] = useState({ name: '', materialName: '', model: '', versions: '', qty: 1, required: true, needCalibration: false, enforceVersionMatch: false })
|
||||
|
||||
const removeBomItem = (id: number) => setBomList(prev => prev.filter(b => b.id !== id))
|
||||
const fetchBom = useCallback(() => {
|
||||
fetch(`/api/models/bom?model=${modelCode}`).then(r => r.json()).then(d => setBomList(d))
|
||||
}, [modelCode])
|
||||
|
||||
const handleAdd = () => {
|
||||
const newItem: BomItem = {
|
||||
id: Date.now(),
|
||||
name: addForm.name,
|
||||
model: addForm.model,
|
||||
versions: addForm.versions.split(',').map(v => v.trim()).filter(Boolean),
|
||||
qty: addForm.qty,
|
||||
required: addForm.required,
|
||||
needCalibration: addForm.needCalibration,
|
||||
enforceVersionMatch: addForm.enforceVersionMatch,
|
||||
}
|
||||
setBomList(prev => [...prev, newItem])
|
||||
useEffect(() => { fetchBom() }, [fetchBom])
|
||||
|
||||
const removeBomItem = async (id: number) => {
|
||||
await fetch(`/api/models/bom?id=${id}`, { method: 'DELETE' })
|
||||
fetchBom()
|
||||
}
|
||||
|
||||
const handleAdd = async () => {
|
||||
await fetch('/api/models/bom', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model_code: modelCode, name: addForm.name, material_name: addForm.materialName, model: addForm.model,
|
||||
versions: addForm.versions.split(',').map(v => v.trim()).filter(Boolean),
|
||||
qty: addForm.qty, required: addForm.required, need_calibration: addForm.needCalibration, enforce_version_match: addForm.enforceVersionMatch,
|
||||
})
|
||||
})
|
||||
fetchBom()
|
||||
setAddDrawer(false)
|
||||
setAddForm({ name: '', model: '', versions: '', qty: 1, required: true, needCalibration: false, enforceVersionMatch: false })
|
||||
setAddForm({ name: '', materialName: '', model: '', versions: '', qty: 1, required: true, needCalibration: false, enforceVersionMatch: false })
|
||||
}
|
||||
|
||||
// 是否有多块采集板需要版本一致
|
||||
const hasMultiAcqBoards = bomList.some(b => b.name === '采集板' && b.qty >= 2 && b.enforceVersionMatch)
|
||||
const hasMultiAcqBoards = bomList.some(b => b.name === '采集板' && b.qty >= 2 && b.enforce_version_match)
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
|
|
@ -118,7 +106,7 @@ function BomContent() {
|
|||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#FAFAFA' }}>
|
||||
{['物料名称', '型号', '兼容版本', '数量', '必需', '版本约束', '需校准', '操作'].map(h => (
|
||||
{['物料名称', '物料分类', '物料类型', '兼容版本', '数量', '必需', '版本约束', '需校准', '操作'].map(h => (
|
||||
<th key={h} style={{ padding: '12px 16px', textAlign: 'left', fontSize: 13, fontWeight: 600, color: 'rgba(0,0,0,0.85)', borderBottom: '1px solid #F0F0F0' }}>{h}</th>
|
||||
))}
|
||||
</tr>
|
||||
|
|
@ -126,7 +114,8 @@ function BomContent() {
|
|||
<tbody>
|
||||
{bomList.map(item => (
|
||||
<tr key={item.id} style={{ borderBottom: '1px solid #F0F0F0' }}>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, fontWeight: 500 }}>{item.name}</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, fontWeight: 500 }}>{item.material_name || item.name}</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, color: 'rgba(0,0,0,0.65)' }}>{item.name}</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, color: 'rgba(0,0,0,0.65)' }}>{item.model}</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
|
|
@ -140,14 +129,14 @@ function BomContent() {
|
|||
{item.required ? <span style={{ fontSize: 12, color: '#FF4D4F' }}>必需</span> : <span style={{ fontSize: 12, color: 'rgba(0,0,0,0.25)' }}>可选</span>}
|
||||
</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
{item.qty >= 2 && item.enforceVersionMatch ? (
|
||||
{item.qty >= 2 && item.enforce_version_match ? (
|
||||
<span style={{ padding: '2px 8px', borderRadius: 4, fontSize: 11, backgroundColor: '#F0F5FF', color: '#597EF7', border: '1px solid #ADC6FF' }}>版本须一致</span>
|
||||
) : (
|
||||
<span style={{ fontSize: 12, color: 'rgba(0,0,0,0.25)' }}>-</span>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
{item.needCalibration ? <span style={{ padding: '2px 8px', borderRadius: 4, fontSize: 11, backgroundColor: '#FFFBE6', color: '#FAAD14', border: '1px solid #FFE58F' }}>需校准</span> : <span style={{ fontSize: 12, color: 'rgba(0,0,0,0.25)' }}>-</span>}
|
||||
{item.need_calibration ? <span style={{ padding: '2px 8px', borderRadius: 4, fontSize: 11, backgroundColor: '#FFFBE6', color: '#FAAD14', border: '1px solid #FFE58F' }}>需校准</span> : <span style={{ fontSize: 12, color: 'rgba(0,0,0,0.25)' }}>-</span>}
|
||||
</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<button onClick={() => removeBomItem(item.id)} style={{ color: '#FF4D4F', cursor: 'pointer', border: 'none', background: 'none', fontSize: 13, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
|
|
@ -171,14 +160,18 @@ function BomContent() {
|
|||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto', padding: 24 }}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料名称</label>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料分类</label>
|
||||
<select value={addForm.name} onChange={e => setAddForm({ ...addForm, name: e.target.value })} style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14 }}>
|
||||
<option value="">请选择</option>
|
||||
{['主协板', '采集板', '发射板', '升压板', '外壳机箱', '电池组', '线缆组件'].map(n => <option key={n} value={n}>{n}</option>)}
|
||||
{['主协板', '采集板', '发射板', '升压板', '外壳机箱', '电池组', '线缆组件', '电缆头', '电缆', '机箱', '电源'].map(n => <option key={n} value={n}>{n}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 型号</label>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料名称</label>
|
||||
<input value={addForm.materialName} onChange={e => setAddForm({ ...addForm, materialName: e.target.value })} placeholder="如 GD30 主协板" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<label style={{ display: 'block', fontSize: 14, fontWeight: 500, marginBottom: 8 }}><span style={{ color: '#FF4D4F' }}>*</span> 物料类型</label>
|
||||
<input value={addForm.model} onChange={e => setAddForm({ ...addForm, model: e.target.value })} placeholder="如 MCB-3000" style={{ width: '100%', padding: '8px 12px', border: '1px solid #D9D9D9', borderRadius: 6, fontSize: 14, boxSizing: 'border-box' }} />
|
||||
</div>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
|
|
|
|||
|
|
@ -2,45 +2,10 @@
|
|||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Plus, X, Info, GripVertical, Trash2, Edit, Key, FileCode, Cpu, ClipboardList } from 'lucide-react'
|
||||
import { useApi } from '@/lib/hooks'
|
||||
|
||||
const initialModelsData = [
|
||||
{ id: 1, name: 'GD-30 Supreme', code: 'GD30', status: '在产', description: '高端高密度电法仪', createDate: '2023-06-01' },
|
||||
{ id: 2, name: 'GD-20 Supreme', code: 'GD20', status: '在产', description: '中端高密度电法仪', createDate: '2023-08-15' },
|
||||
{ id: 3, name: 'GD-10 Supreme', code: 'GD10', status: '停产', description: '入门级高密度电法仪', createDate: '2022-03-10' },
|
||||
]
|
||||
|
||||
const checklistTemplates: Record<string, { id: number; name: string; required: boolean }[]> = {
|
||||
GD30: [
|
||||
{ id: 1, name: '主协板安装检查', required: true },
|
||||
{ id: 2, name: '采集板安装检查', required: true },
|
||||
{ id: 3, name: '发射板安装检查', required: true },
|
||||
{ id: 4, name: '升压板安装检查', required: true },
|
||||
{ id: 5, name: '线缆连接检查', required: true },
|
||||
{ id: 6, name: '整机通电测试', required: true },
|
||||
{ id: 7, name: '通信功能测试', required: true },
|
||||
{ id: 8, name: '采集通道校准', required: true },
|
||||
{ id: 9, name: '外观检查', required: false },
|
||||
{ id: 10, name: '包装检查', required: false },
|
||||
],
|
||||
GD20: [
|
||||
{ id: 1, name: '主协板安装检查', required: true },
|
||||
{ id: 2, name: '采集板安装检查', required: true },
|
||||
{ id: 3, name: '发射板安装检查', required: true },
|
||||
{ id: 4, name: '线缆连接检查', required: true },
|
||||
{ id: 5, name: '整机通电测试', required: true },
|
||||
{ id: 6, name: '通信功能测试', required: true },
|
||||
{ id: 7, name: '采集通道校准', required: true },
|
||||
{ id: 8, name: '外观检查', required: false },
|
||||
],
|
||||
GD10: [
|
||||
{ id: 1, name: '主协板安装检查', required: true },
|
||||
{ id: 2, name: '采集板安装检查', required: true },
|
||||
{ id: 3, name: '线缆连接检查', required: true },
|
||||
{ id: 4, name: '整机通电测试', required: true },
|
||||
{ id: 5, name: '通信功能测试', required: true },
|
||||
{ id: 6, name: '外观检查', required: false },
|
||||
],
|
||||
}
|
||||
interface ModelRow { id: number; name: string; code: string; status: string; description: string; create_date: string }
|
||||
interface CLItem { id: number; name: string; required: number; model_code: string; sort_order: number }
|
||||
|
||||
function getStatusStyle(status: string) {
|
||||
switch (status) {
|
||||
|
|
@ -52,25 +17,27 @@ function getStatusStyle(status: string) {
|
|||
|
||||
export default function ModelsPage() {
|
||||
const router = useRouter()
|
||||
const [modelsData, setModelsData] = useState(initialModelsData)
|
||||
const { data: modelsData, refetch: refetchModels } = useApi<ModelRow[]>('/api/models', [])
|
||||
const { data: checklistTemplates } = useApi<Record<string, CLItem[]>>('/api/models/checklist', {})
|
||||
const [modelDrawer, setModelDrawer] = useState(false)
|
||||
const [checklistDrawer, setChecklistDrawer] = useState(false)
|
||||
const [checklistTab, setChecklistTab] = useState('GD30')
|
||||
const [modelForm, setModelForm] = useState({ name: '', code: '', description:'',status: '在产' })
|
||||
const [checklistForm, setChecklistForm] = useState({ model: 'GD30', items: [{ name: '', required: true }] })
|
||||
const [editDrawer, setEditDrawer] = useState(false)
|
||||
const [editingModel, setEditingModel] = useState<typeof initialModelsData[0] | null>(null)
|
||||
const [editingModel, setEditingModel] = useState<ModelRow | null>(null)
|
||||
const [editStatus, setEditStatus] = useState('')
|
||||
|
||||
const handleEdit = (model: typeof initialModelsData[0]) => {
|
||||
const handleEdit = (model: ModelRow) => {
|
||||
setEditingModel(model)
|
||||
setEditStatus(model.status)
|
||||
setEditDrawer(true)
|
||||
}
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
const handleSaveEdit = async () => {
|
||||
if (editingModel) {
|
||||
setModelsData(prev => prev.map(m => m.id === editingModel.id ? { ...m, status: editStatus } : m))
|
||||
await fetch('/api/models', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: editingModel.id, status: editStatus }) })
|
||||
refetchModels()
|
||||
setEditDrawer(false)
|
||||
setEditingModel(null)
|
||||
}
|
||||
|
|
@ -130,7 +97,7 @@ export default function ModelsPage() {
|
|||
<td style={{ padding: '12px 16px' }}>
|
||||
<span style={{ ...getStatusStyle(model.status), padding: '2px 8px', borderRadius: 4, fontSize: 12 }}>{model.status}</span>
|
||||
</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, color: 'rgba(0,0,0,0.65)' }}>{model.createDate}</td>
|
||||
<td style={{ padding: '12px 16px', fontSize: 14, color: 'rgba(0,0,0,0.65)' }}>{model.create_date}</td>
|
||||
<td style={{ padding: '12px 16px' }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<button
|
||||
|
|
@ -207,7 +174,7 @@ export default function ModelsPage() {
|
|||
</td>
|
||||
<td style={{ padding: '10px 16px', fontSize: 14 }}>{item.name}</td>
|
||||
<td style={{ padding: '10px 16px' }}>
|
||||
{item.required && <span style={{ fontSize: 12, color: '#FF4D4F' }}>必填</span>}
|
||||
{item.required ? <span style={{ fontSize: 12, color: '#FF4D4F' }}>必填</span> : null}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
@ -285,7 +252,7 @@ export default function ModelsPage() {
|
|||
</div>
|
||||
<div style={{ padding: '16px 24px', borderTop: '1px solid #F0F0F0', display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||
<button onClick={() => setModelDrawer(false)} style={{ padding: '8px 20px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>取消</button>
|
||||
<button onClick={() => setModelDrawer(false)} style={{ padding: '8px 20px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}>保存</button>
|
||||
<button onClick={async () => { await fetch('/api/models', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(modelForm) }); refetchModels(); setModelDrawer(false) }} style={{ padding: '8px 20px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}>保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,46 +3,24 @@ import { useState } from 'react'
|
|||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Plus, BarChart3, Search, RotateCcw, X, Eye, Settings, ChevronLeft, ChevronRight, AlertTriangle } from 'lucide-react'
|
||||
import { useApi } from '@/lib/hooks'
|
||||
|
||||
interface RepairOrder {
|
||||
id: string
|
||||
sn: string
|
||||
fault_type: string
|
||||
status: string
|
||||
priority: string
|
||||
assignee: string
|
||||
create_date: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const statusOptions = ['全部', '待处理', '处理中', '已处理']
|
||||
const priorityOptions = ['全部', '高', '中', '低']
|
||||
const assigneeOptions = ['全部', '张工', '李工', '王工', '赵工']
|
||||
const faultTypeOptions = ['板卡故障', '固件异常', '通信故障', '电源故障', '传感器故障', '其他']
|
||||
|
||||
const ordersData = [
|
||||
{ id: 'WO-2024-0001', sn: 'GD30-2024-000056', faultType: '板卡故障', status: '处理中', priority: '高', assignee: '张工', createDate: '2024-03-15', description: '主协板通信异常,无法正常采集数据' },
|
||||
{ id: 'WO-2024-0002', sn: 'GD30-2024-000078', faultType: '固件异常', status: '待处理', priority: '中', assignee: '李工', createDate: '2024-03-14', description: '固件升级后设备无法启动' },
|
||||
{ id: 'WO-2024-0003', sn: 'GT20-2025-000045', faultType: '通信故障', status: '已处理', priority: '低', assignee: '王工', createDate: '2024-03-13', description: 'WiFi模块连接不稳定' },
|
||||
{ id: 'WO-2024-0004', sn: 'GD30-2024-000102', faultType: '电源故障', status: '处理中', priority: '高', assignee: '赵工', createDate: '2024-03-12', description: '升压板输出电压不稳定' },
|
||||
{ id: 'WO-2024-0005', sn: 'GD10-2024-000033', faultType: '传感器故障', status: '待处理', priority: '中', assignee: '张工', createDate: '2024-03-11', description: '温度传感器读数异常' },
|
||||
{ id: 'WO-2024-0006', sn: 'GD30-2024-000089', faultType: '板卡故障', status: '已处理', priority: '低', assignee: '李工', createDate: '2024-03-10', description: '采集板通道3数据丢失' },
|
||||
{ id: 'WO-2024-0007', sn: 'GT20-2025-000012', faultType: '其他', status: '待处理', priority: '高', assignee: '王工', createDate: '2024-03-09', description: '设备外壳损坏,需更换' },
|
||||
{ id: 'WO-2024-0008', sn: 'GD30-2024-000145', faultType: '固件异常', status: '处理中', priority: '中', assignee: '赵工', createDate: '2024-03-08', description: '配置文件丢失,参数重置' },
|
||||
]
|
||||
|
||||
const deviceInfoMap: Record<string, { model: string; type: string; firmware: string; location: string }> = {
|
||||
'GD30-2024-000056': { model: 'GD-30 Supreme', type: '高密度电法仪', firmware: 'v2.3.4', location: '生产车间A' },
|
||||
'GD30-2024-000078': { model: 'GD-30 Supreme', type: '高密度电法仪', firmware: 'v2.3.5', location: '测试实验室' },
|
||||
'GT20-2025-000045': { model: 'GT-20', type: '接地电阻测试仪', firmware: 'v1.2.0', location: '仓库B' },
|
||||
'GD30-2024-000102': { model: 'GD-30 Supreme', type: '高密度电法仪', firmware: 'v2.3.4', location: '生产车间B' },
|
||||
'GD10-2024-000033': { model: 'GD-10', type: '电法仪', firmware: 'v1.5.2', location: '测试实验室' },
|
||||
'GD30-2024-000089': { model: 'GD-30 Supreme', type: '高密度电法仪', firmware: 'v2.3.3', location: '仓库A' },
|
||||
'GT20-2025-000012': { model: 'GT-20', type: '接地电阻测试仪', firmware: 'v1.1.8', location: '客户现场' },
|
||||
'GD30-2024-000145': { model: 'GD-30 Supreme', type: '高密度电法仪', firmware: 'v2.3.4', location: '生产车间A' },
|
||||
}
|
||||
|
||||
const processRecords = [
|
||||
{ date: '2024-03-15 10:30', operator: '张工', action: '创建工单', note: '客户反馈设备异常' },
|
||||
{ date: '2024-03-15 14:00', operator: '张工', action: '开始处理', note: '初步检测为主协板故障' },
|
||||
{ date: '2024-03-16 09:00', operator: '张工', action: '更换板卡', note: '更换主协板MCB-3000,新SN: MCB-2024-0056' },
|
||||
]
|
||||
|
||||
const boardReplacementRecords = [
|
||||
{ oldBoard: 'MCB-2024-0034', newBoard: 'MCB-2024-0056', type: '主协板', model: 'MCB-3000', date: '2024-03-16', operator: '张工' },
|
||||
]
|
||||
|
||||
const snOptions = Object.keys(deviceInfoMap)
|
||||
|
||||
function getStatusStyle(status: string) {
|
||||
switch (status) {
|
||||
case '处理中': return { backgroundColor: '#eef5f0', color: '#4a7c59', border: '1px solid #a3c4ad' }
|
||||
|
|
@ -72,6 +50,20 @@ function getPriorityStyle(priority: string) {
|
|||
|
||||
export default function RepairPage() {
|
||||
const router = useRouter()
|
||||
const { data: ordersRaw, loading, refetch } = useApi<RepairOrder[]>('/api/repair', [])
|
||||
const { data: devicesData } = useApi<{ sn: string; model: string; type: string; firmware: string }[]>('/api/devices', [])
|
||||
|
||||
// Map DB fields to display fields
|
||||
const ordersData = ordersRaw.map(o => ({
|
||||
id: o.id, sn: o.sn, faultType: o.fault_type, status: o.status,
|
||||
priority: o.priority, assignee: o.assignee, createDate: o.create_date, description: o.description,
|
||||
}))
|
||||
|
||||
// Build device info map from devices API
|
||||
const deviceInfoMap: Record<string, { model: string; type: string; firmware: string }> = {}
|
||||
devicesData.forEach(d => { deviceInfoMap[d.sn] = { model: d.model, type: d.type, firmware: d.firmware } })
|
||||
const snOptions = devicesData.map(d => d.sn)
|
||||
|
||||
const [filterStatus, setFilterStatus] = useState('全部')
|
||||
const [filterPriority, setFilterPriority] = useState('全部')
|
||||
const [filterAssignee, setFilterAssignee] = useState('全部')
|
||||
|
|
@ -103,6 +95,8 @@ export default function RepairPage() {
|
|||
const [processNote, setProcessNote] = useState('')
|
||||
const [processScrap, setProcessScrap] = useState(false)
|
||||
|
||||
if (loading) return <div style={{ padding: 24 }}>加载中...</div>
|
||||
|
||||
const pageSize = 5
|
||||
const filteredOrders = ordersData.filter(o => {
|
||||
if (filterStatus !== '全部' && o.status !== filterStatus) return false
|
||||
|
|
@ -264,7 +258,6 @@ export default function RepairPage() {
|
|||
<div style={{ padding: 12, backgroundColor: '#FAFAFA', borderRadius: 6, marginBottom: 16, fontSize: 13, color: 'rgba(0,0,0,0.65)' }}>
|
||||
<div>型号:{selectedDeviceInfo.model} ({selectedDeviceInfo.type})</div>
|
||||
<div>固件:{selectedDeviceInfo.firmware}</div>
|
||||
<div>位置:{selectedDeviceInfo.location}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -314,7 +307,12 @@ export default function RepairPage() {
|
|||
</div>
|
||||
<div style={{ padding: '16px 24px', borderTop: '1px solid #F0F0F0', display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||
<button onClick={() => setNewDrawer(false)} style={{ padding: '8px 20px', border: '1px solid #D9D9D9', borderRadius: 6, backgroundColor: '#fff', cursor: 'pointer', fontSize: 14 }}>取消</button>
|
||||
<button onClick={() => setNewDrawer(false)} style={{ padding: '8px 20px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}>创建工单</button>
|
||||
<button onClick={async () => {
|
||||
if (!newSN) return
|
||||
const orderId = `WO-${new Date().getFullYear()}-${String(ordersData.length + 1).padStart(4, '0')}`
|
||||
await fetch('/api/repair', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: orderId, sn: newSN, fault_type: newFaultType, status: '待处理', priority: newPriority, assignee: newAssignee, create_date: new Date().toISOString().slice(0, 10), description: newDescription }) })
|
||||
refetch(); setNewDrawer(false); setNewSN(''); setNewDescription(''); setNewPhenomenon('')
|
||||
}} style={{ padding: '8px 20px', border: 'none', borderRadius: 6, backgroundColor: '#4a7c59', color: '#fff', cursor: 'pointer', fontSize: 14 }}>创建工单</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,20 @@
|
|||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { Download, Search, X, Eye, CheckCircle, Clock, Package, Trash2, AlertTriangle, ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import { useApi } from '@/lib/hooks'
|
||||
|
||||
interface ScrapRecord {
|
||||
id: number
|
||||
sn: string
|
||||
model: string
|
||||
reason: string
|
||||
applicant: string
|
||||
status: string
|
||||
order_id: string
|
||||
date: string
|
||||
value: number
|
||||
materials: string[]
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{ label: '申请报废', desc: '维修工单发起' },
|
||||
|
|
@ -11,23 +25,6 @@ const steps = [
|
|||
{ label: '报废完成', desc: '设备注销' },
|
||||
]
|
||||
|
||||
const scrapData = [
|
||||
{ id: 1, sn: 'GD30-2023-001234', model: 'GD-30 Supreme', reason: '主板损坏无法修复', applicant: '李工', status: '待审批', orderId: 'WO-2024-0001', date: '2024-03-10', value: 12000, materials: ['采集板 AC20240308002', '测控板 CT20240308003', '升压板 BS20240308001'] },
|
||||
{ id: 2, sn: 'GD30-2023-001567', model: 'GD-30 Supreme', reason: '多个核心部件损坏', applicant: '张工', status: '审批中', orderId: 'WO-2024-0003', date: '2024-03-08', value: 8500, materials: ['采集板 AC20240215006', '发射板 TX20240215003'] },
|
||||
{ id: 3, sn: 'GT20-2023-000890', model: 'GD-20 Supreme', reason: '维修成本超过设备价值', applicant: '王工', status: '已审批', orderId: 'WO-2024-0005', date: '2024-02-28', value: 5200, materials: ['主协板 MC20231205004'] },
|
||||
{ id: 4, sn: 'GD10-2023-000456', model: 'GD-10 Supreme', reason: '设备老化严重', applicant: '赵工', status: '已驳回', orderId: 'WO-2024-0006', date: '2024-02-20', value: 3000, materials: [] },
|
||||
{ id: 5, sn: 'GD30-2023-002345', model: 'GD-30 Supreme', reason: '主板损坏无法修复', applicant: '李工', status: '回收中', orderId: 'WO-2024-0008', date: '2024-02-15', value: 15000, materials: ['采集板 AC20231110010', '采集板 AC20231110011', '升压板 BS20231110002'] },
|
||||
{ id: 6, sn: 'GT20-2023-000123', model: 'GD-20 Supreme', reason: '多个核心部件损坏', applicant: '张工', status: '已回收', orderId: 'WO-2024-0010', date: '2024-01-25', value: 6800, materials: ['采集板 AC20230801007', '发射板 TX20230801002'] },
|
||||
{ id: 7, sn: 'GD30-2023-003456', model: 'GD-30 Supreme', reason: '维修成本超过设备价值', applicant: '王工', status: '已回收', orderId: 'WO-2024-0012', date: '2024-01-10', value: 9200, materials: ['主协板 MC20230610003', '采集板 AC20230610008'] },
|
||||
]
|
||||
|
||||
const statsCards = [
|
||||
{ label: '报废总数', value: 7, color: 'rgba(0,0,0,0.85)' },
|
||||
{ label: '待审批', value: 1, color: '#FAAD14' },
|
||||
{ label: '已审批待回收', value: 1, color: '#4a7c59' },
|
||||
{ label: '已回收', value: 2, color: '#52C41A' },
|
||||
]
|
||||
|
||||
const statusOptions = ['全部', '待审批', '审批中', '已审批', '已驳回', '回收中', '已回收']
|
||||
|
||||
function getStatusStyle(status: string) {
|
||||
|
|
@ -42,12 +39,23 @@ function getStatusStyle(status: string) {
|
|||
}
|
||||
}
|
||||
|
||||
const approvalTimeline = [
|
||||
{ date: '2024-03-10 09:30', action: '提交报废申请', operator: '李工', note: '维修工单WO-2024-0001发起报废' },
|
||||
{ date: '2024-03-10 14:00', action: '主管审批中', operator: '系统', note: '等待主管审核' },
|
||||
]
|
||||
|
||||
export default function ScrapPage() {
|
||||
const { data: scrapRaw, loading } = useApi<ScrapRecord[]>('/api/scrap', [])
|
||||
|
||||
// Map DB fields to display fields
|
||||
const scrapData = scrapRaw.map(r => ({
|
||||
id: r.id, sn: r.sn, model: r.model, reason: r.reason, applicant: r.applicant,
|
||||
status: r.status, orderId: r.order_id, date: r.date, value: r.value, materials: r.materials,
|
||||
}))
|
||||
|
||||
// Dynamic stats
|
||||
const statsCards = [
|
||||
{ label: '报废总数', value: scrapData.length, color: 'rgba(0,0,0,0.85)' },
|
||||
{ label: '待审批', value: scrapData.filter(r => r.status === '待审批').length, color: '#FAAD14' },
|
||||
{ label: '已审批待回收', value: scrapData.filter(r => r.status === '已审批').length, color: '#4a7c59' },
|
||||
{ label: '已回收', value: scrapData.filter(r => r.status === '已回收').length, color: '#52C41A' },
|
||||
]
|
||||
|
||||
const [filterSN, setFilterSN] = useState('')
|
||||
const [filterStatus, setFilterStatus] = useState('全部')
|
||||
const [filterDate, setFilterDate] = useState('')
|
||||
|
|
@ -60,6 +68,8 @@ export default function ScrapPage() {
|
|||
const [checkedMaterials, setCheckedMaterials] = useState<string[]>([])
|
||||
const pageSize = 5
|
||||
|
||||
if (loading) return <div style={{ padding: 24 }}>加载中...</div>
|
||||
|
||||
const filtered = scrapData.filter(r => {
|
||||
if (filterSN && !r.sn.toLowerCase().includes(filterSN.toLowerCase())) return false
|
||||
if (filterStatus !== '全部' && r.status !== filterStatus) return false
|
||||
|
|
@ -233,16 +243,14 @@ export default function ScrapPage() {
|
|||
{/* 审批记录 */}
|
||||
<div style={{ padding: 16, backgroundColor: '#FAFAFA', borderRadius: 8, border: '1px solid #F0F0F0' }}>
|
||||
<h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 12 }}>审批记录</h4>
|
||||
{approvalTimeline.map((r, i) => (
|
||||
<div key={i} style={{ display: 'flex', gap: 12, paddingBottom: 12, marginBottom: 12, borderBottom: i < approvalTimeline.length - 1 ? '1px solid #F0F0F0' : 'none' }}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: '50%', backgroundColor: '#4a7c59', marginTop: 6, flexShrink: 0 }} />
|
||||
<div style={{ fontSize: 13 }}>
|
||||
<div style={{ fontWeight: 500, marginBottom: 4 }}>{r.action}</div>
|
||||
<div style={{ color: 'rgba(0,0,0,0.45)' }}>{r.note}</div>
|
||||
<div style={{ color: 'rgba(0,0,0,0.35)', marginTop: 4 }}>{r.date} · {r.operator}</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 12, paddingBottom: 12 }}>
|
||||
<div style={{ width: 8, height: 8, borderRadius: '50%', backgroundColor: '#4a7c59', marginTop: 6, flexShrink: 0 }} />
|
||||
<div style={{ fontSize: 13 }}>
|
||||
<div style={{ fontWeight: 500, marginBottom: 4 }}>提交报废申请</div>
|
||||
<div style={{ color: 'rgba(0,0,0,0.45)' }}>维修工单{detailDrawer.orderId}发起报废</div>
|
||||
<div style={{ color: 'rgba(0,0,0,0.35)', marginTop: 4 }}>{detailDrawer.date} · {detailDrawer.applicant}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '16px 24px', borderTop: '1px solid #F0F0F0', display: 'flex', justifyContent: 'flex-end', gap: 12 }}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
import Database from 'better-sqlite3'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
let db: Database.Database | null = null
|
||||
|
||||
export function getDb(): Database.Database {
|
||||
if (db) return db
|
||||
const dataDir = path.join(process.cwd(), 'data')
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
|
||||
const dbPath = path.join(dataDir, 'app.db')
|
||||
db = new Database(dbPath)
|
||||
db.pragma('journal_mode = WAL')
|
||||
db.pragma('foreign_keys = ON')
|
||||
initTables(db)
|
||||
return db
|
||||
}
|
||||
|
||||
function initTables(db: Database.Database) {
|
||||
db.exec(`
|
||||
-- 设备型号
|
||||
CREATE TABLE IF NOT EXISTS device_models (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL DEFAULT '在产',
|
||||
description TEXT DEFAULT '',
|
||||
create_date TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- 装配 Checklist 模板
|
||||
CREATE TABLE IF NOT EXISTS checklist_templates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
model_code TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
required INTEGER NOT NULL DEFAULT 1,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (model_code) REFERENCES device_models(code)
|
||||
);
|
||||
|
||||
-- 板卡类型
|
||||
CREATE TABLE IF NOT EXISTS board_types (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
device_models TEXT NOT NULL DEFAULT '[]',
|
||||
description TEXT DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT '启用'
|
||||
);
|
||||
|
||||
-- 板卡版本
|
||||
CREATE TABLE IF NOT EXISTS board_versions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT '在产'
|
||||
);
|
||||
|
||||
-- 物料实例(板卡列表)
|
||||
CREATE TABLE IF NOT EXISTS materials (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sn TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
category TEXT NOT NULL DEFAULT '',
|
||||
type TEXT NOT NULL,
|
||||
device_model TEXT NOT NULL DEFAULT '',
|
||||
version TEXT NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
firmware TEXT DEFAULT '-',
|
||||
status TEXT NOT NULL DEFAULT '在库',
|
||||
device_sn TEXT DEFAULT '-',
|
||||
production_date TEXT NOT NULL,
|
||||
calib_status TEXT DEFAULT '-',
|
||||
calib_date TEXT DEFAULT '-'
|
||||
);
|
||||
|
||||
-- 设备
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sn TEXT NOT NULL UNIQUE,
|
||||
model TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT '装配中',
|
||||
firmware TEXT DEFAULT '',
|
||||
production_date TEXT NOT NULL,
|
||||
customer TEXT DEFAULT '-',
|
||||
batch TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
-- BOM 模板
|
||||
CREATE TABLE IF NOT EXISTS bom_templates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
model_code TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
material_name TEXT NOT NULL DEFAULT '',
|
||||
model TEXT NOT NULL,
|
||||
versions TEXT NOT NULL DEFAULT '[]',
|
||||
qty INTEGER NOT NULL DEFAULT 1,
|
||||
required INTEGER NOT NULL DEFAULT 1,
|
||||
need_calibration INTEGER NOT NULL DEFAULT 0,
|
||||
enforce_version_match INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (model_code) REFERENCES device_models(code)
|
||||
);
|
||||
|
||||
-- 固件
|
||||
CREATE TABLE IF NOT EXISTS firmware (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version TEXT NOT NULL,
|
||||
board_version TEXT DEFAULT '-',
|
||||
type TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT '草稿',
|
||||
size TEXT DEFAULT '',
|
||||
downloads INTEGER DEFAULT 0,
|
||||
hw_range TEXT DEFAULT '',
|
||||
upgrade_type TEXT DEFAULT '可选',
|
||||
signed INTEGER DEFAULT 0,
|
||||
md5 TEXT DEFAULT '',
|
||||
sha256 TEXT DEFAULT '',
|
||||
notes TEXT DEFAULT '[]',
|
||||
model_code TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
-- 配置文件
|
||||
CREATE TABLE IF NOT EXISTS config_files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
version TEXT NOT NULL,
|
||||
create_time TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT '生效',
|
||||
voltage TEXT DEFAULT '',
|
||||
current TEXT DEFAULT '',
|
||||
duty_cycle TEXT DEFAULT '',
|
||||
pulse_width TEXT DEFAULT '',
|
||||
iterations TEXT DEFAULT '',
|
||||
channels INTEGER DEFAULT 0,
|
||||
sample_rate TEXT DEFAULT '',
|
||||
voltage_range TEXT DEFAULT '',
|
||||
waveform TEXT DEFAULT '',
|
||||
ssid TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
-- 授权
|
||||
CREATE TABLE IF NOT EXISTS licenses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
model TEXT NOT NULL,
|
||||
modules TEXT NOT NULL,
|
||||
expiry TEXT DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT '生效'
|
||||
);
|
||||
|
||||
-- 维修工单
|
||||
CREATE TABLE IF NOT EXISTS repair_orders (
|
||||
id TEXT PRIMARY KEY,
|
||||
sn TEXT NOT NULL,
|
||||
fault_type TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT '待处理',
|
||||
priority TEXT NOT NULL DEFAULT '中',
|
||||
assignee TEXT DEFAULT '',
|
||||
create_date TEXT NOT NULL,
|
||||
description TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
-- 报废记录
|
||||
CREATE TABLE IF NOT EXISTS scrap_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sn TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
applicant TEXT DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT '待审批',
|
||||
order_id TEXT DEFAULT '',
|
||||
date TEXT NOT NULL,
|
||||
value INTEGER DEFAULT 0,
|
||||
materials TEXT DEFAULT '[]'
|
||||
);
|
||||
`)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
|
||||
export function useApi<T>(url: string, defaultValue: T): { data: T; loading: boolean; refetch: () => void } {
|
||||
const [data, setData] = useState<T>(defaultValue)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
setLoading(true)
|
||||
fetch(url)
|
||||
.then(r => r.json())
|
||||
.then(d => { setData(d); setLoading(false) })
|
||||
.catch(() => setLoading(false))
|
||||
}, [url])
|
||||
|
||||
useEffect(() => { refetch() }, [refetch])
|
||||
|
||||
return { data, loading, refetch }
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
import { getDb } from './db'
|
||||
|
||||
export function seedIfEmpty() {
|
||||
const db = getDb()
|
||||
const count = db.prepare('SELECT COUNT(*) as c FROM device_models').get() as { c: number }
|
||||
if (count.c > 0) return // already seeded
|
||||
|
||||
// Device Models
|
||||
const insertModel = db.prepare('INSERT INTO device_models (name, code, status, description, create_date) VALUES (?, ?, ?, ?, ?)')
|
||||
const models = [
|
||||
['GD-30 Supreme', 'GD30', '在产', '高端高密度电法仪', '2023-06-01'],
|
||||
['GD-20 Supreme', 'GD20', '在产', '中端高密度电法仪', '2023-08-15'],
|
||||
['GD-10 Supreme', 'GD10', '停产', '入门级高密度电法仪', '2022-03-10'],
|
||||
['GM-10', 'GM10', '在产', '大地电磁仪', '2025-01-10'],
|
||||
['GT-10', 'GT10', '在产', '瞬变电磁仪', '2025-02-15'],
|
||||
['GP-10', 'GP10', '在产', '磁力仪', '2025-03-01'],
|
||||
]
|
||||
for (const m of models) insertModel.run(...m)
|
||||
|
||||
// Checklist Templates
|
||||
const insertCL = db.prepare('INSERT INTO checklist_templates (model_code, name, required, sort_order) VALUES (?, ?, ?, ?)')
|
||||
const clGD30 = ['主协板安装检查','采集板安装检查','发射板安装检查','升压板安装检查','线缆连接检查','整机通电测试','通信功能测试','采集通道校准','外观检查','包装检查']
|
||||
clGD30.forEach((n, i) => insertCL.run('GD30', n, i < 8 ? 1 : 0, i))
|
||||
const clGD20 = ['主协板安装检查','采集板安装检查','发射板安装检查','线缆连接检查','整机通电测试','通信功能测试','采集通道校准','外观检查']
|
||||
clGD20.forEach((n, i) => insertCL.run('GD20', n, i < 7 ? 1 : 0, i))
|
||||
const clGD10 = ['主协板安装检查','采集板安装检查','线缆连接检查','整机通电测试','通信功能测试','外观检查']
|
||||
clGD10.forEach((n, i) => insertCL.run('GD10', n, i < 5 ? 1 : 0, i))
|
||||
const clGM10 = ['主协板安装检查','采集板安装检查','电磁传感器安装检查','线缆连接检查','整机通电测试','通信功能测试','采集通道校准','外观检查']
|
||||
clGM10.forEach((n, i) => insertCL.run('GM10', n, i < 7 ? 1 : 0, i))
|
||||
const clGT10 = ['主协板安装检查','采集板安装检查','发射板安装检查','瞬变电磁传感器检查','线缆连接检查','整机通电测试','通信功能测试','外观检查']
|
||||
clGT10.forEach((n, i) => insertCL.run('GT10', n, i < 7 ? 1 : 0, i))
|
||||
const clGP10 = ['主协板安装检查','磁力传感器安装检查','线缆连接检查','整机通电测试','通信功能测试','磁力校准','外观检查']
|
||||
clGP10.forEach((n, i) => insertCL.run('GP10', n, i < 6 ? 1 : 0, i))
|
||||
|
||||
// Board Types
|
||||
const insertBT = db.prepare('INSERT INTO board_types (name, category, device_models, description, status) VALUES (?, ?, ?, ?, ?)')
|
||||
const boardTypes = [
|
||||
['GD30 主协板', '主协板', '["GD-30 Supreme"]', 'GD-30 Supreme 专用主协板', '启用'],
|
||||
['GD30 采集板', '采集板', '["GD-30 Supreme"]', 'GD-30 Supreme 专用采集板,6通道', '启用'],
|
||||
['GD20 采集板', '采集板', '["GD-20 Supreme"]', 'GD-20 Supreme 专用采集板,5通道', '启用'],
|
||||
['GD30 发射板', '发射板', '["GD-30 Supreme"]', 'GD-30 Supreme 专用发射板', '启用'],
|
||||
['GD30 升压板', '升压板', '["GD-30 Supreme"]', 'GD-30 Supreme 专用升压板', '启用'],
|
||||
['GM10 采集板', '采集板', '["GM-10"]', '大地电磁仪 GM-10 专用采集板', '启用'],
|
||||
['GM10 主协板', '主协板', '["GM-10"]', '大地电磁仪 GM-10 专用主协板', '启用'],
|
||||
['GT10 采集板', '采集板', '["GT-10"]', '瞬变电磁仪 GT-10 专用采集板', '启用'],
|
||||
['GT10 发射板', '发射板', '["GT-10"]', '瞬变电磁仪 GT-10 专用发射板', '启用'],
|
||||
['GP10 主协板', '主协板', '["GP-10"]', '磁力仪 GP-10 专用主协板', '启用'],
|
||||
['通用电缆头 SR10/SR20', '电缆头', '["GD-30 Supreme","GD-20 Supreme","GD-10 Supreme"]', 'SR10/SR20 电缆头,适配 GD 系列', '启用'],
|
||||
['通用电缆头 CS60', '电缆头', '["GD-30 Supreme","GD-20 Supreme","GD-10 Supreme"]', 'CS60 电缆头,适配 GD 系列', '启用'],
|
||||
['通用电缆头 SR60/SR60PLUS', '电缆头', '["GD-30 Supreme","GD-20 Supreme","GD-10 Supreme"]', 'SR60/SR60PLUS 电缆头,适配 GD 系列', '启用'],
|
||||
['通用电缆', '电缆', '["GD-30 Supreme","GD-20 Supreme","GM-10","GT-10"]', '通用电缆', '启用'],
|
||||
['通用机箱', '机箱', '["GD-30 Supreme","GD-20 Supreme","GD-10 Supreme"]', '通用机箱外壳', '启用'],
|
||||
['BP150 电源', '电源', '["GD-10 Supreme"]', 'BP150 便携电源模块,适配入门级设备', '启用'],
|
||||
['BP300 电源', '电源', '["GD-20 Supreme","GT-10"]', 'BP300 中功率电源模块', '启用'],
|
||||
['BP600 电源', '电源', '["GD-30 Supreme","GM-10","GT-10","GP-10"]', 'BP600 大功率电源模块', '启用'],
|
||||
]
|
||||
for (const bt of boardTypes) insertBT.run(...bt)
|
||||
|
||||
// Board Versions
|
||||
const insertBV = db.prepare('INSERT INTO board_versions (type, version, status) VALUES (?, ?, ?)')
|
||||
const bvs = [
|
||||
['主协板', 'MB-V1.8', '在产'], ['采集板', 'RX-V2.3', '在产'], ['发射板', 'TX-V2.1', '停产'],
|
||||
['升压板', 'BP600-V1.2', '停产'], ['电缆头', 'SR10', '在产'], ['电缆头', 'SR20', '在产'], ['电缆头', 'CS60', '在产'], ['电缆头', 'SR60', '在产'], ['电缆头', 'SR60PLUS', '在产'], ['电缆', 'CBL-60M', '在产'],
|
||||
['机箱', 'GD30-CASE-B', '在产'], ['电源', 'BP150-V1.0', '在产'], ['电源', 'BP300-V1.0', '在产'], ['电源', 'BP600-V2.0', '在产'],
|
||||
]
|
||||
for (const bv of bvs) insertBV.run(...bv)
|
||||
|
||||
// Materials
|
||||
const insertMat = db.prepare('INSERT INTO materials (sn, name, category, type, device_model, version, description, firmware, status, device_sn, production_date, calib_status, calib_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
||||
const mats = [
|
||||
['MB25011500', 'GD30 主协板', '主协板', 'MCB-3000', 'GD-30 Supreme', 'MB-V2.1', 'GD-30 Supreme 专用主协板', 'v2.1', '在库', '-', '2025-01-15', '-', '-'],
|
||||
['MCB-3000-20250118002', 'GD30 主协板', '主协板', 'MCB-3000', 'GD-30 Supreme', 'MCB-3000', 'GD-30 Supreme 专用主协板', 'v2.1.0', '已装配', 'GD30-2025-000001', '2025-01-18', '-', '-'],
|
||||
['ACB-6000-20250110001', 'GD30 采集板', '采集板', 'ACB-6000', 'GD-30 Supreme', 'ACB-6000', 'GD-30 Supreme 专用采集板,6通道', 'v3.0.2', '已装配', 'GD30-2025-000001', '2025-01-10', '合格', '2025-01-12'],
|
||||
['ACB-6000-20250110002', 'GD30 采集板', '采集板', 'ACB-6000', 'GD-30 Supreme', 'ACB-6000', 'GD-30 Supreme 专用采集板,6通道', 'v3.0.2', '已装配', 'GD30-2025-000001', '2025-01-10', '合格', '2025-01-12'],
|
||||
['ACB-6000-20250112003', 'GD30 采集板', '采集板', 'ACB-6000', 'GD-30 Supreme', 'ACB-6000', 'GD-30 Supreme 专用采集板,6通道', 'v3.0.2', '在库', '-', '2025-01-12', '待校准', '-'],
|
||||
['ACB-5000-20241205001', 'GD20 采集板', '采集板', 'ACB-5000', 'GD-20 Supreme', 'ACB-5000', 'GD-20 Supreme 专用采集板,5通道', 'v2.5.1', '已装配', 'GD20-2024-000045', '2024-12-05', '合格', '2024-12-08'],
|
||||
['TXB-1000-20250120001', 'GD30 发射板', '发射板', 'TXB-1000', 'GD-30 Supreme', 'TXB-1000', 'GD-30 Supreme 专用发射板', 'v1.2.0', '已装配', 'GD30-2025-000001', '2025-01-20', '-', '-'],
|
||||
['TXB-1000-20250122002', 'GD30 发射板', '发射板', 'TXB-1000', 'GD-30 Supreme', 'TXB-1000', 'GD-30 Supreme 专用发射板', 'v1.2.0', '在库', '-', '2025-01-22', '-', '-'],
|
||||
['BST-500-20250201001', 'GD30 升压板', '升压板', 'BST-500', 'GD-30 Supreme', 'BST-500', 'GD-30 Supreme 专用升压板', '-', '已装配', 'GD30-2025-000002', '2025-02-01', '-', '-'],
|
||||
['BST-500-20250203002', 'GD30 升压板', '升压板', 'BST-500', 'GD-30 Supreme', 'BST-500', 'GD-30 Supreme 专用升压板', '-', '在库', '-', '2025-02-03', '-', '-'],
|
||||
['ACB-6000-20241120001', 'GD30 采集板', '采集板', 'ACB-6000', 'GD-30 Supreme', 'ACB-6000', 'GD-30 Supreme 专用采集板,6通道', 'v3.0.2', '故障', '-', '2024-11-20', '不合格', '2025-02-10'],
|
||||
['MCB-2000-20240915001', 'GD20 主协板', '主协板', 'MCB-2000', 'GD-20 Supreme', 'MCB-2000', 'GD-20 Supreme 专用主协板', 'v1.8.5', '已装配', 'GD20-2024-000046', '2024-09-15', '-', '-'],
|
||||
['ACB-6000-20250305001', 'GD30 采集板', '采集板', 'ACB-6000', 'GD-30 Supreme', 'ACB-6000', 'GD-30 Supreme 专用采集板,6通道', 'v3.0.2', '在库', '-', '2025-03-05', '待校准', '-'],
|
||||
['TXB-800-20240610001', 'GD20 发射板', '发射板', 'TXB-800', 'GD-20 Supreme', 'TXB-800', 'GD-20 Supreme 专用发射板', 'v1.0.3', '报废', '-', '2024-06-10', '-', '-'],
|
||||
['CBH-SR10-20250301001', '通用电缆头 SR10/SR20', '电缆头', 'SR10', 'GD-30 Supreme', 'SR10', 'SR10/SR20 电缆头,适配 GD 系列', '-', '在库', '-', '2025-03-01', '-', '-'],
|
||||
['CBL-010-20250305001', '通用电缆', '电缆', 'CBL-60M', 'GD-30 Supreme', 'CBL-60M', '通用电缆', '-', '已装配', 'GD30-2025-000001', '2025-03-05', '-', '-'],
|
||||
['CHS-GD30-20250310001', '通用机箱', '机箱', 'GD30-CASE-B', 'GD-30 Supreme', 'GD30-CASE-B', '通用机箱外壳', '-', '在库', '-', '2025-03-10', '-', '-'],
|
||||
['BP600-20250312001', 'BP600 电源', '电源', 'BP600-V2.0', 'GD-30 Supreme', 'BP600-V2.0', 'BP600 大功率电源模块', '-', '已装配', 'GD30-2025-000002', '2025-03-12', '-', '-'],
|
||||
['BP300-20250315001', 'BP300 电源', '电源', 'BP300-V1.0', 'GD-20 Supreme', 'BP300-V1.0', 'BP300 中功率电源模块', '-', '在库', '-', '2025-03-15', '-', '-'],
|
||||
['BP150-20250318001', 'BP150 电源', '电源', 'BP150-V1.0', 'GD-10 Supreme', 'BP150-V1.0', 'BP150 便携电源模块', '-', '在库', '-', '2025-03-18', '-', '-'],
|
||||
]
|
||||
for (const m of mats) insertMat.run(...m)
|
||||
|
||||
// Devices
|
||||
const insertDev = db.prepare('INSERT INTO devices (sn, model, type, status, firmware, production_date, customer, batch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
|
||||
const devs = [
|
||||
['GD30-2025-000001', 'GD-30 Supreme', '高密度电法仪', '已激活', 'v2.3.5', '2025-01-15 14:30', '北京地质研究院', 'BATCH-2025-Q1-001'],
|
||||
['GD30-2025-000002', 'GD-30 Supreme', '高密度电法仪', '已激活', 'v2.3.5', '2025-01-18 09:15', '中国地质大学', 'BATCH-2025-Q1-001'],
|
||||
['GD30-2024-000056', 'GD-30 Supreme', '高密度电法仪', '已出厂', 'v2.3.4', '2024-12-20 16:00', '成都理工大学', 'BATCH-2024-Q4-003'],
|
||||
['GT20-2025-000045', 'GD-20', '二维电法仪', '已激活', 'v1.8.5', '2025-02-10 11:20', '武汉地质调查中心', 'BATCH-2025-Q1-002'],
|
||||
['GT20-2025-000046', 'GD-20', '二维电法仪', '装配中', 'v1.8.5', '2025-03-01 08:45', '-', 'BATCH-2025-Q1-002'],
|
||||
['GD30-2024-000078', 'GD-30 Supreme', '高密度电法仪', '已出厂', 'v2.3.4', '2024-11-05 13:30', '长安大学', 'BATCH-2024-Q4-002'],
|
||||
['GD10-2024-000033', 'GD-10 Supreme', '入门级电法仪', '已激活', 'v1.5.2', '2024-09-12 10:00', '河海大学', 'BATCH-2024-Q3-001'],
|
||||
['GD30-2024-000089', 'GD-30 Supreme', '高密度电法仪', '装配中', 'v2.3.5', '2025-03-05 15:10', '-', 'BATCH-2025-Q1-001'],
|
||||
['GT20-2025-000012', 'GD-20', '二维电法仪', '已激活', 'v1.8.5', '2025-01-22 09:30', '中南大学', 'BATCH-2025-Q1-002'],
|
||||
['GD30-2024-000102', 'GD-30 Supreme', '高密度电法仪', '已出厂', 'v2.3.4', '2024-10-18 14:00', '吉林大学', 'BATCH-2024-Q4-001'],
|
||||
['GD10-2024-000034', 'GD-10 Supreme', '入门级电法仪', '装配中', 'v1.5.2', '2025-03-08 11:45', '-', 'BATCH-2025-Q1-003'],
|
||||
['GD30-2024-000145', 'GD-30 Supreme', '高密度电法仪', '已激活', 'v2.3.5', '2024-08-25 16:20', '同济大学', 'BATCH-2024-Q3-002'],
|
||||
]
|
||||
for (const d of devs) insertDev.run(...d)
|
||||
|
||||
// Licenses
|
||||
const insertLic = db.prepare('INSERT INTO licenses (model, modules, expiry, status) VALUES (?, ?, ?, ?)')
|
||||
insertLic.run('GD-30', '一维自电/电阻率/激电测试模块, 二维自电/电阻率/激电测试模块, 三维自电/电阻率/激电测试模块, 水上, 跨孔, 电流场法', '2025-12-31', '生效')
|
||||
insertLic.run('GD-20', '一维自电/电阻率/激电测试模块, 二维自电/电阻率/激电测试模块, 三维自电/电阻率/激电测试模块', '2025-06-30', '生效')
|
||||
insertLic.run('GD-10', '一维自电/电阻率/激电测试模块, 二维自电/电阻率/激电测试模块', '2024-12-31', '生效')
|
||||
|
||||
// Repair Orders
|
||||
const insertRO = db.prepare('INSERT INTO repair_orders (id, sn, fault_type, status, priority, assignee, create_date, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
|
||||
const ros = [
|
||||
['WO-2024-0001', 'GD30-2024-000056', '板卡故障', '处理中', '高', '张工', '2024-03-15', '主协板通信异常,无法正常采集数据'],
|
||||
['WO-2024-0002', 'GD30-2024-000078', '固件异常', '待处理', '中', '李工', '2024-03-14', '固件升级后设备无法启动'],
|
||||
['WO-2024-0003', 'GT20-2025-000045', '通信故障', '已处理', '低', '王工', '2024-03-13', 'WiFi模块连接不稳定'],
|
||||
['WO-2024-0004', 'GD30-2024-000102', '电源故障', '处理中', '高', '赵工', '2024-03-12', '升压板输出电压不稳定'],
|
||||
['WO-2024-0005', 'GD10-2024-000033', '传感器故障', '待处理', '中', '张工', '2024-03-11', '温度传感器读数异常'],
|
||||
['WO-2024-0006', 'GD30-2024-000089', '板卡故障', '已处理', '低', '李工', '2024-03-10', '采集板通道3数据丢失'],
|
||||
['WO-2024-0007', 'GT20-2025-000012', '其他', '待处理', '高', '王工', '2024-03-09', '设备外壳损坏,需更换'],
|
||||
['WO-2024-0008', 'GD30-2024-000145', '固件异常', '处理中', '中', '赵工', '2024-03-08', '配置文件丢失,参数重置'],
|
||||
]
|
||||
for (const r of ros) insertRO.run(...r)
|
||||
|
||||
// Scrap Records
|
||||
const insertSR = db.prepare('INSERT INTO scrap_records (sn, model, reason, applicant, status, order_id, date, value, materials) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
||||
const srs = [
|
||||
['GD30-2023-001234', 'GD-30 Supreme', '主板损坏无法修复', '李工', '待审批', 'WO-2024-0001', '2024-03-10', 12000, '["采集板 AC20240308002","测控板 CT20240308003","升压板 BS20240308001"]'],
|
||||
['GD30-2023-001567', 'GD-30 Supreme', '多个核心部件损坏', '张工', '审批中', 'WO-2024-0003', '2024-03-08', 8500, '["采集板 AC20240215006","发射板 TX20240215003"]'],
|
||||
['GT20-2023-000890', 'GD-20 Supreme', '维修成本超过设备价值', '王工', '已审批', 'WO-2024-0005', '2024-02-28', 5200, '["主协板 MC20231205004"]'],
|
||||
['GD10-2023-000456', 'GD-10 Supreme', '设备老化严重', '赵工', '已驳回', 'WO-2024-0006', '2024-02-20', 3000, '[]'],
|
||||
['GD30-2023-002345', 'GD-30 Supreme', '主板损坏无法修复', '李工', '回收中', 'WO-2024-0008', '2024-02-15', 15000, '["采集板 AC20231110010","采集板 AC20231110011","升压板 BS20231110002"]'],
|
||||
['GT20-2023-000123', 'GD-20 Supreme', '多个核心部件损坏', '张工', '已回收', 'WO-2024-0010', '2024-01-25', 6800, '["采集板 AC20230801007","发射板 TX20230801002"]'],
|
||||
['GD30-2023-003456', 'GD-30 Supreme', '维修成本超过设备价值', '王工', '已回收', 'WO-2024-0012', '2024-01-10', 9200, '["主协板 MC20230610003","采集板 AC20230610008"]'],
|
||||
]
|
||||
for (const s of srs) insertSR.run(...s)
|
||||
|
||||
// Config Files
|
||||
const insertCF = db.prepare('INSERT INTO config_files (name, model, version, create_time, status, voltage, current, duty_cycle, pulse_width, channels, sample_rate, voltage_range, waveform, ssid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
||||
const cfs = [
|
||||
['CFG-GD30-v1.2.0', 'GD-30 Supreme', 'v1.2.0', '2025-01-15 10:30', '生效', '1500V', '10A', '0+0-、+0-0、+-', '0.25s~64s', 12, '50Hz/60Hz/100Hz/1000Hz', '±2.5V/±80V', '支持', 'GD30'],
|
||||
['CFG-GD30-v1.1.0', 'GD-30 Supreme', 'v1.1.0', '2024-09-20 14:00', '已停用', '1200V', '10A', '0+0-、+0-0', '0.25s~32s', 12, '50Hz/60Hz/100Hz', '±2.5V/±80V', '支持', 'GD30'],
|
||||
['CFG-GD20-v1.0.0', 'GD-20 Supreme', 'v1.0.0', '2024-08-10 09:15', '生效', '1000V', '8A', '0+0-、+0-0', '0.25s~8s', 6, '50Hz/60Hz', '±2.5V/±80V', '不支持', 'GD20'],
|
||||
['CFG-GD10-v1.0.0', 'GD-10 Supreme', 'v1.0.0', '2024-06-05 16:45', '生效', '800V', '5A', '0+0-', '0.5s~8s', 1, '50Hz/60Hz', '±2.5V', '不支持', 'GD10'],
|
||||
['CFG-GD20-v1.1.0', 'GD-20 Supreme', 'v1.1.0', '2025-02-28 11:20', '生效', '1000V', '8A', '0+0-、+0-0、+-', '0.25s~16s', 6, '50Hz/60Hz/100Hz', '±2.5V/±80V', '支持', 'GD20'],
|
||||
['CFG-GD30-v1.3.0', 'GD-30 Supreme', 'v1.3.0', '2025-03-15 08:00', '生效', '1500V', '10A', '0+0-、+0-0、+-', '0.25s~64s', 12, '50Hz/60Hz/100Hz/1000Hz', '±2.5V/±80V', '支持', 'GD30'],
|
||||
]
|
||||
for (const c of cfs) insertCF.run(...c)
|
||||
|
||||
// BOM Templates
|
||||
const insertBOM = db.prepare('INSERT INTO bom_templates (model_code, name, material_name, model, versions, qty, required, need_calibration, enforce_version_match) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
||||
const boms: [string, string, string, string, string, number, number, number, number][] = [
|
||||
['GD30', '主协板', 'GD30 主协板', 'MCB-3000', '["MB-V2.1","MB-V1.8"]', 1, 1, 0, 0],
|
||||
['GD30', '采集板', 'GD30 采集板', 'ACB-6000', '["RX-V2.3","RX-V1.3"]', 2, 1, 1, 1],
|
||||
['GD30', '发射板', 'GD30 发射板', 'TXB-1000', '["TX-V2.1","TX-V1.5"]', 1, 1, 0, 0],
|
||||
['GD30', '升压板', 'GD30 升压板', 'BST-500', '["BP600-V1.2"]', 1, 1, 0, 0],
|
||||
['GD30', '机箱', '通用机箱', 'GD30-CASE-A', '["GD30-CASE-A","GD30-CASE-B"]', 1, 1, 0, 0],
|
||||
['GD30', '电缆', '通用电缆', 'CBL-GD30', '["CBL-60M","CBL-100M"]', 1, 1, 0, 0],
|
||||
['GD30', '电缆头', '通用电缆头 SR10/SR20', 'CBH-GD30', '["SR10","SR20","CS60","SR60","SR60PLUS"]', 2, 1, 0, 0],
|
||||
['GD30', '电源', 'BP600 电源', 'BP600', '["BP600-V2.0","BP600-V1.0"]', 1, 1, 0, 0],
|
||||
['GD20', '主协板', 'GD20 主协板', 'MCB-2000', '["MB-V1.8","MB-V1.2"]', 1, 1, 0, 0],
|
||||
['GD20', '采集板', 'GD20 采集板', 'ACB-5000', '["RX-V2.1","RX-V1.3"]', 1, 1, 1, 0],
|
||||
['GD20', '发射板', 'GD20 发射板', 'TXB-800', '["TX-V1.5"]', 1, 1, 0, 0],
|
||||
['GD20', '机箱', '通用机箱', 'GD20-CASE-A', '["-"]', 1, 1, 0, 0],
|
||||
['GD20', '电源', 'BP300 电源', 'BP300', '["BP300-V1.0"]', 1, 1, 0, 0],
|
||||
['GD10', '主协板', 'GD10 主协板', 'MCB-1000', '["MB-V1.2"]', 1, 1, 0, 0],
|
||||
['GD10', '采集板', 'GD10 采集板', 'ACB-3000', '["RX-V1.3"]', 1, 1, 1, 0],
|
||||
['GD10', '机箱', '通用机箱', 'GD10-CASE-A', '["-"]', 1, 1, 0, 0],
|
||||
['GD10', '电源', 'BP150 电源', 'BP150', '["BP150-V1.0"]', 1, 0, 0, 0],
|
||||
['GM10', '主协板', 'GM10 主协板', 'MCB-GM10', '["MB-V2.1"]', 1, 1, 0, 0],
|
||||
['GM10', '采集板', 'GM10 采集板', 'ACB-GM10', '["RX-V2.3"]', 2, 1, 1, 1],
|
||||
['GM10', '机箱', '通用机箱', 'GM10-CASE-A', '["GM10-CASE-A"]', 1, 1, 0, 0],
|
||||
['GM10', '电缆', '通用电缆', 'CBL-GM10', '["CBL-30M"]', 1, 1, 0, 0],
|
||||
['GM10', '电源', 'BP600 电源', 'BP600', '["BP600-V2.0"]', 1, 1, 0, 0],
|
||||
['GT10', '主协板', 'GT10 主协板', 'MCB-GT10', '["MB-V2.1"]', 1, 1, 0, 0],
|
||||
['GT10', '采集板', 'GT10 采集板', 'ACB-GT10', '["RX-V2.3"]', 1, 1, 1, 0],
|
||||
['GT10', '发射板', 'GT10 发射板', 'TXB-GT10', '["TX-V2.1"]', 1, 1, 0, 0],
|
||||
['GT10', '机箱', '通用机箱', 'GT10-CASE-A', '["GT10-CASE-A"]', 1, 1, 0, 0],
|
||||
['GT10', '电源', 'BP600 电源', 'BP600', '["BP600-V2.0"]', 1, 1, 0, 0],
|
||||
['GP10', '主协板', 'GP10 主协板', 'MCB-GP10', '["MB-V2.1"]', 1, 1, 0, 0],
|
||||
['GP10', '机箱', '通用机箱', 'GP10-CASE-A', '["GP10-CASE-A"]', 1, 1, 0, 0],
|
||||
['GP10', '电源', 'BP600 电源', 'BP600', '["BP600-V2.0"]', 1, 0, 0, 0],
|
||||
]
|
||||
for (const b of boms) insertBOM.run(...b)
|
||||
|
||||
// Firmware
|
||||
const insertFW = db.prepare('INSERT INTO firmware (version, board_version, type, date, status, size, downloads, hw_range, upgrade_type, signed, md5, sha256, notes, model_code) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
||||
const fws = [
|
||||
['v2.1.0', 'MB-V1.8', '主协板', '2024-03-10', '已发布', '12.5MB', 1234, 'MB25130025 Rev.A~C', '可选', 1, 'a1b2c3d4e5f6...', '9f8e7d6c5b4a...', '["修复通信协议兼容性问题","优化低功耗模式切换","新增看门狗超时配置"]', ''],
|
||||
['v3.0.2', 'RX-V2.3', '采集板', '2024-02-20', '已发布', '8.7MB', 890, 'ACB-6000 Rev.A~B', '可选', 1, 'd4e5f6a1b2c3...', '6c5b4a9f8e7d...', '["提升采样精度","修复通道串扰问题","新增自校准功能"]', ''],
|
||||
['v1.2.0', 'TX-V2.1', '发射板', '2024-02-28', '已发布', '6.3MB', 456, 'TXB-1000 Rev.A', '强制', 1, 'f6a1b2c3d4e5...', '4a9f8e7d6c5b...', '["新增过流保护","优化PWM控制算法"]', ''],
|
||||
]
|
||||
for (const f of fws) insertFW.run(...f)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue