This commit is contained in:
feie9456 2025-06-22 11:34:32 +08:00
parent 142992a6c9
commit 7d139b22db
55 changed files with 11035 additions and 105 deletions

View File

@ -0,0 +1,662 @@
[
{
"name": "英特尔INTELINTELE31225V3122612451246123112761275V312711285V3CPU二 E31246V3带核显",
"brand": "英特尔INTEL",
"model": "E3-1246V3",
"price": 285.96,
"description": "带核显",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/228561/16/23683/131837/66cf4403Fcb23e4a3/33a8da8e60781001.jpg",
"stock": 30,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"3.5GHz\",\"boostClock\":\"3.9GHz\",\"socket\":\"LGA1150\",\"integratedGraphics\":\"Intel HD Graphics P4600\"}"
},
{
"name": "英特尔(Intel) i5-14600KF 酷睿14代 处理器",
"brand": "英特尔(Intel)",
"model": "i5-14600KF",
"price": 1399,
"description": "酷睿14代i5可超频192GB至高内存容量兼容600/700主板芯片组更高帧率更快相应游戏办公两不误",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/293395/35/15365/120737/68552403F3dbabf43/d4b4bb8fcfd8286f.png",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"cache\":\"24M三级缓存\"}"
},
{
"name": "英特尔Intel酷睿 Ultra 7 265K 台式机处理器",
"brand": "英特尔Intel",
"model": "酷睿 Ultra 7 265K",
"price": 2294.4,
"description": "酷睿Ultra7睿频至高5.5GHz核显支持全新3D混合架构智能功耗管理兼容810/860/890芯片组电竞创作两不误",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/296128/30/4814/80017/68552415F3cc7312c/37d758b30c4aa079.png",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":20,\"threads\":20,\"boostClock\":\"5.5GHz\",\"socket\":\"LGA1851 (推测)\",\"architecture\":\"全新3D混合架构\",\"integratedGraphics\":\"支持\"}"
},
{
"name": "英特尔Inteli5-13490F 酷睿13代 处理器",
"brand": "英特尔Intel",
"model": "i5-13490F",
"price": 849,
"description": "混合CPU架构多线程高效能流畅体验超乎想象",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/299590/39/16486/101405/68552406F754195d2/e9c26d0d7fac6f87.png",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":16,\"cache\":\"24M三级缓存\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel酷睿3代i7-3770K/i5-3470/i5-3570K/i3-3240/3220/LGA1155/CPU i5-3570",
"brand": "英特尔Intel",
"model": "i5-3570",
"price": 579,
"description": "只卖正品/零售批发",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/265175/9/29671/69017/67caf6f0Fffc9e56b/f3c03d8a8ff47385.jpg",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":4,\"baseClock\":\"3.4GHz\",\"boostClock\":\"3.8GHz\",\"socket\":\"LGA1155\",\"integratedGraphics\":\"Intel HD Graphics 2500\"}"
},
{
"name": "英特尔i5 i7酷睿12/13/14代CPU处理器 12700KF 14600KF全新盒装 散片 I5 14600KF盒装",
"brand": "英特尔",
"model": "I5 14600KF盒装",
"price": 1279,
"description": "全新盒装",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/291736/21/10248/133257/6853cda6F418d8342/d398288fbd6ff1fd.png",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔(Intel) i7-14700KF 酷睿14代 处理器",
"brand": "英特尔(Intel)",
"model": "i7-14700KF",
"price": 2399,
"description": "酷睿14代i7192GB至高内存容量旗舰超频可兼容600/700主板内容设计创作更流畅",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/314535/25/11097/120004/685523fdFcd2bc63a/985bd41061a2a352.png",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":20,\"threads\":28,\"baseClock\":\"3.4GHz (P-core); 2.5GHz (E-core)\",\"boostClock\":\"5.5GHz (P-core); 4.3GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Intel酷睿Ultra CPU 台式机处理器 盒装CPU Ultra 9 285K 盒装【24核24线程】",
"brand": "英特尔Intel",
"model": "酷睿 Ultra 9 285K",
"price": 4499,
"description": "英特尔酷睿Ultra台式机处理器(第二代)解锁AI新竞界。功耗更优化多线程性能再提升解锁AI+游戏新体验,超频体验再升级!爆款晒单返京豆,详情请咨询客服。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/318828/19/10448/103437/685528c4F5eb17246/78027a05983da637.png",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":24,\"threads\":24,\"socket\":\"LGA1851 (推测)\",\"architecture\":\"第二代酷睿Ultra\"}"
},
{
"name": "英特尔Intel酷睿i7 X系列/i7-5820K/i7-5960X至尊版/LGA2011/X99 i7-5960X",
"brand": "英特尔Intel",
"model": "i7-5960X",
"price": 899,
"description": "只卖正品/零售批发",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/275584/12/4224/113524/67d68f9dFf5ac3e73/8397b31a24870483.jpg",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":8,\"threads\":16,\"baseClock\":\"3.0GHz\",\"boostClock\":\"3.5GHz\",\"socket\":\"LGA2011-3\",\"cache\":\"20MB\"}"
},
{
"name": "英特尔Intel酷睿 Ultra 9 285K 台式机处理器",
"brand": "英特尔Intel",
"model": "酷睿 Ultra 9 285K",
"price": 4340.3,
"description": "酷睿Ultra9睿频至高5.7GHz全新3D混合架构支持内存超频兼容810/860/890芯片组更流畅更节能更静音",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/314400/10/11078/94234/68552411Fa8f0998f/57b264cc7fb1e327.png",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":24,\"threads\":24,\"boostClock\":\"5.7GHz\",\"socket\":\"LGA1851 (推测)\",\"architecture\":\"全新3D混合架构\",\"memoryOverclocking\":\"支持\"}"
},
{
"name": "英特尔Intel酷睿 Ultra 9 285 台式机处理器",
"brand": "英特尔Intel",
"model": "酷睿 Ultra 9 285",
"price": 4290.4,
"description": "酷睿Ultra936MB智能缓存全新3D混合架构原装风扇支持支持内存超频兼容810/860/890芯片组体验再升级",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/296514/7/17725/88895/68552412F05717cf5/cf2552e83048391a.png",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":24,\"threads\":24,\"boostClock\":\"5.6GHz\",\"socket\":\"LGA1851 (推测)\",\"cache\":\"36MB智能缓存\",\"architecture\":\"全新3D混合架构\",\"memoryOverclocking\":\"支持\"}"
},
{
"name": "英特尔(Intel) i9-14900K 酷睿14代",
"brand": "英特尔(Intel)",
"model": "i9-14900K",
"price": 3399,
"description": "酷睿14代i9单核超高睿频AI超频192GB至高内存容量内存超频至高可达8000+可兼容600/700主板畅玩游戏无卡顿",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/297671/28/17012/116505/685523fbF24b22709/01d545c39f494f0e.png",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":24,\"threads\":32,\"socket\":\"LGA1700\",\"memoryCapacity\":\"192GB\",\"memorySpeed\":\"8000+ MHz (超频)\",\"integratedGraphics\":\"Intel UHD Graphics 770\"}"
},
{
"name": "英特尔Intel酷睿8代i7-8700K/8700/8086K/i5-8600K/8600/8500/8400/LGA1151 i5-8600K",
"brand": "英特尔Intel",
"model": "i5-8600K",
"price": 899,
"description": "只卖正品/零售批发",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/262118/29/30839/54424/67cfa4a8F55f141c1/c2fa42c20ef55ce0.jpg",
"stock": 10,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":6,\"baseClock\":\"3.6GHz\",\"boostClock\":\"4.3GHz\",\"socket\":\"LGA1151\",\"integratedGraphics\":\"Intel UHD Graphics 630\"}"
},
{
"name": "英特尔酷睿14代15代i5i7i9U5U7U9 14490F/14700KF国行盒装台式机盒装CPU 14代i9-14900KF 盒装 24核32线程",
"brand": "英特尔",
"model": "i9-14900KF",
"price": 2876,
"description": "国行盒装",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/103503/21/47232/93933/65fbe567F20663dc6/7c848967805e43ad.jpg",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":24,\"threads\":32,\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔(Intel) i5-12400F 酷睿12代 CPU处理器",
"brand": "英特尔(Intel)",
"model": "i5-12400F",
"price": 839,
"description": "6核12线程18MB三级缓存睿频至高可达4.40GHz,一芯多用,状态稳定,办公休闲两不误!",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/307531/24/11117/79585/6855240dF45f7e32e/7f8588b5574f26b5.png",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":12,\"boostClock\":\"4.4GHz\",\"cache\":\"18MB三级缓存\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔i5 i7酷睿12/13/14代CPU处理器 12700KF 14600KF全新盒装 散片 I5 12400F 全新散片【三年店保】",
"brand": "英特尔",
"model": "I5 12400F",
"price": 649,
"description": "全新散片,三年店保",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/294353/12/14374/147106/6853cdd6F95d7988b/d47d31d355290395.png",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":12,\"baseClock\":\"2.5GHz\",\"boostClock\":\"4.4GHz\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel4代酷睿i7/i5/i3 i7-4790K 4770 i5-4460 i5-4590K i3-4170(盒) i3-4170",
"brand": "英特尔Intel",
"model": "i3-4170",
"price": 479,
"description": "只卖正品/零售批发",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/266237/7/18044/78932/67a870ecFa5173024/6960e969231c37f2.jpg",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":2,\"threads\":4,\"baseClock\":\"3.7GHz\",\"socket\":\"LGA1150\",\"integratedGraphics\":\"Intel HD Graphics 4400\"}"
},
{
"name": "英特尔(Intel) i5-12600KF 酷睿12代 处理器",
"brand": "英特尔(Intel)",
"model": "i5-12600KF",
"price": 1099,
"description": "10核16线程20MB三级缓存睿频至高可达4.90GHz,增强音频功能,玩游戏做直播尽享清晰语音!",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/315514/27/10468/100500/6855240aFe64abfe7/f7f029ca4b2f1626.png",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":16,\"boostClock\":\"4.9GHz\",\"cache\":\"20MB三级缓存\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Intel14代酷睿CPU处理器 台式机处理器 盒装CPU i5-14600KF 盒装【五年质保】",
"brand": "英特尔Intel",
"model": "i5-14600KF",
"price": 1379,
"description": "英特尔酷睿Ultra台式机处理器(第二代)解锁AI新竞界。功耗更优化多线程性能再提升解锁AI+游戏新体验,超频体验再升级!爆款晒单返京豆,详情请咨询客服。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/295747/38/5632/122106/68552e0fF35ed4b9b/67d1fb7937ee6bd1.png",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Intel14代酷睿CPU处理器 台式机处理器 盒装CPU i7-14700KF 盒装【五年质保】",
"brand": "英特尔Intel",
"model": "i7-14700KF",
"price": 2359,
"description": "英特尔酷睿Ultra台式机处理器(第二代)解锁AI新竞界。功耗更优化多线程性能再提升解锁AI+游戏新体验,超频体验再升级!爆款晒单返京豆,详情请咨询客服。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/297995/15/17373/126874/68552e16Ff0984355/c28425df8a4a8f3c.png",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":20,\"threads\":28,\"baseClock\":\"3.4GHz (P-core); 2.5GHz (E-core)\",\"boostClock\":\"5.5GHz (P-core); 4.3GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Intel酷睿6代i7-6700K/I7-6850K/i7-6900K/6950X/i5-6600K/6500/6400 i5-6600(盒)",
"brand": "英特尔Intel",
"model": "i5-6600(盒)",
"price": 729,
"description": "只卖正品/零售批发",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/275667/19/1057/27491/67cfee3bF563d89f8/cae1d68670cca41d.jpg",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":4,\"baseClock\":\"3.3GHz\",\"boostClock\":\"3.9GHz\",\"socket\":\"LGA1151\",\"integratedGraphics\":\"Intel HD Graphics 530\"}"
},
{
"name": "英特尔Intel13代酷睿CPU处理器 台式机处理器 盒装CPU 13代i5-13400F 盒装【10核16线程】",
"brand": "英特尔Intel",
"model": "i5-13400F",
"price": 959,
"description": "英特尔酷睿Ultra台式机处理器(第二代)解锁AI新竞界。功耗更优化多线程性能再提升解锁AI+游戏新体验,超频体验再升级!爆款晒单返京豆,详情请咨询客服。",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/313697/17/11151/111685/68552e15F9e572c37/a1bba0a043ce77dc.png",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":16,\"baseClock\":\"2.5GHz (P-core); 1.8GHz (E-core)\",\"boostClock\":\"4.6GHz (P-core); 3.3GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔IntelE5至强处理器服务器CPU2666V3 2676 2678 2680 E31231V2拆机散片 E5-2660v3【10核心20线程】",
"brand": "英特尔Intel",
"model": "E5-2660v3",
"price": 39.9,
"description": "拆机散片",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/256943/7/26630/23893/67c3c8baFebbeceef/04c9e3f71b8771e4.jpg",
"stock": 10,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":20,\"baseClock\":\"2.6GHz\",\"boostClock\":\"3.3GHz\",\"socket\":\"LGA2011-3\"}"
},
{
"name": "英特尔(Intel) i5-13400F 酷睿13代 处理器",
"brand": "英特尔(Intel)",
"model": "i5-13400F",
"price": 989,
"description": "13代酷睿i5混合架构600/700主板平台兼容DDR4/5内存支持游戏办公轻松胜任",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/300763/8/16476/108482/68552408F08dbbdd2/c6dc9652210f76a0.png",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":16,\"boostClock\":\"4.6GHz\",\"cache\":\"20M三级缓存\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel酷睿10代i9/i7/i5/i3 10700KF 10400F 10500 10600KF 10900K i3-10105F CPU",
"brand": "英特尔Intel",
"model": "i3-10105F",
"price": 499,
"description": "只卖正品/零售批发",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/261058/13/19674/58212/67af2fa6F67819b9e/f833fe86e5abcae2.jpg",
"stock": 30,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"3.7GHz\",\"boostClock\":\"4.4GHz\",\"socket\":\"LGA1200\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel12代酷睿CPU处理器 台式机处理器 盒装CPU 12代i3-12100F 盒装【4核8线程】",
"brand": "英特尔Intel",
"model": "i3-12100F",
"price": 499,
"description": "英特尔酷睿Ultra台式机处理器(第二代)解锁AI新竞界。功耗更优化多线程性能再提升解锁AI+游戏新体验,超频体验再升级!爆款晒单返京豆,详情请咨询客服。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/313144/3/11045/111968/68552e1dF701da797/737c9a759e7e7ad2.png",
"stock": 10,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"3.3GHz\",\"boostClock\":\"4.3GHz\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔(Intel) i9-14900KF 酷睿14代",
"brand": "英特尔(Intel)",
"model": "i9-14900KF",
"price": 3199,
"description": "酷睿14代i9单核超高睿频AI超频192GB至高内存容量内存超频至高可达8000+可兼容600/700主板更多超频选择实现硬核突破",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/287796/3/15834/130037/68526e79Fb9e1d5a6/b0272c2a8fad398d.png",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":24,\"threads\":32,\"socket\":\"LGA1700\",\"memoryCapacity\":\"192GB\",\"memorySpeed\":\"8000+ MHz (超频)\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Intel13代酷睿CPU处理器 台式机处理器 盒装CPU 13代i5-13490F 盒装【10核16线程】",
"brand": "英特尔Intel",
"model": "i5-13490F",
"price": 849,
"description": "英特尔酷睿Ultra台式机处理器(第二代)解锁AI新竞界。功耗更优化多线程性能再提升解锁AI+游戏新体验,超频体验再升级!爆款晒单返京豆,详情请咨询客服。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/299800/12/13041/126430/68552e1aF66dca4c8/904ac6440a4294ac.png",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":16,\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel酷睿i9/i5/i7 11代 11400 11500 11600K i7 11700 11700K 11900K i7-11700",
"brand": "英特尔Intel",
"model": "i7-11700",
"price": 1762.02,
"description": "只卖正品/零售批发",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/263802/6/21325/22656/67b42d87Fb0353130/a55c8c1c53922b24.jpg",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":8,\"threads\":16,\"baseClock\":\"2.5GHz\",\"boostClock\":\"4.9GHz\",\"socket\":\"LGA1200\",\"integratedGraphics\":\"Intel UHD Graphics 750\"}"
},
{
"name": "英特尔INTELi5-14600kf/13400F/12600kf/12400f/10400f/9400f散片cpu处理器 14代酷睿 i5-14600KF",
"brand": "英特尔INTEL",
"model": "i5-14600KF",
"price": 1159,
"description": "散片",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/319467/17/10319/96779/6853c133F644568e2/c4786b7940291f3d.jpg",
"stock": 31,
"typeName": "CPU",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔(Intel) i5-14600K 酷睿14代",
"brand": "英特尔(Intel)",
"model": "i5-14600K",
"price": 1799,
"description": "酷睿14代i5可超频英特尔770核显192GB至高内存容量兼容600/700主板芯片组职场性能主打",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/297594/37/16091/119216/68552405F245f963c/111ec63fe98259f6.png",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"integratedGraphics\":\"Intel UHD Graphics 770\"}"
},
{
"name": "英特尔(Intel) i3-12100F 酷睿12代 处理器",
"brand": "英特尔(Intel)",
"model": "i3-12100F",
"price": 549,
"description": "4核8线程12MB三级缓存睿频至高可达4.3Ghz,性能强劲,满足日常办公以及游戏娱乐需求!",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/58120/26/19560/120809/62a2fdd8E47da21f2/7691c878321b0369.jpg",
"stock": 30,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"boostClock\":\"4.3GHz\",\"cache\":\"12MB三级缓存\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "INTEL 英特尔 中央处理器 BX80684I59400F 2.9 GHz LGA 1151插槽",
"brand": "INTEL 英特尔",
"model": "i5-9400F",
"price": 1351.99,
"description": "2.9 GHz LGA 1151插槽",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/166443/24/51160/117809/672c0007F89b2a040/65b43a5f90816d80.jpg",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":6,\"baseClock\":\"2.9GHz\",\"boostClock\":\"4.1GHz\",\"socket\":\"LGA1151\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔i5 i7酷睿12/13/14代CPU处理器 12700KF 14600KF全新盒装 散片 I5 14600KF散片",
"brand": "英特尔",
"model": "I5 14600KF散片",
"price": 1219,
"description": "散片",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/302925/10/15997/147999/6853cdf2Fbc059b03/e0d2ebe95ac18fbf.png",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔(Intel) i5-12490F 酷睿12代 处理器",
"brand": "英特尔(Intel)",
"model": "i5-12490F",
"price": 749,
"description": "6核12线程20MB三级缓存睿频至高可达4.6Ghz,胜任主流游戏,稳定输出,畅享胜果!",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/298198/21/17457/110316/6855240cFe4ee7ea1/c7096321d44ee437.png",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":12,\"boostClock\":\"4.6GHz\",\"cache\":\"20MB三级缓存\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔(Intel 67 89代 酷睿 i3 i5 i7 i9 全系列处理器 CPU 店保一年 i3 8100散片",
"brand": "英特尔(Intel",
"model": "i3-8100",
"price": 123,
"description": "店保一年,散片",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/3658/9/24037/91623/66dd8b9cF80cc77f8/d1d005da1dc3db72.jpg",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":4,\"baseClock\":\"3.6GHz\",\"socket\":\"LGA1151\",\"integratedGraphics\":\"Intel UHD Graphics 630\"}"
},
{
"name": "英特尔二手服务器CPU 6330质保一年",
"brand": "英特尔",
"model": "至强金牌 6330",
"price": 2900,
"description": "二手服务器CPU质保一年",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/283542/1/23248/179089/68060d90F77dae3af/156b984be9c8dc41.jpg",
"stock": 10,
"typeName": "CPU",
"specifications": "{\"cores\":28,\"threads\":56,\"baseClock\":\"2.0GHz\",\"boostClock\":\"3.1GHz\",\"socket\":\"LGA4189\"}"
},
{
"name": "英特尔(Intel) i5-10400F 10代 酷睿 处理器",
"brand": "英特尔(Intel)",
"model": "i5-10400F",
"price": 589,
"description": "6核12线程12MB三级缓存睿频至高可达4.30GHz英特尔睿频加速技术2.0,高速内核负责关键工作负载,提高单线程性能!",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/308541/25/10799/111528/6855240eF9889db05/b76924336c0e1de1.png",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":12,\"boostClock\":\"4.3GHz\",\"cache\":\"12MB三级缓存\",\"socket\":\"LGA1200\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel酷睿 Ultra 7 265KF 台式机处理器",
"brand": "英特尔Intel",
"model": "酷睿 Ultra 7 265KF",
"price": 2294.4,
"description": "酷睿Ultra7睿频至高5.5GHz192GB至高内存容量全新3D混合架构智能功耗管理兼容810/860/890芯片组电竞创作两不误",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/304788/10/12222/82767/68552414F978f56c7/56e9bce44cf0c2bb.png",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":20,\"threads\":20,\"boostClock\":\"5.5GHz\",\"socket\":\"LGA1851 (推测)\",\"architecture\":\"全新3D混合架构\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Intel4代酷睿 i3 i5 i7处理器1150针e3 1231v3 4590 4770 4790K散片cpu i7 4790【拆机散片】",
"brand": "英特尔Intel",
"model": "i7-4790",
"price": 229,
"description": "拆机散片",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/260378/30/26289/132960/67c29c2dF39a8afef/c12adf548438aaf2.jpg",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"3.6GHz\",\"boostClock\":\"4.0GHz\",\"socket\":\"LGA1150\",\"integratedGraphics\":\"Intel HD Graphics 4600\"}"
},
{
"name": "intel4代酷睿/4790盒装/4590/4690四核LGA1150针四代CPU K盒95成色 i5-4590",
"brand": "Intel",
"model": "i5-4590",
"price": 311.27,
"description": "95成色",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/281294/21/20901/67206/67ffa298Fd224e88c/0aecf2d7447b9ef0.jpg",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":4,\"baseClock\":\"3.3GHz\",\"boostClock\":\"3.7GHz\",\"socket\":\"LGA1150\",\"integratedGraphics\":\"Intel HD Graphics 4600\"}"
},
{
"name": "英特尔(Intel) i5-12400 12代 酷睿 处理器",
"brand": "英特尔(Intel)",
"model": "i5-12400",
"price": 1096.8,
"description": "6核12线程18MB三级缓存睿频至高可达4.40GHz,支持同时连接多设备,高效输出!",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/111559/1/28543/32367/63071912Edd79ce3d/1083d989aa54e908.jpg",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":12,\"boostClock\":\"4.4GHz\",\"cache\":\"18MB三级缓存\",\"socket\":\"LGA1700\",\"integratedGraphics\":\"Intel UHD Graphics 730\"}"
},
{
"name": "英特尔14代酷睿i5i7i9 14490F/14700KF/14900KS 国行全新盒装台式机CPU 14代i5-14600KF 盒装 14核20线程",
"brand": "英特尔",
"model": "i5-14600KF",
"price": 1249,
"description": "国行全新盒装",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/236950/4/22802/152261/668e4975F5d6d052f/0bc7f1d4fd52ae94.jpg",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Intel12/13/14代CPU处理器 i5 12400f 13400f 14600kf 盒装 散片 12代 i3-12100F散片",
"brand": "英特尔Intel",
"model": "i3-12100F",
"price": 309,
"description": "散片",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/260992/33/29357/56561/67ca92eeF744f12f7/80c3c2da06443655.png",
"stock": 30,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"3.3GHz\",\"boostClock\":\"4.3GHz\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔服务器工作站多核心线程中央处理器CPU 至强金牌6430 32核心 2.1GHz",
"brand": "英特尔",
"model": "至强金牌 6430",
"price": 9800,
"description": "服务器工作站多核心线程",
"imageUrl": "",
"stock": 30,
"typeName": "CPU",
"specifications": "{\"cores\":32,\"threads\":64,\"baseClock\":\"2.1GHz\",\"socket\":\"LGA4677\"}"
},
{
"name": "英特尔Intel酷睿9/8/7/6代CPU处理器i3 i5 i7 i9系列9400F 9700KF拆机散片 i5 9400F【散片+赠硅脂】",
"brand": "英特尔Intel",
"model": "i5-9400F",
"price": 283,
"description": "拆机散片,赠硅脂",
"imageUrl": "",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":6,\"baseClock\":\"2.9GHz\",\"boostClock\":\"4.1GHz\",\"socket\":\"LGA1151\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel4代酷睿 i3 i5 i7处理器1150针e3 1231v3 4590 4770 4790K散片cpu E3-1230 V3【拆机散片】",
"brand": "英特尔Intel",
"model": "E3-1230 V3",
"price": 88,
"description": "拆机散片",
"imageUrl": "",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"3.3GHz\",\"boostClock\":\"3.7GHz\",\"socket\":\"LGA1150\",\"graphics\":\"无核显\"}"
},
{
"name": "英特尔Intel至强xeno处理器E3 1220 1231 V3 1240 1270 1285 V3 双核/四核CPU E3-1220 V51151针",
"brand": "英特尔Intel",
"model": "E3-1220 V5",
"price": 30,
"description": "1151针",
"imageUrl": "",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":4,\"baseClock\":\"3.0GHz\",\"boostClock\":\"3.5GHz\",\"socket\":\"LGA1151\"}"
},
{
"name": "英特尔Intel十四代处理器14代i5 盒装华硕主板CPU板U套装 华硕ROG B760-G WIFI D4 小吹雪 14代i5-14600KF 14核20线程 无核显",
"brand": "英特尔Intel",
"model": "i5-14600KF",
"price": 2513,
"description": "盒装无核显搭配华硕ROG B760-G WIFI D4主板",
"imageUrl": "",
"stock": 11,
"typeName": "CPU套装",
"specifications": "{\"cores\":14,\"threads\":20,\"baseClock\":\"3.5GHz (P-core); 2.6GHz (E-core)\",\"boostClock\":\"5.3GHz (P-core); 4.0GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\",\"motherboard\":\"华硕ROG B760-G WIFI D4\"}"
},
{
"name": "英特尔INTELi5-14600kf/13400F/12600kf/12400f/10400f/9400f散片cpu处理器 9代酷睿 i5-9400F",
"brand": "英特尔INTEL",
"model": "i5-9400F",
"price": 269,
"description": "散片",
"imageUrl": "",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":6,\"baseClock\":\"2.9GHz\",\"boostClock\":\"4.1GHz\",\"socket\":\"LGA1151\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel酷睿9/8/7/6代CPU处理器i3 i5 i7 i9系列9400F 9700KF拆机散片 i5 9600KF【散片+赠硅脂】",
"brand": "英特尔Intel",
"model": "i5-9600KF",
"price": 289,
"description": "拆机散片,赠硅脂",
"imageUrl": "",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":6,\"baseClock\":\"3.7GHz\",\"boostClock\":\"4.6GHz\",\"socket\":\"LGA1151\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔Inteli5-14490F 酷睿14代 处理器",
"brand": "英特尔Intel",
"model": "i5-14490F",
"price": 1096.8,
"description": "酷睿14代i5192GB至高内存容量10核16线程硬核动力效率翻倍",
"imageUrl": "",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":16,\"boostClock\":\"4.9GHz\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel 十四代处理器I9 14900KF/14900K盒装华硕主板CPU板U套装 华硕 华硕ROG B760-G WIFI D5 小吹雪S 14代i9-14900K 24核32线程 带核",
"brand": "英特尔Intel",
"model": "i9-14900K",
"price": 4509,
"description": "盒装带核显搭配华硕ROG B760-G WIFI D5主板",
"imageUrl": "",
"stock": 26,
"typeName": "CPU套装",
"specifications": "{\"cores\":24,\"threads\":32,\"socket\":\"LGA1700\",\"integratedGraphics\":\"Intel UHD Graphics 770\",\"motherboard\":\"华硕ROG B760-G WIFI D5\"}"
},
{
"name": "英特尔Intel酷睿 Ultra 7 265F 台式机处理器",
"brand": "英特尔Intel",
"model": "酷睿 Ultra 7 265F",
"price": 1995,
"description": "酷睿Ultra730MB智能缓存全新3D混合架构原装风扇支持智能功耗管理兼容810/860/890芯片组电竞创作两不误",
"imageUrl": "",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":20,\"threads\":20,\"boostClock\":\"5.3GHz\",\"cache\":\"30MB智能缓存\",\"socket\":\"LGA1851 (推测)\",\"architecture\":\"全新3D混合架构\",\"graphics\":\"无核显 (F系列)\"}"
},
{
"name": "英特尔Intel7代酷睿i3 i5 i7 i9系列处理器7300 7500 7600 7700K拆机散片cpu i3 7100 散片",
"brand": "英特尔Intel",
"model": "i3-7100",
"price": 70,
"description": "拆机散片",
"imageUrl": "",
"stock": 10,
"typeName": "CPU",
"specifications": "{\"cores\":2,\"threads\":4,\"baseClock\":\"3.9GHz\",\"socket\":\"LGA1151\",\"integratedGraphics\":\"Intel HD Graphics 630\"}"
},
{
"name": "英特尔i5 i7酷睿12/13/14代CPU处理器 12700KF 14600KF全新盒装 散片 I5 12600KF全新散片三年店保",
"brand": "英特尔",
"model": "I5 12600KF",
"price": 975,
"description": "全新散片,三年店保",
"imageUrl": "",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":16,\"baseClock\":\"3.7GHz (P-core); 2.8GHz (E-core)\",\"boostClock\":\"4.9GHz (P-core); 3.6GHz (E-core)\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (KF系列)\"}"
},
{
"name": "英特尔IntelPRO H610M-S DDR4台式电脑家用办公学习M-ATX主板支持CPU LGA1700",
"brand": "英特尔Intel",
"model": "PRO H610M-S DDR4",
"price": 605.6,
"description": "台式电脑家用办公学习M-ATX主板支持CPU LGA1700",
"imageUrl": "",
"stock": 14,
"typeName": "主板",
"specifications": "{\"formFactor\":\"M-ATX\",\"memoryType\":\"DDR4\",\"socket\":\"LGA1700\",\"chipset\":\"H610\"}"
},
{
"name": "英特尔Intel酷睿 Ultra 5 225 台式机处理器",
"brand": "英特尔Intel",
"model": "酷睿 Ultra 5 225",
"price": 1496,
"description": "酷睿Ultra520MB智能缓存全新3D混合架构原装风扇支持核显支持兼容810/860/890芯片组",
"imageUrl": "",
"stock": 31,
"typeName": "CPU",
"specifications": "{\"cores\":10,\"threads\":10,\"boostClock\":\"4.9GHz\",\"cache\":\"20MB智能缓存\",\"socket\":\"LGA1851 (推测)\",\"architecture\":\"全新3D混合架构\",\"integratedGraphics\":\"支持\"}"
},
{
"name": "英特尔Intel4代酷睿 i3 i5 i7处理器1150针e3 1231v3 4590 4770 4790K散片cpu i7 4790K【拆机散片】",
"brand": "英特尔Intel",
"model": "i7-4790K",
"price": 292,
"description": "拆机散片",
"imageUrl": "",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"4.0GHz\",\"boostClock\":\"4.4GHz\",\"socket\":\"LGA1150\",\"integratedGraphics\":\"Intel HD Graphics 4600\"}"
},
{
"name": "英特尔IntelCPU处理器i3 14100f 13100f 12100f 散片 盒装i313100 i312100 i3-12100F散片无盒 店保3年 LGA1700针脚",
"brand": "英特尔Intel",
"model": "i3-12100F",
"price": 299,
"description": "散片无盒店保3年LGA1700针脚",
"imageUrl": "",
"stock": 14,
"typeName": "CPU",
"specifications": "{\"cores\":4,\"threads\":8,\"baseClock\":\"3.3GHz\",\"boostClock\":\"4.3GHz\",\"socket\":\"LGA1700\",\"graphics\":\"无核显 (F系列)\"}"
}
]

View File

@ -0,0 +1,332 @@
[
{
"name": "AMD 锐龙7 5700X处理器",
"brand": "AMD",
"model": "锐龙7 5700X",
"price": 797.4,
"description": "8核16线程加速频率至高4.6GHz65W AM4接口36MB游戏高速缓存。",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/310223/10/10985/56468/685507f0Fd4b53c55/41d0255fb243b769.jpg",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"cores\":8,\"threads\":16,\"boostClock\":\"4.6GHz\",\"tdp\":\"65W\",\"socket\":\"AM4\",\"cache\":\"36MB\"}"
},
{
"name": "AMD 锐龙5 5500 处理器",
"brand": "AMD",
"model": "锐龙5 5500",
"price": 458.08,
"description": "7nm6核12线程加速频率至高4.2GHz65W AM4接口19MB游戏高速缓存。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/317136/38/10469/55907/685507e7Fa214d175/f37bf7ce1f3f224c.jpg",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"architecture\":\"7nm\",\"cores\":6,\"threads\":12,\"boostClock\":\"4.2GHz\",\"tdp\":\"65W\",\"socket\":\"AM4\",\"cache\":\"19MB\"}"
},
{
"name": "AMD 锐龙R5 5600 CPU处理器",
"brand": "AMD",
"model": "锐龙R5 5600",
"price": 579,
"description": "AMD 锐龙R5 5600 CPU处理器。",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/280337/37/23403/52866/68085b82F2b804d49/28068d42b7e75e43.jpg",
"stock": 43,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":12,\"boostClock\":\"4.4GHz\",\"socket\":\"AM4\",\"cache\":\"35MB\"}"
},
{
"name": "AMD 锐龙7 9800X3D游戏处理器",
"brand": "AMD",
"model": "锐龙7 9800X3D",
"price": 3891.2,
"description": "8核16线程104MB游戏缓存加速频率至高5.2GHzTDP 120W。",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/298224/39/17213/45200/68550866F1b470153/31f478ea36ba66c1.jpg",
"stock": 46,
"typeName": "CPU",
"specifications": "{\"cores\":8,\"threads\":16,\"cache\":\"104MB\",\"boostClock\":\"5.2GHz\",\"tdp\":\"120W\"}"
},
{
"name": "AMD 锐龙R5 9600X CPU",
"brand": "AMD",
"model": "锐龙R5 9600X",
"price": 1349,
"description": "AMD 锐龙R5 9600X CPU。",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/278320/20/25464/45057/68086198F098a50c1/448a819aad8f7310.jpg",
"stock": 10,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD锐龙9 9950X3D处理器",
"brand": "AMD",
"model": "锐龙9 9950X3D",
"price": 5288.4,
"description": "16核32线程144MB缓存加速频率至高5.7GHz。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/301221/34/15138/42237/6855086eF629e9eea/c01c8e52fb4d89e2.jpg",
"stock": 58,
"typeName": "CPU",
"specifications": "{\"cores\":16,\"threads\":32,\"cache\":\"144MB\",\"boostClock\":\"5.7GHz\"}"
},
{
"name": "AMD锐龙5 5600处理器",
"brand": "AMD",
"model": "锐龙5 5600",
"price": 627.74,
"description": "7nm6核12线程加速频率至高4.4GHzAM4盒装CPU35MB游戏高速缓存。",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/321122/15/10646/55580/685507fbFa8d55853/9645509d1f3483b9.jpg",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"architecture\":\"7nm\",\"cores\":6,\"threads\":12,\"boostClock\":\"4.4GHz\",\"socket\":\"AM4\",\"cache\":\"35MB\"}"
},
{
"name": "AMD 锐龙7 7800X3D 散片CPU",
"brand": "AMD",
"model": "锐龙7 7800X3D",
"price": 2319,
"description": "5nm CPU处理器R7 7800X3D 散片。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/243065/40/4314/71309/65bb4815F252349a2/af010b9c89333ade.jpg",
"stock": 31,
"typeName": "CPU",
"specifications": "{\"architecture\":\"5nm\",\"socket\":\"AM5\"}"
},
{
"name": "AMD 锐龙R5 7500F CPU",
"brand": "AMD",
"model": "锐龙R5 7500F",
"price": 899,
"description": "AMD 锐龙R5 7500F CPU。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/281428/28/25695/34533/68086491Fbf29e922/fbc187866354e26d.jpg",
"stock": 27,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD 锐龙5 7500F处理器",
"brand": "AMD",
"model": "锐龙5 7500F",
"price": 997,
"description": "5nm6核12线程加速频率至高5GHzAM5盒装CPU自带风扇。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/313586/19/11007/66884/6855081bF33831959/711c362d92ef497b.jpg",
"stock": 46,
"typeName": "CPU",
"specifications": "{\"architecture\":\"5nm\",\"cores\":6,\"threads\":12,\"boostClock\":\"5GHz\",\"socket\":\"AM5\"}"
},
{
"name": "AMD 锐龙R9 9950X3D盒装CPU",
"brand": "AMD",
"model": "锐龙R9 9950X3D",
"price": 5299,
"description": "AMD 锐龙 9代处理器AM5接口盒装。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/276863/10/1862/49949/67d15213Fe7355a7b/398b00edb2ff64fd.jpg",
"stock": 30,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD 锐龙R9 9950X3D散片CPU",
"brand": "AMD",
"model": "锐龙R9 9950X3D",
"price": 4799,
"description": "AMD 锐龙 9代处理器AM5接口散片。",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/276863/10/1862/49949/67d15213Fe7355a7b/398b00edb2ff64fd.jpg",
"stock": 47,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD Threadripper PRO 7995WX",
"brand": "AMD",
"model": "Threadripper PRO 7995WX",
"price": 78899,
"description": "Threadripper线程撕裂者PRO盒装CPU工作站台式机处理器sTR5 AI。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/230736/2/38435/43912/67d38dbfF4a2ad259/4a547f79c1e7cb8b.jpg",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"socket\":\"sTR5\"}"
},
{
"name": "AMD锐龙 R7 9800X3D散片",
"brand": "AMD",
"model": "锐龙R7 9800X3D",
"price": 3349,
"description": "AMD锐龙 9000系列AM5接口CPU处理器散片。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/183665/36/55076/65760/6758f8e6F249081c4/61e1f88f24c76e08.jpg",
"stock": 31,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD 锐龙R5 5600X 散片CPU",
"brand": "AMD",
"model": "锐龙R5 5600X",
"price": 599,
"description": "AMD 锐龙 CPU 台式机处理器R5 5600X 散片。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/91655/7/46255/72546/65386d2bF379748bc/b8e2bffcace0f9b3.jpg",
"stock": 30,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM4\"}"
},
{
"name": "AMD 锐龙R7 5700X 散片CPU",
"brand": "AMD",
"model": "锐龙R7 5700X",
"price": 749,
"description": "AMD 锐龙 CPU 台式机处理器R7 5700X 散片。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/193184/40/40489/70107/65386d2aF00588cc1/80258e4bbd8911f4.jpg",
"stock": 42,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM4\"}"
},
{
"name": "AMD 锐龙R5 7500F(散片)CPU套装",
"brand": "AMD",
"model": "锐龙R5 7500F",
"price": 1839,
"description": "AMD七代锐龙CPU处理器搭华硕TUF GAMING B650M-E WIFI主板CPU套装。",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/178546/17/40673/113479/653b6944F30fd3073/8256ae616fa63469.jpg",
"stock": 43,
"typeName": "CPU套装",
"specifications": "{\"cpuModel\":\"R5 7500F\",\"motherboardModel\":\"华硕TUF GAMING B650M-E WIFI\",\"socket\":\"AM5\"}"
},
{
"name": "AMD 锐龙R7 9700X散片",
"brand": "AMD",
"model": "锐龙R7 9700X",
"price": 1879,
"description": "AMD 锐龙 9代处理器AM5接口散片。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/233879/26/25160/54945/6711c7a9Feee14f82/91fd72e95c7cdc68.jpg",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD锐龙7 7800X3D游戏处理器",
"brand": "AMD",
"model": "锐龙7 7800X3D",
"price": 2693.6,
"description": "8核16线程104MB游戏缓存加速频率至高5.0GHz盒装CPU。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/292409/29/14415/49255/68550813Fe9d960ba/3e9968bb3a8b94fa.jpg",
"stock": 42,
"typeName": "CPU",
"specifications": "{\"cores\":8,\"threads\":16,\"cache\":\"104MB\",\"boostClock\":\"5.0GHz\",\"socket\":\"AM5\"}"
},
{
"name": "AMD锐龙 R7 9700X散片",
"brand": "AMD",
"model": "锐龙R7 9700X",
"price": 1879,
"description": "AMD锐龙 9000系列AM5接口CPU处理器散片。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/87538/31/47649/66232/66b6d555F22e05e91/17c32968a7142bc8.jpg",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD B650M 主板",
"brand": "AMD",
"model": "CVN B650M D5 WIFI",
"price": 899,
"description": "AMDB650M WiFi主板支持AMD锐龙7000/8000/9000系列CPU。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/304781/37/12415/179214/685617f3Fa85a9603/a355bb0ee42a404d.jpg",
"stock": 42,
"typeName": "主板",
"specifications": "{\"chipset\":\"B650M\",\"socket\":\"AM5\",\"features\":\"WIFI, DDR5\"}"
},
{
"name": "AMD 锐龙R5 5600散片b2",
"brand": "AMD",
"model": "锐龙R5 5600",
"price": 559,
"description": "AMDamdcpu五代锐龙散片CPU质保三年。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/290706/16/11030/68564/683d5208F9b4d948d/989d3d4527ca8613.jpg",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM4\"}"
},
{
"name": "AMD 锐龙R5 7500F散片",
"brand": "AMD",
"model": "锐龙R5 7500F",
"price": 899,
"description": "AMD 锐龙 7代处理器AM5接口散片。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/163404/17/49900/53058/6711ef2cF90a244de/9b37f46783b2890f.jpg",
"stock": 11,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD 锐龙R5 7500F 散片CPU",
"brand": "AMD",
"model": "锐龙R5 7500F",
"price": 899,
"description": "AMD 锐龙 CPU 台式机处理器R5 7500F 散片。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/192360/22/35577/66381/65bb4817Fcdd5547e/eaa5704157d88fdd.jpg",
"stock": 26,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
},
{
"name": "AMD TUF B850M-PLUS WIFI主板",
"brand": "AMD",
"model": "TUF B850M-PLUS WIFI",
"price": 1319,
"description": "AMD主板 TUF GAMING B850M-PLUS WIFI重炮手主板支持7800X3D/9800X3D/9600X CPU。",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/289585/28/6233/140005/685617e1Fcb80642f/faeaf6d02c2b8ed9.jpg",
"stock": 31,
"typeName": "主板",
"specifications": "{\"chipset\":\"B850M\",\"socket\":\"AM5\",\"features\":\"WIFI\"}"
},
{
"name": "AMD 锐龙5 7600X处理器",
"brand": "AMD",
"model": "锐龙5 7600X",
"price": 1246.5,
"description": "6核12线程加速频率至高5.3GHz105W AM5接口32MB三级缓存。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/307552/27/11181/46632/68550803Fd9fe1e75/0a11fa017e2f9c1b.jpg",
"stock": 31,
"typeName": "CPU",
"specifications": "{\"cores\":6,\"threads\":12,\"boostClock\":\"5.3GHz\",\"tdp\":\"105W\",\"socket\":\"AM5\",\"cache\":\"32MB\"}"
},
{
"name": "AMD 锐龙R5 5600(散片)",
"brand": "AMD",
"model": "锐龙R5 5600",
"price": 579,
"description": "AMD 锐龙 CPU7nm 65W AM4接口处理器散片。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/92031/5/52947/56852/67120df2F6b82696a/fe963d87909c3e49.jpg",
"stock": 10,
"typeName": "CPU",
"specifications": "{\"architecture\":\"7nm\",\"tdp\":\"65W\",\"socket\":\"AM4\"}"
},
{
"name": "AMD 锐龙R5 5500盒装",
"brand": "AMD",
"model": "锐龙R5 5500",
"price": 409,
"description": "AMDamdcpu五代锐龙盒装CPU质保三年。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/308247/29/6012/68485/683d52deFf04c7baa/01559d74871a373d.jpg",
"stock": 42,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM4\"}"
},
{
"name": "AMD Threadripper 7980X",
"brand": "AMD",
"model": "Threadripper 7980X",
"price": 27999,
"description": "AMD Threadripper线程撕裂者盒装高端工作站台式机CPU处理器适用于科学计算深度学习。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/273409/11/2819/58288/67d39c8fF99d81cf7/fed7b0fc4b24b665.jpg",
"stock": 31,
"typeName": "CPU",
"specifications": "{\"socket\":\"sTR5\"}"
},
{
"name": "AMD 锐龙R7 7800X3D散片",
"brand": "AMD",
"model": "锐龙R7 7800X3D",
"price": 2319,
"description": "AMD 锐龙 7代处理器AM5接口散片。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/225111/31/26777/55001/6711f2b0F9d380aa7/4533b21fbf6dd0b7.jpg",
"stock": 15,
"typeName": "CPU",
"specifications": "{\"socket\":\"AM5\"}"
}
]

View File

@ -0,0 +1,662 @@
[
{
"name": "英伟达NVIDIATesla T4/A10/A30/A40/L4/L40S显卡人工智能服务器GPU推理显卡 Tesla A2 16G",
"brand": "英伟达NVIDIA",
"model": "Tesla A2",
"price": 3189,
"description": "人工智能服务器GPU推理显卡 Tesla A2 16G不开发票",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/307817/10/11064/84061/685609c9F2f2a23e3/642c5cc95c710a24.png",
"stock": 46,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Tesla\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"purpose\":\"AI推理\",\"interface\":\"PCIe\"}"
},
{
"name": "英伟达NVIDIARTX4090系列 英伟达RTX4060 Ti 16G 涡轮版",
"brand": "英伟达NVIDIA",
"model": "RTX 4060 Ti",
"price": 3653,
"description": "公版系列原装定制涡轮AI深度学习训练推理大模型专业显卡内容创作卡顶级游戏卡16G显存京采质保3年",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/284589/20/10886/58185/67e6755dF258de9f7/140c61650c896457.jpg",
"stock": 46,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,内容创作,游戏\",\"cooling\":\"涡轮散热\"}"
},
{
"name": "英伟达P1000 P400 2GB",
"brand": "英伟达",
"model": "Quadro P400",
"price": 249,
"description": "盒装P4002GB显存适用于图形设计建模渲染多屏炒股工业包装",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/279405/11/18624/87867/67f9d276Fc374d51c/5cb67af266a4d9ba.jpg",
"stock": 42,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro P系列\",\"memory\":\"2GB\",\"memory_type\":\"不详\",\"purpose\":\"图形设计,建模,渲染,多屏\"}"
},
{
"name": "NVIDIA英伟达Quadro K2200 4G",
"brand": "NVIDIA英伟达",
"model": "Quadro K2200",
"price": 439,
"description": "专业渲染建模图形剪辑人工智能设计师绘图工业显卡4G显存工包",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/231461/36/11255/91304/659796a5Fa40a211a/4cad801723b6b259.jpg",
"stock": 11,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro K系列\",\"memory\":\"4GB\",\"memory_type\":\"不详\",\"purpose\":\"渲染,建模,图形剪辑,AI设计,绘图\"}"
},
{
"name": "英伟达【企业专享价】RTX A2000 12G",
"brand": "英伟达",
"model": "RTX A2000",
"price": 3859,
"description": "专业设计显卡12G显存建模渲染/AI推理/视频剪辑,单槽低功耗,工包(全新)",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/254368/27/20220/51009/67ad8aa0Fadd37605/aac43712e53ecbde.jpg",
"stock": 15,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX A系列\",\"memory\":\"12GB\",\"memory_type\":\"不详\",\"purpose\":\"设计,建模,渲染,AI推理,视频剪辑\",\"form_factor\":\"单槽\",\"power_consumption\":\"低功耗\"}"
},
{
"name": "耕升GeForce RTX 5060 Ti 踏雪X3 OC 16G",
"brand": "耕升",
"model": "GeForce RTX 5060 Ti 踏雪X3 OC",
"price": 4040.9,
"description": "16G游戏显卡支持DLSS 4适用于电竞游戏/设计剪辑/AI本地部署/直播娱乐",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/298040/12/8542/95247/682af59aF2061b01c/83397a2560de22b2.jpg",
"stock": 47,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"features\":\"DLSS 4,OC\",\"purpose\":\"电竞游戏,设计剪辑,AI本地部署,直播娱乐\"}"
},
{
"name": "英伟达RTX3060 12G公版涡轮",
"brand": "英伟达",
"model": "RTX 3060",
"price": 1769,
"description": "全新定制涡轮AI深度学习大模型专业显卡12G显存公版涡轮",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/229514/10/37240/76928/67b9b319F2ba4ec17/63dc07fbbac2b781.jpg",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"12GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\",\"design\":\"公版\"}"
},
{
"name": "NVIDIA英伟达Quadro RTX4000 8G",
"brand": "NVIDIA英伟达",
"model": "Quadro RTX 4000",
"price": 2289,
"description": "专业渲染建模图形剪辑人工智能设计师绘图工业显卡8G显存工包",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/249699/2/2117/63383/6597991aF7a60fdde/c64c74f24086619d.jpg",
"stock": 31,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro RTX系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"purpose\":\"渲染,建模,图形剪辑,AI设计,绘图\"}"
},
{
"name": "英伟达NVIDIATesla A100显卡80G 原版",
"brand": "英伟达NVIDIA",
"model": "Tesla A100",
"price": 168890,
"description": "定制PCIE人工智能大模型加速GPU服务器显卡80G显存原版不开发票",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/318143/1/10580/61481/685609b5Ffc45fe82/52d0d3d5597f0ff7.jpg",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Tesla\",\"memory\":\"80GB\",\"memory_type\":\"不详\",\"purpose\":\"人工智能,大模型加速,GPU服务器\",\"interface\":\"PCIe\"}"
},
{
"name": "英伟达NVIDIARTX4090系列 英伟达RTX4070 SUPER 12G公版涡轮",
"brand": "英伟达NVIDIA",
"model": "RTX 4070 SUPER",
"price": 5034,
"description": "公版系列原装定制涡轮AI深度学习训练推理大模型专业显卡内容创作卡顶级游戏卡12G显存京采质保3年",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/274809/37/12964/69082/67e9f31dFe30b13b9/3bda82292adfc13d.jpg",
"stock": 59,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"12GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,内容创作,游戏\",\"cooling\":\"涡轮散热\"}"
},
{
"name": "英伟达 NVIDIA RTX5090 32G 公版",
"brand": "英伟达 NVIDIA",
"model": "RTX 5090",
"price": 25959,
"description": "独立显卡32G显存适用于大模型算力深度学习电竞游戏专业设计AI渲染公版不开发票",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/320143/29/9528/81858/68511713F92ffc35d/bec0b929e0c0789d.jpg",
"stock": 42,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"32GB\",\"memory_type\":\"不详\",\"purpose\":\"大模型算力,深度学习,电竞游戏,专业设计,AI渲染\",\"design\":\"公版\"}"
},
{
"name": "NVIDIA英伟达Quadro M4000 8G",
"brand": "NVIDIA英伟达",
"model": "Quadro M4000",
"price": 1099,
"description": "专业渲染建模图形剪辑人工智能设计师绘图工业显卡8G显存工包",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/247506/5/2047/64039/6597970eF9d763915/0dd2bbf83683d698.jpg",
"stock": 43,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro M系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"purpose\":\"渲染,建模,图形剪辑,AI设计,绘图\"}"
},
{
"name": "NVIDIA英伟达Quadro M2000 4G",
"brand": "NVIDIA英伟达",
"model": "Quadro M2000",
"price": 559,
"description": "专业渲染建模图形剪辑人工智能设计师绘图工业显卡4G显存工包",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/234046/27/11489/81101/659796faF7878eb6c/8759e0a00eefed56.jpg",
"stock": 27,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro M系列\",\"memory\":\"4GB\",\"memory_type\":\"不详\",\"purpose\":\"渲染,建模,图形剪辑,AI设计,绘图\"}"
},
{
"name": "影驰 GeForce RTX 5060 Ti 经典大将 8GB GDDR7",
"brand": "影驰",
"model": "GeForce RTX 5060 Ti 经典大将",
"price": 3192.6,
"description": "8GB GDDR7显存支持DLSS 4电竞光追游戏/AI本地部署显卡",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/298179/27/16038/131916/68512f28Fafd94934/a3ba958813802dc0.jpg",
"stock": 15,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"GDDR7\",\"features\":\"DLSS 4,光线追踪\",\"purpose\":\"电竞游戏,AI本地部署\"}"
},
{
"name": "英伟达RTX4060Ti 16G涡轮",
"brand": "英伟达",
"model": "RTX 4060 Ti",
"price": 3659,
"description": "全新定制涡轮AI深度学习训练推理大模型专业显卡16G显存",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/253592/13/25767/52653/67bdced9Fcefbbd86/d9ebfcf765e10b78.jpg",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,训练,推理,大模型\",\"cooling\":\"涡轮散热\"}"
},
{
"name": "盈通yestonGeForce RTX 5060 8G GDDR7 萌宠",
"brand": "盈通yeston",
"model": "GeForce RTX 5060 萌宠",
"price": 2643.7,
"description": "8G GDDR7显存电竞光追游戏AI智能学习电脑独立显卡",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/299054/4/16964/76693/6855122bF85e10635/f9608349b60269aa.jpg",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"GDDR7\",\"features\":\"光线追踪,AI智能学习\",\"purpose\":\"电竞游戏,AI智能学习\"}"
},
{
"name": "英伟达NVIDIATesla H100 80G",
"brand": "英伟达NVIDIA",
"model": "Tesla H100",
"price": 211999,
"description": "DeepSeeK部署显卡人工智能深度学习高性能计算GPUAI推理训练卡80G显存",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/256593/23/19578/47861/67b602f4F17b542e7/0ea2b1d7e6ccc2b9.jpg",
"stock": 46,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Tesla\",\"memory\":\"80GB\",\"memory_type\":\"不详\",\"purpose\":\"AI部署,深度学习,高性能计算,AI推理训练\"}"
},
{
"name": "耕升GeForce RTX 5060 Ti 追风 白 OC 16G",
"brand": "耕升",
"model": "GeForce RTX 5060 Ti 追风 白 OC",
"price": 3941.1,
"description": "16G游戏显卡支持DLSS 4适用于电竞游戏/设计剪辑/AI本地部署/直播娱乐",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/303153/31/7138/86921/682af5a1Fe8a17e6e/32391f33f6a62778.jpg",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"features\":\"DLSS 4,OC\",\"purpose\":\"电竞游戏,设计剪辑,AI本地部署,直播娱乐\",\"color\":\"白色\"}"
},
{
"name": "英伟达RTX3080 10G 涡轮版",
"brand": "英伟达",
"model": "RTX 3080",
"price": 2629,
"description": "涡轮显卡原厂公版双宽AI模型绘图服务器GPU10G显存不开发票",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/305451/10/12494/65718/6856064eF985f9d61/cd5ebac2fbef5170.jpg",
"stock": 47,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"10GB\",\"memory_type\":\"不详\",\"purpose\":\"AI模型绘图,服务器GPU\",\"cooling\":\"涡轮散热\",\"design\":\"公版\",\"width\":\"双宽\"}"
},
{
"name": "英伟达RTX3060ti 8G 公版涡轮",
"brand": "英伟达",
"model": "RTX 3060 Ti",
"price": 1979,
"description": "全新定制涡轮AI深度学习大模型专业显卡8G显存公版涡轮",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/311614/28/9065/62875/684a95dfF6610da67/cbac2d8badda016b.jpg",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\",\"design\":\"公版\"}"
},
{
"name": "英伟达NVIDIA RTX 4500 Ada 24G DDR6",
"brand": "英伟达NVIDIA",
"model": "RTX 4500 Ada",
"price": 12999,
"description": "3D建模渲染设计绘图专业图形显卡24G DDR6显存工包全新",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/208603/27/48624/44037/673aafd1Fa656fdb4/699ccf6a6e557e8b.jpg",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX Ada系列\",\"memory\":\"24GB\",\"memory_type\":\"GDDR6\",\"purpose\":\"3D建模,渲染,设计绘图\"}"
},
{
"name": "英伟达RTX3060 12G公版涡轮",
"brand": "英伟达",
"model": "RTX 3060",
"price": 1769,
"description": "全新定制涡轮AI深度学习大模型专业显卡12G显存公版涡轮",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/305217/35/9785/68666/684a95dbFa16c4705/e3e3055a86556924.jpg",
"stock": 42,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"12GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\",\"design\":\"公版\"}"
},
{
"name": "华硕七彩虹技嘉微星RX580 8G",
"brand": "华硕,七彩虹,技嘉,微星",
"model": "RX 580",
"price": 369,
"description": "台式电脑独立显卡游戏设计师办公二手拆机卡8G显存618年中大促预计到手369元",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/320441/16/10281/111856/68536cacF3d3b0c15/77721a9daafbba26.jpg",
"stock": 27,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RX 500系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"purpose\":\"游戏,设计,办公\",\"condition\":\"二手拆机卡\"}"
},
{
"name": "英伟达NVIDIAQuadro K620 2G",
"brand": "英伟达NVIDIA",
"model": "Quadro K620",
"price": 260,
"description": "专业图形设计建模渲染显卡2G显存全新工业包装开发票联系客服",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/191802/32/35612/79768/65e549b4Fe3b9ae52/1a0fbfeaf98b2887.jpg",
"stock": 42,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro K系列\",\"memory\":\"2GB\",\"memory_type\":\"不详\",\"purpose\":\"图形设计,建模,渲染\"}"
},
{
"name": "英伟达RTX4060Ti 16G涡轮",
"brand": "英伟达",
"model": "RTX 4060 Ti",
"price": 3659,
"description": "全新定制涡轮AI深度学习大模型专业显卡16G显存",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/302327/36/13101/45200/684a9616Fc4ed25b2/501957d90178e7c7.jpg",
"stock": 15,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\"}"
},
{
"name": "英伟达NVIDIARTX3080 10G涡轮公版",
"brand": "英伟达NVIDIA",
"model": "RTX 3080",
"price": 2629,
"description": "涡轮公版适用于4/8卡服务器工作站AI推理训练专业图形显卡10G显存现货不开发票",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/314523/4/11024/82630/685609aeF87931a15/d49584f4d8a97af0.png",
"stock": 11,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"10GB\",\"memory_type\":\"不详\",\"purpose\":\"AI推理训练,专业图形,服务器工作站\",\"cooling\":\"涡轮散热\",\"design\":\"公版\"}"
},
{
"name": "宽科 NVIDIA A6000 48G",
"brand": "宽科",
"model": "NVIDIA A6000",
"price": 41999,
"description": "专业图形显卡48G显存工包高性能GPU显卡可按需定制支持对公专票/13%增值税/Ubuntu/Centos/Win",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/258497/19/971/52452/6765749fF5f2ba950/1580ade88b428c97.jpg",
"stock": 14,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX A系列\",\"memory\":\"48GB\",\"memory_type\":\"不详\",\"purpose\":\"专业图形\"}"
},
{
"name": "英伟达(NVIDIA)GeForce RTX 5070 Founders Edition 12g",
"brand": "英伟达(NVIDIA)",
"model": "GeForce RTX 5070 Founders Edition",
"price": 5699,
"description": "全新国行公版显卡12G显存",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/281649/36/10745/41220/67e54099F52979978/dfdc1795bfaba778.png",
"stock": 30,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"12GB\",\"memory_type\":\"不详\",\"design\":\"Founders Edition(公版)\"}"
},
{
"name": "铭瑄MAXSUNGeForce MS-RTX3050 TR 6G DLSS",
"brand": "铭瑄MAXSUN",
"model": "GeForce MS-RTX3050 TR",
"price": 1296.4,
"description": "6G GDDR6显存性能提升太极双9CM刀锋扇叶强化散热支持DLSS AI加速家用办公娱乐装机优选三年质保支持个人送保",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/314823/6/11194/121567/685519bdF5f516fea/ede7f83132677c86.jpg",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"6GB\",\"memory_type\":\"GDDR6\",\"features\":\"DLSS AI加速\",\"cooling\":\"双风扇\",\"purpose\":\"游戏,专业设计,直播,家用办公娱乐\"}"
},
{
"name": "耕升GeForce RTX5070踏雪 OC",
"brand": "耕升",
"model": "GeForce RTX 5070 踏雪 OC",
"price": 4919,
"description": "支持DLSS4支持DeepSeek电竞游戏台式机电脑GM游戏显卡",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/277659/5/19271/82285/67f8ac6fF8429a183/623b87cfd69c05f9.jpg",
"stock": 59,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"不详\",\"memory_type\":\"不详\",\"features\":\"DLSS4,DeepSeek,OC\",\"purpose\":\"电竞游戏\"}"
},
{
"name": "华硕七彩虹技嘉微星RX580 8G",
"brand": "华硕,七彩虹,技嘉,微星",
"model": "RX 580",
"price": 369,
"description": "台式电脑独立显卡游戏设计师办公二手拆机卡8G显存618年中大促预计到手369元",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/320441/16/10281/111856/68536cacF3d3b0c15/77721a9daafbba26.jpg",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RX 500系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"purpose\":\"游戏,设计,办公\",\"condition\":\"二手拆机卡\"}"
},
{
"name": "英伟达RTX4060Ti 16G涡轮",
"brand": "英伟达",
"model": "RTX 4060 Ti",
"price": 3659,
"description": "全新定制涡轮AI深度学习大模型专业显卡16G显存",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/302327/36/13101/45200/684a9616Fc4ed25b2/501957d90178e7c7.jpg",
"stock": 46,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\"}"
},
{
"name": "英伟达NVIDIARTX4060TI 16G 涡轮版",
"brand": "英伟达NVIDIA",
"model": "RTX 4060 Ti",
"price": 3653,
"description": "16G显存涡轮版现货",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/284589/20/10886/58185/67e6755dF258de9f7/140c61650c896457.jpg",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\",\"cooling\":\"涡轮散热\"}"
},
{
"name": "英伟达NVIDIARTX 3080TI 12G 涡轮版",
"brand": "英伟达NVIDIA",
"model": "RTX 3080 Ti",
"price": 3709,
"description": "原厂公版双宽适用于4卡/8卡服务器工作站GPU12G显存涡轮版不开发票",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/288083/3/15554/55900/68564feeF651232ac/5dfdcccc62dde191.jpg",
"stock": 42,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"12GB\",\"memory_type\":\"不详\",\"purpose\":\"服务器工作站GPU\",\"cooling\":\"涡轮散热\",\"design\":\"公版\",\"width\":\"双宽\"}"
},
{
"name": "英伟达RTX3080 20G公版涡轮",
"brand": "英伟达",
"model": "RTX 3080",
"price": 3499,
"description": "全新定制涡轮AI深度学习大模型专业显卡20G显存公版涡轮",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/316702/31/8516/48302/684a95fcFc2f0ae8e/2f2c9ba9cd33b34d.jpg",
"stock": 42,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"20GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\",\"design\":\"公版\"}"
},
{
"name": "英伟达NVIDIARTX A400 4GB GDDR6",
"brand": "英伟达NVIDIA",
"model": "RTX A400",
"price": 1246.5,
"description": "4GB GDDR6显存平面设计多屏输出专业图形显卡工业包装",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/129608/8/47496/69102/670f832eFbbf5ce0a/59dd665d134d3cbf.jpg",
"stock": 31,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX A系列\",\"memory\":\"4GB\",\"memory_type\":\"GDDR6\",\"purpose\":\"平面设计,多屏输出,专业图形\"}"
},
{
"name": "英伟达 NVIDAI RTX T1000 8G 原厂盒装",
"brand": "英伟达 NVIDAI",
"model": "RTX T1000",
"price": 2799,
"description": "3D建模设计绘图台式机专业图形显卡8G显存原厂盒装全新",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/168328/16/51437/71397/673ae49dF3dbee7bd/40558aa5d49adf54.jpg",
"stock": 27,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX T系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"purpose\":\"3D建模,设计绘图,专业图形\"}"
},
{
"name": "影驰 GeForce RTX 5060 Ti 经典大将 8GB GDDR7",
"brand": "影驰",
"model": "GeForce RTX 5060 Ti 经典大将",
"price": 3192.6,
"description": "8GB GDDR7显存支持DLSS 4电竞光追游戏/AI本地部署显卡",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/298179/27/16038/131916/68512f28Fafd94934/a3ba958813802dc0.jpg",
"stock": 47,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"GDDR7\",\"features\":\"DLSS 4,光线追踪\",\"purpose\":\"电竞游戏,AI本地部署\"}"
},
{
"name": "万竞 GTX1060 6G 百战-至龙",
"brand": "万竞",
"model": "GTX 1060",
"price": 788,
"description": "高端独立显卡全新盒装适用于电脑黑神话悟空游戏设计渲染学习台式显卡6G显存",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/273641/39/22652/148775/6801f82fF8bfa62d5/bf69574555e4f402.jpg",
"stock": 30,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"GTX 10系列\",\"memory\":\"6GB\",\"memory_type\":\"不详\",\"purpose\":\"游戏,设计,渲染,学习\"}"
},
{
"name": "英伟达NVIDIARTX A1000 8GB GDDR6",
"brand": "英伟达NVIDIA",
"model": "RTX A1000",
"price": 2993,
"description": "8GB GDDR6显存视频剪辑建模渲染专业图形显卡工业包装",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/96026/11/45380/72560/670f89b2Fa2f18ac2/cc3ba86ddf32d153.jpg",
"stock": 14,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX A系列\",\"memory\":\"8GB\",\"memory_type\":\"GDDR6\",\"purpose\":\"视频剪辑,建模,渲染,专业图形\"}"
},
{
"name": "英伟达(NVIDIA) P400 2G",
"brand": "英伟达(NVIDIA)",
"model": "P400",
"price": 256,
"description": "2G显存现货",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/273370/40/16973/75655/67f4cb3bF2cc60132/cae804fc089cb8a2.jpg",
"stock": 15,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro P系列\",\"memory\":\"2GB\",\"memory_type\":\"不详\"}"
},
{
"name": "英伟达RTX3080 20G公版涡轮",
"brand": "英伟达",
"model": "RTX 3080",
"price": 3499,
"description": "全新定制涡轮AI深度学习大模型专业显卡20G显存公版涡轮",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/280498/35/6001/54260/67dbe352F0223e061/cda01b396f228073.jpg",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"20GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\",\"design\":\"公版\"}"
},
{
"name": "英伟达NVIDIARTX 2000 Ada 16GB GDDR6",
"brand": "英伟达NVIDIA",
"model": "RTX 2000 Ada",
"price": 5117.74,
"description": "16GB GDDR6显存专业显卡工业包装",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/11671/9/24414/34958/66dfc776Fec2f18d0/6ccc24633289eddb.jpg",
"stock": 15,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX Ada系列\",\"memory\":\"16GB\",\"memory_type\":\"GDDR6\",\"purpose\":\"专业显卡\"}"
},
{
"name": "NVIDIA英伟达 NVIDIA RTX4090 涡轮显卡 AI深度学习 显存48G定制",
"brand": "NVIDIA英伟达",
"model": "RTX 4090",
"price": 3600,
"description": "24G升级置换48G升级费涡轮显卡AI深度学习工业包装定制",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/279637/24/16558/53741/67f35745F784e0439/b76ddaec4521c860.png",
"stock": 26,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"48GB(升级后)\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习\",\"cooling\":\"涡轮散热\",\"customization\":\"显存升级\"}"
},
{
"name": "英伟达珑京 NVIDIA TESLA L40 48G",
"brand": "英伟达珑京 NVIDIA",
"model": "TESLA L40",
"price": 49999,
"description": "AI深度学习训练推理GPU服务器配件专业计算显卡48G显存",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/221707/10/43823/39580/673ae857Fa0e9e0e9/601ee3533ebe6b44.jpg",
"stock": 47,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Tesla\",\"memory\":\"48GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,训练,推理,服务器配件,专业计算\"}"
},
{
"name": "英伟达P1000 4G",
"brand": "英伟达",
"model": "Quadro P1000",
"price": 788,
"description": "专业显卡4G显存工业包装全新不含发票",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/190933/35/52794/38916/673ead61Fe18c9294/a0273a7215e908b1.jpg",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"Quadro P系列\",\"memory\":\"4GB\",\"memory_type\":\"不详\",\"purpose\":\"专业显卡\"}"
},
{
"name": "微星MSI幻影师 GeForce RTX 5060 Ti 8G SHADOW 2X OC PLUS",
"brand": "微星MSI",
"model": "GeForce RTX 5060 Ti 幻影师 SHADOW 2X OC PLUS",
"price": 3192.6,
"description": "8G显存电竞游戏设计专业电脑显卡",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/287994/2/10420/70695/6837f286F556dbf2c/ea7eb0fdb1d41bd5.png",
"stock": 43,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"features\":\"OC PLUS\",\"purpose\":\"电竞游戏,设计专业\"}"
},
{
"name": "盈通yestonGeForce RTX 5060 8GB GDDR7 大地之神 GA",
"brand": "盈通yeston",
"model": "GeForce RTX 5060 大地之神 GA",
"price": 2543.9,
"description": "8GB GDDR7显存电竞光追游戏AI智能学习电脑独立显卡",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/320405/23/6913/71982/68429145Fc92a087c/29d4fd849a9b5393.jpg",
"stock": 14,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"GDDR7\",\"features\":\"光线追踪,AI智能学习\",\"purpose\":\"电竞游戏,AI智能学习\"}"
},
{
"name": "英伟达(NVIDIA) RTX5000 16G",
"brand": "英伟达(NVIDIA)",
"model": "RTX 5000",
"price": 5637,
"description": "16G显存现货",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/279710/20/14310/60834/67ecfa60F34a6ff7f/be1dd3f5e277c4e9.jpg",
"stock": 31,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX系列\",\"memory\":\"16GB\",\"memory_type\":\"不详\"}"
},
{
"name": "电竞叛客 RTX5060 X2W OC 8G",
"brand": "电竞叛客",
"model": "RTX 5060 X2W OC",
"price": 2599,
"description": "新品8G显存全新架构白色甜品电竞游戏台式电脑独立显卡",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/307753/20/2202/103414/682ae0b2F7cf830a0/858c05238bea3500.jpg",
"stock": 30,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"features\":\"OC\",\"purpose\":\"电竞游戏\",\"architecture\":\"全新架构\",\"color\":\"白色\"}"
},
{
"name": "英伟达RTX3060ti 8G 公版涡轮",
"brand": "英伟达",
"model": "RTX 3060 Ti",
"price": 1979,
"description": "全新定制涡轮AI深度学习大模型专业显卡8G显存公版涡轮",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/256193/39/23995/71305/67b9b319F39021ef4/6c96d653d9259404.jpg",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 30系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"purpose\":\"AI深度学习,大模型\",\"cooling\":\"涡轮散热\",\"design\":\"公版\"}"
},
{
"name": "电竞叛客 RTX 5060 Ti X3W 8G",
"brand": "电竞叛客",
"model": "RTX 5060 Ti X3W",
"price": 3449,
"description": "8G显存全新架构白色甜品电竞游戏台式电脑独立显卡RTX5060Ti新品首发",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/304731/10/7953/111660/68410a84Fea969450/9c66969584c535d6.jpg",
"stock": 31,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"features\":\"OC\",\"purpose\":\"电竞游戏\",\"architecture\":\"全新架构\",\"color\":\"白色\"}"
},
{
"name": "英伟达NVIDIA RTX PRO 6000 96G",
"brand": "英伟达NVIDIA",
"model": "RTX 6000 Ada",
"price": 58999,
"description": "顶级专业显卡96G显存支持vGPU软件适用于AI训练/8K渲染/深度学习,工作站服务器专用,工包",
"imageUrl": "",
"stock": 46,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX Ada系列\",\"memory\":\"96GB\",\"memory_type\":\"不详\",\"features\":\"vGPU支持\",\"purpose\":\"AI训练,8K渲染,深度学习,工作站服务器专用\"}"
},
{
"name": "英伟达NvidiaNVIDIA 4060TI-8G公版FE",
"brand": "英伟达Nvidia",
"model": "NVIDIA 4060 Ti Founders Edition",
"price": 3499,
"description": "8G显存公版原厂FE涡轮显卡",
"imageUrl": "",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"design\":\"Founders Edition(公版)\",\"cooling\":\"涡轮散热\"}"
},
{
"name": "电竞叛客 GeForce RTX 5060 Ti X3W 8GB",
"brand": "电竞叛客",
"model": "GeForce RTX 5060 Ti X3W",
"price": 3449,
"description": "8G显存台式机电脑显卡支持DLSS 4电竞游戏AI运算deepseek人工智能独立显卡",
"imageUrl": "",
"stock": 14,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 50系列\",\"memory\":\"8GB\",\"memory_type\":\"不详\",\"features\":\"DLSS 4,deepseek AI运算\",\"purpose\":\"电竞游戏,AI运算,人工智能\"}"
},
{
"name": "七彩虹4060ultra 双风扇",
"brand": "七彩虹",
"model": "RTX 4060 Ultra",
"price": 2099,
"description": "电竞游戏显卡,质保一年,只换不修,拆机显卡,双风扇",
"imageUrl": "",
"stock": 30,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"不详\",\"memory_type\":\"不详\",\"cooling\":\"双风扇\",\"warranty\":\"质保一年,只换不修\",\"condition\":\"拆机显卡\"}"
},
{
"name": "英伟达(NVIDIA)RTX A6000 48G",
"brand": "英伟达(NVIDIA)",
"model": "RTX A6000",
"price": 42806,
"description": "48G显存现货",
"imageUrl": "",
"stock": 46,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX A系列\",\"memory\":\"48GB\",\"memory_type\":\"不详\"}"
},
{
"name": "耕升GAINWARD GeForce RTX 4060 Ti 踏雪 8G",
"brand": "耕升GAINWARD",
"model": "GeForce RTX 4060 Ti 踏雪",
"price": 2698,
"description": "8G GDDR6显存支持DLSS 3专业设计绘画AI制图电竞游戏电脑台式机显卡",
"imageUrl": "",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"8GB\",\"memory_type\":\"GDDR6\",\"features\":\"DLSS 3\",\"purpose\":\"专业设计,绘画,AI制图,电竞游戏\"}"
},
{
"name": "七彩虹ColorfulRTX 4060 U W DUO OC 8G",
"brand": "七彩虹Colorful",
"model": "RTX 4060 U W DUO OC",
"price": 2599,
"description": "8G GDDR6显存白色战斧系列电竞台式机游戏显卡",
"imageUrl": "",
"stock": 11,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 40系列\",\"memory\":\"8GB\",\"memory_type\":\"GDDR6\",\"features\":\"OC\",\"cooling\":\"双风扇\",\"color\":\"白色\",\"purpose\":\"电竞游戏\"}"
},
{
"name": "七彩虹 RTX2060 6G",
"brand": "七彩虹",
"model": "RTX 2060",
"price": 975,
"description": "游戏显卡6G显存拒绝矿卡拆机精品",
"imageUrl": "",
"stock": 10,
"typeName": "显卡",
"specifications": "{\"GPU_series\":\"RTX 20系列\",\"memory\":\"6GB\",\"memory_type\":\"不详\",\"purpose\":\"游戏\",\"condition\":\"拆机精品,非矿卡\"}"
}
]

View File

@ -0,0 +1,398 @@
[
{
"name": "雷克沙Lexar 雷神铠 DDR4台式机内存条 8G 3200 3600频率 马甲条 3200 皓月白 8GB 1条",
"brand": "雷克沙Lexar",
"model": "雷神铠",
"price": 199,
"description": "DDR4台式机内存条8G3200/3600频率马甲条皓月白",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/47315/39/25669/79542/66ab7034F816dd189/6e50eadbe86976ea.jpg",
"stock": 42,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"8GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\",\"color\":\"皓月白\"}"
},
{
"name": "七彩虹ColorfulDDR3L 8G 1600 笔记本内存条",
"brand": "七彩虹Colorful",
"model": "DDR3L",
"price": 44.91,
"description": "DDR3L笔记本内存条8G1600频率",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/279183/1/545/97174/67ceb4f8F8833714c/e0975d50472bf7ba.jpg",
"stock": 10,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3L\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"笔记本\"}"
},
{
"name": "联想Lenovo16GB DDR4 3200 笔记本内存条 助力AI 适配黑神话悟空",
"brand": "联想Lenovo",
"model": "",
"price": 228.54,
"description": "16GB DDR4 3200笔记本内存条助力AI适配黑神话悟空",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/243597/39/4822/86937/65e1a1daF3b8aa636/8070623b0a1046d8.jpg",
"stock": 14,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"16GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"笔记本\"}"
},
{
"name": "七彩虹(Colorful)16GB DDR4 3200 台式机内存条 战斧系列·钛金灰 CL18",
"brand": "七彩虹(Colorful)",
"model": "战斧系列·钛金灰",
"price": 198.6,
"description": "16GB DDR4 3200台式机内存条CL18升级大容量铝制马甲散热稳定3200XMP高频",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/91655/4/48798/88364/66b2effcFe0380d3e/b8e2bffcace0f9b3.jpg",
"stock": 15,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"16GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"CL18\",\"color\":\"钛金灰\",\"features\":\"铝制马甲,XMP高频\"}"
},
{
"name": "雷克沙Lexar DDR4台式机电脑内存条8g 16g 32G 3200 DDR4 3200 8GB 1条",
"brand": "雷克沙Lexar",
"model": "",
"price": 139,
"description": "DDR4台式机电脑内存条8GB3200频率",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/132145/6/23419/116266/6200dd47E767f5c2d/ce3aefac7effa2b8.jpg",
"stock": 11,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"8GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\"}"
},
{
"name": "Crucial英睿达 16GB DDR5 5600频率 笔记本内存条 美光(原镁光)原厂颗粒 AI电脑配件",
"brand": "Crucial英睿达",
"model": "",
"price": 298.4,
"description": "16GB DDR5 5600频率笔记本内存条美光原厂颗粒AI电脑配件Deepseek硬件AI大模型硬件稳定兼容即插即用",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/295996/8/14031/192479/685521b5Ff5610f6b/58a38d973f6c9736.jpg",
"stock": 47,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"5600MHz\",\"formFactor\":\"笔记本\",\"chipBrand\":\"美光\"}"
},
{
"name": "三星 SAMSUNG 笔记本内存条 16G DDR5 5600频率",
"brand": "三星 SAMSUNG",
"model": "",
"price": 378.24,
"description": "笔记本内存条16G DDR5 5600频率",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/301698/23/16241/162476/68558330F22ecf9d2/c61b042395e0f801.jpg",
"stock": 46,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"5600MHz\",\"formFactor\":\"笔记本\"}"
},
{
"name": "光威Gloway8GB DDR3 1600 台式机内存条 战将系列",
"brand": "光威Gloway",
"model": "战将系列",
"price": 48.9,
"description": "8GB DDR3 1600台式机内存条国产存储品牌七天免费试用终身质保稳定兼容以换代修",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/225216/31/27171/30806/6708c2f8Fc474cd58/299b5ac28dbfc23c.jpg",
"stock": 31,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"台式机\"}"
},
{
"name": "2017/19/20款苹果一体机内存条",
"brand": "苹果",
"model": "",
"price": 199,
"description": "适用于2017/19/20款苹果一体机内存条",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/187604/9/28251/98223/63213aaaEc705a73e/d00d9bec81925125.jpg",
"stock": 30,
"typeName": "内存",
"specifications": "{\"compatibleModels\":\"2017/2019/2020款苹果一体机\"}"
},
{
"name": "七彩虹Colorful16GB DDR5 5600 笔记本内存条",
"brand": "七彩虹Colorful",
"model": "",
"price": 254.49,
"description": "16GB DDR5 5600笔记本内存条精选优质颗粒DDR5良好兼容性16G大容量5600频率强悍性能",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/246047/6/1734/82415/6593b9ecF9b9e471e/0ee7492a6c312569.jpg",
"stock": 15,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"5600MHz\",\"formFactor\":\"笔记本\"}"
},
{
"name": "金百达KINGBANK8GB DDR3 1600 台式机内存条",
"brand": "金百达KINGBANK",
"model": "",
"price": 58.88,
"description": "8GB DDR3 1600台式机内存条企业购优选款高性价比助您电脑运行速度快人一步",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/304879/28/11595/189098/68537b29Ff82a6ebe/a8d770c64e1ee12b.jpg",
"stock": 26,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"台式机\"}"
},
{
"name": "金百达KINGBANK16GB DDR5 5600 笔记本内存条 适配迷你主机 可组双通道",
"brand": "金百达KINGBANK",
"model": "",
"price": 238.52,
"description": "16GB DDR5 5600笔记本内存条适配迷你主机可组双通道",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/300989/36/16282/198808/68537c7dFfaf1e090/2d40356779513589.jpg",
"stock": 14,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"5600MHz\",\"formFactor\":\"笔记本\",\"features\":\"支持双通道,适配迷你主机\"}"
},
{
"name": "金泰克kimtigo【12期免息】8G/16G DDR4 3200MHz 台式机电脑内存条磐虎系列 16G 3200MHz",
"brand": "金泰克kimtigo",
"model": "磐虎系列",
"price": 259,
"description": "16G DDR4 3200MHz台式机电脑内存条磐虎系列",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/303762/23/6563/131186/6839a9bdF8f0e63db/ec08f8117daff86f.jpg",
"stock": 30,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"16GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\"}"
},
{
"name": "金百达KINGBANK32GB(16GBX2)套装 DDR4 3200 台式机内存条 银爵 C16 适配黑神话悟空",
"brand": "金百达KINGBANK",
"model": "银爵 C16",
"price": 517.96,
"description": "32GB(16GBX2)套装 DDR4 3200台式机内存条银爵 C16适配黑神话悟空电竞京选高效散热片强悍性能电竞游戏一爽到底",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/303165/40/15188/159962/6853c04fFa3cc0b6b/817331a42f6e2c1a.jpg",
"stock": 26,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"32GB (16GBx2)\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"C16\",\"features\":\"高效散热片,适配黑神话悟空\"}"
},
{
"name": "Kingston金士顿8g 1600 4g 16Gb 2666台式机3/4代DDR3/4内存条95新 金士顿DDR3-4G-1600 DDR3【三年保】",
"brand": "Kingston金士顿",
"model": "",
"price": 13.7,
"description": "金士顿DDR3-4G-1600台式机内存条95新三年保",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/253594/19/1234/73799/6762ccf0F8a56f911/35a62eaa0927d076.jpg",
"stock": 31,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3\",\"capacity\":\"4GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"台式机\",\"condition\":\"95新\"}"
},
{
"name": "金百达KINGBANK16GB(8GBX2)套装 DDR4 3200 台式机内存条 银爵 C16 适配黑神话悟空",
"brand": "金百达KINGBANK",
"model": "银爵 C16",
"price": 278.44,
"description": "16GB(8GBX2)套装 DDR4 3200台式机内存条银爵 C16适配黑神话悟空电竞京选高效散热片强悍性能电竞游戏一爽到底",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/311461/17/10782/158725/6853a4fdF25dc2e4e/ae851f75de6f780b.jpg",
"stock": 11,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"16GB (8GBx2)\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"C16\",\"features\":\"高效散热片,适配黑神话悟空\"}"
},
{
"name": "倍控倍控ikuaios软路由N5105工控机N100电脑迷你整机服务器云桌面2.5G网卡2500M防火墙7505 准系统+电源 G31黑N100六网226-DDR5内存M.2接口",
"brand": "倍控",
"model": "ikuaios软路由N5105工控机N100",
"price": 840,
"description": "准系统+电源 G31黑N100六网226-DDR5内存M.2接口",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/186259/36/41641/80590/6556d355F527fa076/bbe11cdad293fd34.jpg",
"stock": 47,
"typeName": "其他",
"specifications": "{\"CPU\":\"N100\",\"networkPorts\":\"六网226\",\"memoryType\":\"DDR5\",\"storageInterface\":\"M.2\",\"formFactor\":\"迷你整机\"}"
},
{
"name": "威刚ADATA8GB DDR4 3200 台式机内存 万紫千红",
"brand": "威刚ADATA",
"model": "万紫千红",
"price": 138.72,
"description": "8GB DDR4 3200台式机内存品质铸就经典3200高频万紫千红严选颗粒、稳定兼容",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/298599/39/12677/70486/683e8278F29fca227/edb538867f3a58c3.jpg",
"stock": 14,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"8GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\"}"
},
{
"name": "联想Lenovo8GB DDR3 1600 笔记本内存条 低电压版",
"brand": "联想Lenovo",
"model": "",
"price": 58.88,
"description": "8GB DDR3 1600笔记本内存条低电压版",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/198038/22/52302/51999/6752a045F21ab1fe8/0eef83af1bd83b6a.jpg",
"stock": 30,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"笔记本\",\"voltage\":\"低电压\"}"
},
{
"name": "联想Lenovo 拯救者Y系列16GB DDR5 5600频率 笔记本内存条 三星原厂颗粒 AI电脑配件",
"brand": "联想Lenovo",
"model": "拯救者Y系列",
"price": 298.4,
"description": "16GB DDR5 5600频率笔记本内存条三星原厂颗粒AI电脑配件",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/314906/3/11328/133170/68561c0bFceb6af27/ece52de63f9f6de5.jpg",
"stock": 11,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"5600MHz\",\"formFactor\":\"笔记本\",\"chipBrand\":\"三星\"}"
},
{
"name": "金士顿Kingston ddr4内存条 Beast野兽系列 Fury骇客神条 台式机电脑内存条 DDR4 2666 8G 台式内存",
"brand": "金士顿Kingston",
"model": "Beast野兽系列 Fury骇客神条",
"price": 199,
"description": "DDR4 2666 8G台式机内存条",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/113571/27/45104/57542/65e17e36F01edc0da/d08de790bad94acf.jpg",
"stock": 10,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"8GB\",\"frequency\":\"2666MHz\",\"formFactor\":\"台式机\"}"
},
{
"name": "Crucial英睿达 16GB DDR5 4800频率 笔记本内存条 美光(原镁光)原厂颗粒 AI电脑配件",
"brand": "Crucial英睿达",
"model": "",
"price": 278.44,
"description": "16GB DDR5 4800频率笔记本内存条美光原厂颗粒AI电脑配件Deepseek硬件AI大模型硬件稳定兼容即插即用",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/306154/20/12168/187990/685521b7Fb557a4fc/0b85bfb5485f4abf.jpg",
"stock": 42,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"4800MHz\",\"formFactor\":\"笔记本\",\"chipBrand\":\"美光\"}"
},
{
"name": "金百达KINGBANK32GB(16G×2)套装 DDR4 3600 台式机内存条 海力士原装CJR/DJR颗粒 银爵 C18",
"brand": "金百达KINGBANK",
"model": "银爵 C18",
"price": 537.92,
"description": "32GB(16G×2)套装 DDR4 3600台式机内存条海力士原装CJR/DJR颗粒银爵 C18",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/291398/27/11831/161925/6853a5e7F23c98867/e7ba3dd1633c5739.jpg",
"stock": 11,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"32GB (16GBx2)\",\"frequency\":\"3600MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"C18\",\"chipBrand\":\"海力士 (CJR/DJR)\"}"
},
{
"name": "阿斯加特Asgard32GB(16GBx2) DDR5 6000 台式机内存条 女武神·瓦尔基里II代 RGB灯条 海力士A-die CL28 极夜黑",
"brand": "阿斯加特Asgard",
"model": "女武神·瓦尔基里II代",
"price": 977.04,
"description": "32GB(16GBx2) DDR5 6000台式机内存条RGB灯条海力士A-die CL28极夜黑AMDX3D黄金搭档海力士A-die颗粒6000C28低时序10层PCB加强散热支持Intel-XMP3.0/AMD平台",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/303516/30/13712/151928/68529d27F8fef93e7/09ba9a95acc22686.jpg",
"stock": 59,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"32GB (16GBx2)\",\"frequency\":\"6000MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"CL28\",\"chipBrand\":\"海力士 A-die\",\"features\":\"RGB灯条,10层PCB,支持XMP3.0\"}"
},
{
"name": "雷克沙Lexar 内存条DDR4 3200笔记本电脑内存8g 16g 32G 可选 DDR4 3200 16GB 1条",
"brand": "雷克沙Lexar",
"model": "",
"price": 239,
"description": "DDR4 3200笔记本电脑内存条16GB",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/20331/16/19176/95764/6347808fEe5b3e83e/907c7bd9e89e7ff1.jpg",
"stock": 42,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"16GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"笔记本\"}"
},
{
"name": "三星SAMSUNG 内存条16g/32g DDR5 4800/5600 频率 五代笔记本内存条原厂 三星DDR5 16G 笔记本内存条 DDR5 5600",
"brand": "三星SAMSUNG",
"model": "",
"price": 329,
"description": "三星DDR5 16G笔记本内存条DDR5 5600频率支持12代英特尔cpu1.1V工作电压ECC技术保持数据完整持续工作稳定",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/113633/37/36443/100448/64632309Fcce3d958/15aeade9178acca3.jpg",
"stock": 30,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"5600MHz\",\"formFactor\":\"笔记本\",\"voltage\":\"1.1V\",\"features\":\"支持ECC技术\",\"compatibleCPU\":\"12代英特尔CPU\"}"
},
{
"name": "金士顿/华硕/品牌拆机 2GB 4GB 8GB 3代4代内存 1600 2400 内存条台式机内存 骇客神条 DDR3 8G 1600",
"brand": "金士顿/华硕",
"model": "骇客神条",
"price": 49.9,
"description": "DDR3 8G 1600台式机内存条拆机件",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/220807/18/22227/88952/63eaeecfF4f221398/838005d27d235cb2.jpg",
"stock": 59,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"台式机\",\"condition\":\"拆机件\"}"
},
{
"name": "宏碁掠夺者PREDATOR48G(24G×2)套装 DDR5 6000频率 台式机内存条 Hermes冰刃系列 RGB灯条(C28) 石耀黑 AI电脑配件",
"brand": "宏碁掠夺者PREDATOR",
"model": "Hermes冰刃系列",
"price": 1096.8,
"description": "48G(24G×2)套装 DDR5 6000频率台式机内存条RGB灯条(C28)石耀黑AI电脑配件",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/283871/1/24943/77867/68085621Fca8e1628/b40d730366fe6454.jpg",
"stock": 42,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"48GB (24GBx2)\",\"frequency\":\"6000MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"C28\",\"features\":\"RGB灯条\",\"color\":\"石耀黑\"}"
},
{
"name": "海力士 8G DDR3L 1600 低电压笔记本内存",
"brand": "海力士",
"model": "",
"price": 80,
"description": "8G DDR3L 1600低电压笔记本内存",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/191103/21/17752/774694/61122699Eb1a077ea/368d3a0f71eef4eb.jpg",
"stock": 26,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3L\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"笔记本\",\"voltage\":\"低电压\"}"
},
{
"name": "七彩虹Colorful8GB DDR3 1600 台式机内存 普条系列 1.35V 低压",
"brand": "七彩虹Colorful",
"model": "普条系列",
"price": 54.89,
"description": "8GB DDR3 1600台式机内存1.35V低压精选优质颗粒DDR3良好兼容性增容好帮手8G增容1600良好频率",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/260168/26/21775/100817/67b59b90F64d3a9b6/093912a6785b3adc.jpg",
"stock": 42,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"台式机\",\"voltage\":\"1.35V\",\"features\":\"低压\"}"
},
{
"name": "长城Great Wall16GB DDR4 3200频率 台式机内存条马甲条 Intel专用条龙鳞黑金系列",
"brand": "长城Great Wall",
"model": "龙鳞黑金系列",
"price": 158.68,
"description": "16GB DDR4 3200频率台式机内存条马甲条Intel专用条",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/149717/12/47222/70232/67189a9dF79d90f06/4e1dba4f76088121.jpg",
"stock": 26,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"16GB\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\",\"features\":\"马甲条,Intel专用\"}"
},
{
"name": "光威Gloway8GB DDR3L 1600 笔记本内存条 战将系列 低电压版",
"brand": "光威Gloway",
"model": "战将系列",
"price": 54.89,
"description": "8GB DDR3L 1600笔记本内存条低电压版国产存储品牌七天免费试用终身质保稳定兼容以换代修",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/249595/31/20688/42201/6708bf71Fba8f2edb/f5771c5fa69f0709.jpg",
"stock": 26,
"typeName": "内存",
"specifications": "{\"type\":\"DDR3L\",\"capacity\":\"8GB\",\"frequency\":\"1600MHz\",\"formFactor\":\"笔记本\",\"voltage\":\"低电压\"}"
},
{
"name": "倍控倍控ikuaios软路由N5105工控机N100电脑迷你整机服务器云桌面2.5G网卡2500M防火墙7505 准系统+电源 G31黑N100六网226-DDR5内存M.2接口",
"brand": "倍控",
"model": "ikuaios软路由N5105工控机N100",
"price": 840,
"description": "准系统+电源 G31黑N100六网226-DDR5内存M.2接口",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/186259/36/41641/80590/6556d355F527fa076/bbe11cdad293fd34.jpg",
"stock": 10,
"typeName": "其他",
"specifications": "{\"CPU\":\"N100\",\"networkPorts\":\"六网226\",\"memoryType\":\"DDR5\",\"storageInterface\":\"M.2\",\"formFactor\":\"迷你整机\"}"
},
{
"name": "金百达KINGBANK32GB(16G×2)套装 DDR4 3600 台式机内存条 海力士原装CJR/DJR颗粒 银爵 C18",
"brand": "金百达KINGBANK",
"model": "银爵 C18",
"price": 537.92,
"description": "32GB(16G×2)套装 DDR4 3600台式机内存条海力士原装CJR/DJR颗粒银爵 C18",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/291398/27/11831/161925/6853a5e7F23c98867/e7ba3dd1633c5739.jpg",
"stock": 46,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"32GB (16GBx2)\",\"frequency\":\"3600MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"C18\",\"chipBrand\":\"海力士 (CJR/DJR)\"}"
},
{
"name": "七彩虹Colorful16GB DDR5 4800 笔记本内存条",
"brand": "七彩虹Colorful",
"model": "",
"price": 238.52,
"description": "16GB DDR5 4800笔记本内存条精选优质颗粒DDR5良好兼容性16G大容量4800频率强悍性能",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/237632/5/9544/82472/6593b9d5Fa97f182d/3b753fe1e3c21b72.jpg",
"stock": 26,
"typeName": "内存",
"specifications": "{\"type\":\"DDR5\",\"capacity\":\"16GB\",\"frequency\":\"4800MHz\",\"formFactor\":\"笔记本\"}"
},
{
"name": "金百达KINGBANK32GB(16GBX2)套装 DDR4 3200 台式机内存条 银爵 C16 适配黑神话悟空",
"brand": "金百达KINGBANK",
"model": "银爵 C16",
"price": 517.96,
"description": "32GB(16GBX2)套装 DDR4 3200台式机内存条银爵 C16适配黑神话悟空电竞京选高效散热片强悍性能电竞游戏一爽到底",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/303165/40/15188/159962/6853c04fFa3cc0b6b/817331a42f6e2c1a.jpg",
"stock": 30,
"typeName": "内存",
"specifications": "{\"type\":\"DDR4\",\"capacity\":\"32GB (16GBx2)\",\"frequency\":\"3200MHz\",\"formFactor\":\"台式机\",\"casLatency\":\"C16\",\"features\":\"高效散热片,适配黑神话悟空\"}"
}
]

View File

@ -0,0 +1,178 @@
[
{
"name": "希捷酷鱼固态硬盘1T SATA3.0接口",
"brand": "希捷",
"model": "酷鱼",
"price": 279,
"description": "酷鱼固态硬盘1T SATA3.0接口 台式机笔记本电脑硬盘SSD SATA3接口 512G",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/208931/2/50574/63352/674ec887Fc8d497c5/bd9ea594e4a34578.jpg",
"stock": 15,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"1TB\",\"interface\":\"SATA3.0\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
},
{
"name": "长城Great Wall) 512GB SSD固态硬盘 SATA3.0接口 GW560系列",
"brand": "长城",
"model": "GW560系列",
"price": 188.62,
"description": "512GB SSD固态硬盘 SATA3.0接口 读速540MB/S台式机/笔记本通用",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/319159/21/10201/83910/685286fbFfb80d404/680fab1aed34ff41.jpg",
"stock": 31,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"512GB\",\"interface\":\"SATA3.0\",\"readSpeed\":\"540MB/s\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
},
{
"name": "长城Great Wall) 128GB SSD固态硬盘 SATA3.0接口 GW560系列",
"brand": "长城",
"model": "GW560系列",
"price": 68.86,
"description": "128GB SSD固态硬盘 SATA3.0接口 读速540MB/S台式机/笔记本通用",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/298612/14/13194/78595/685286bfF8fdb0429/62d7fd70a5f1acfc.jpg",
"stock": 47,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"128GB\",\"interface\":\"SATA3.0\",\"readSpeed\":\"540MB/s\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
},
{
"name": "西数/希捷企业级氦气硬盘 500GB",
"brand": "西部数据/希捷",
"model": "企业级氦气硬盘",
"price": 31.8,
"description": "8T 10T 12T 14T 16T 18T企业级氦气硬盘监控录像NAS阵列游戏储存 500GB 西数/希捷随机发货",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/223373/27/31325/91985/6646a42bF6b1963e3/b49443b744fcfe27.jpg",
"stock": 14,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"500GB\",\"type\":\"HDD\",\"interface\":\"SATA\",\"features\":\"氦气盘,企业级,监控,NAS\"}"
},
{
"name": "西部数据WDSSD固态硬盘 SA510 SATA M.2 Blue系列 500G",
"brand": "西部数据WD",
"model": "SA510 Blue",
"price": 339,
"description": "笔记本台式机电脑 SSD固态硬盘 SA510 SATA M.2 Blue系列 3D技术 高速读写 WD Blue SATA | 性能款 500G",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/304134/15/12361/155883/6856a8a0F47d73a5d/2be842b4e9dd85ae.jpg",
"stock": 42,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"500GB\",\"interface\":\"SATA M.2\",\"type\":\"SSD\",\"technology\":\"3D NAND\"}"
},
{
"name": "七彩虹(Colorful) 1TB SSD固态硬盘 SATA3.0接口 SL500系列",
"brand": "七彩虹(Colorful)",
"model": "SL500系列",
"price": 358.28,
"description": "1TB SSD固态硬盘 SATA3.0接口 SL500系列 标准版。出色性能,高性价比,静音运行。大容量大满足,新增盘推荐,游戏畅快无阻",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/114380/15/24069/344213/625fc2acEd61f5899/ed6f86abaca42556.jpg",
"stock": 10,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"1TB\",\"interface\":\"SATA3.0\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
},
{
"name": "西部数据WD台式机械硬盘 WD Blue 西数蓝盘 2TB",
"brand": "西部数据WD",
"model": "WD Blue WD20EZBX",
"price": 458.08,
"description": "台式机械硬盘 WD Blue 西数蓝盘 2TB 7200转 256MB SATA 电脑硬盘 3.5英寸 WD20EZBX。机械硬盘超值爆款天天抢。DIY装机升级理想选择低功耗、经久耐用",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/315254/1/11219/140818/68557e2cF209b54e8/6fc913d11b9ce990.jpg",
"stock": 31,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"2TB\",\"interface\":\"SATA\",\"rotationalSpeed\":\"7200RPM\",\"cache\":\"256MB\",\"type\":\"HDD\",\"formFactor\":\"3.5英寸\"}"
},
{
"name": "希捷SEAGATE台式机硬盘 2TB 希捷酷鱼系列",
"brand": "希捷SEAGATE",
"model": "酷鱼 ST2000DM008",
"price": 448.1,
"description": "台式机硬盘 2TB 7200转 256MB 机械硬盘 SATA 希捷酷鱼系列 电脑硬盘 3.5英寸 ST2000DM008",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/310221/39/11203/123121/68551ad2F577277af/54e628420b8968b8.jpg",
"stock": 47,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"2TB\",\"interface\":\"SATA\",\"rotationalSpeed\":\"7200RPM\",\"cache\":\"256MB\",\"type\":\"HDD\",\"formFactor\":\"3.5英寸\"}"
},
{
"name": "HUWEI通用固态移动硬盘 2TB",
"brand": "HUWEI",
"model": "标准版",
"price": 123.45,
"description": "通用固态移动硬盘1t高速ssd移动固态u盘大容量存储颗粒固态硬盘4t 黑色-标准版 2TB",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/313002/18/1555/94187/68278f08F4dc102ff/c6f1a44b60cf7f11.jpg",
"stock": 15,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"2TB\",\"type\":\"移动固态硬盘\",\"interface\":\"USB\"}"
},
{
"name": "西数企业级监控氦气硬盘 1TB",
"brand": "西部数据",
"model": "企业级监控氦气硬盘",
"price": 127.49,
"description": "1T 8T 10T 12T 14T 16T 18T企业级监控氦气硬盘控录像NAS阵列储存 1TB 西数1tb机械硬盘",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/272632/39/5181/113985/67d8d5acF667120d2/8804d86bc54a1a67.jpg",
"stock": 27,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"1TB\",\"type\":\"HDD\",\"interface\":\"SATA\",\"features\":\"氦气盘,企业级,监控,NAS\"}"
},
{
"name": "七彩虹(Colorful) 512GB SSD固态硬盘 SATA3.0接口 SL500系列",
"brand": "七彩虹(Colorful)",
"model": "SL500系列",
"price": 194.61,
"description": "512GB SSD固态硬盘 SATA3.0接口 SL500系列。爆款力荐主流容量疾速开机游戏。三年质保多平台兼容可靠稳定耐用低耗节能",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/126750/28/28531/348087/625fc27cE063cc028/0544e3b1519b5de5.jpg",
"stock": 43,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"512GB\",\"interface\":\"SATA3.0\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
},
{
"name": "西部数据WDSSD固态硬盘 SN5000 1TB",
"brand": "西部数据WD",
"model": "SN5000",
"price": 449,
"description": "SSD固态硬盘 NVMe M.2接口 AI电脑配件 笔记本电脑游戏硬盘 原厂颗粒不虚标不掉速 SN5000 | PCle4x4 5150MB/s 大容量",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/286485/35/16717/170972/68553164F3aeb2bb8/4362d3e4766f0e05.jpg",
"stock": 47,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"1TB\",\"interface\":\"NVMe M.2\",\"protocol\":\"PCIe 4.0 x4\",\"readSpeed\":\"5150MB/s\",\"type\":\"SSD\"}"
},
{
"name": "TANLR 探路人128GB SSD固态硬盘 SATA3.0接口 镭豹T560系列",
"brand": "TANLR 探路人",
"model": "镭豹T560系列",
"price": 68.86,
"description": "128GB SSD固态硬盘 SATA3.0接口高速TLC颗粒 读速560MB/s 台式机笔记本电脑存储配件",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/292774/17/15991/211279/6853657aF3c50cb29/e52a4c6300ad6f89.jpg",
"stock": 42,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"128GB\",\"interface\":\"SATA3.0\",\"readSpeed\":\"560MB/s\",\"type\":\"SSD\",\"flashType\":\"TLC\"}"
},
{
"name": "光威Gloway512GB SSD固态硬盘 SATA3.0接口 悍将系列",
"brand": "光威Gloway",
"model": "悍将系列",
"price": 168.66,
"description": "512GB SSD固态硬盘 SATA3.0接口 悍将系列。为国产存储崛起而努力。专为提升电脑运行速度设计,大容量高速存储升级优选方案。三年质保!",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/291606/3/14242/107856/6850fac7F6290d7e2/1e74bac4a3823695.jpg",
"stock": 14,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"512GB\",\"interface\":\"SATA3.0\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
},
{
"name": "长城Great Wall) 1TB SSD固态硬盘 SATA3.0接口 GW560系列",
"brand": "长城",
"model": "GW560系列",
"price": 338.32,
"description": "1TB SSD固态硬盘 SATA3.0接口 读速550MB/S台式机/笔记本通用",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/274756/18/4588/79068/67d78c6aF6ea41c45/65f1eac32b30f764.jpg",
"stock": 15,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"1TB\",\"interface\":\"SATA3.0\",\"readSpeed\":\"550MB/s\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
},
{
"name": "长城Great Wall) 256GB SSD固态硬盘 SATA3.0接口 GW560系列",
"brand": "长城",
"model": "GW560系列",
"price": 108.78,
"description": "256GB SSD固态硬盘 SATA3.0接口 读速540MB/S台式机/笔记本通用",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/312376/12/10675/78986/685286e7F5541039c/9877e42ecf903836.jpg",
"stock": 27,
"typeName": "硬盘",
"specifications": "{\"capacity\":\"256GB\",\"interface\":\"SATA3.0\",\"readSpeed\":\"540MB/s\",\"type\":\"SSD\",\"formFactor\":\"2.5英寸\"}"
}
]

View File

@ -0,0 +1,519 @@
[
{
"name": "长城Great Wall本色K13升级版电脑游戏机箱",
"brand": "长城Great Wall",
"model": "本色K13升级版",
"price": 99.7,
"description": "ATX主板/五硬盘位/U3/玻璃侧透/宽体/8风扇位/5080显卡",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/182653/25/43199/136528/65fc0336Ff00dea1d/39132cd40cab781d.jpg",
"stock": 27,
"typeName": "机箱",
"specifications": "{\"formFactor\":\"ATX\",\"driveBays\":5,\"usbPort\":\"USB3.0\",\"sidePanel\":\"钢化玻璃\",\"fanSlots\":8,\"gpuLength\":\"5080mm\"}"
},
{
"name": "先马SAMA朱雀air 台式电脑主机箱",
"brand": "先马SAMA",
"model": "朱雀air",
"price": 97.8,
"description": "前板铁网/双面散热孔/6个风扇位/宽体五金/支持ATX主板240水冷竖装显卡高性价比散热机箱",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/308763/30/10893/163454/6853c6f1F7a96f43a/034b73d9649c2182.jpg",
"stock": 30,
"typeName": "机箱",
"specifications": "{\"frontPanel\":\"铁网\",\"cooling\":\"双面散热孔\",\"fanSlots\":6,\"formFactor\":\"ATX\",\"liquidCooling\":\"240mm\",\"gpuMount\":\"竖装\"}"
},
{
"name": "长城Great Wall冰霜X3黑色电脑机箱",
"brand": "长城Great Wall",
"model": "冰霜X3",
"price": 159.58,
"description": "MATX/360水冷位/U3/9风扇位/磁吸翻门/钢网面板/五槽PCI/兼容5080超高性价比360水冷机箱金属面板冲细网孔磁吸翻门轻便装机有一手",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/221102/11/24782/132919/64daea83F97f5fa72/da3372be063413d9.jpg",
"stock": 31,
"typeName": "机箱",
"specifications": "{\"formFactor\":\"MATX\",\"liquidCooling\":\"360mm\",\"usbPort\":\"USB3.0\",\"fanSlots\":9,\"doorType\":\"磁吸翻门\",\"frontPanel\":\"钢网\",\"pciSlots\":5,\"gpuLength\":\"5080mm\"}"
},
{
"name": "AOCCG391W白色 游戏电脑主机海景房机箱",
"brand": "AOC",
"model": "CG391W",
"price": 118.76,
"description": "双面玻璃/支持MATX主板/240水冷/7风扇位/270°全景/左右分仓",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t20270806/3905/18/24725/111944/66b17b21F70b6c877/752fbecca417a925.jpg",
"stock": 27,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"sidePanel\":\"双面玻璃\",\"formFactor\":\"MATX\",\"liquidCooling\":\"240mm\",\"fanSlots\":7,\"viewAngle\":\"270°\",\"layout\":\"左右分仓\"}"
},
{
"name": "鑫谷Segotep卓灵1白色机箱",
"brand": "鑫谷Segotep",
"model": "卓灵1",
"price": 128.74,
"description": "MATX主板位/玻璃侧透/小型风冷散热家用办公游戏桌面台式电脑主机箱",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/151346/33/28488/130586/64c504acFb1ecb93c/a0121dc5275d92ee.jpg",
"stock": 14,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"formFactor\":\"MATX\",\"sidePanel\":\"钢化玻璃\",\"coolingType\":\"风冷\"}"
},
{
"name": "AOCCG391B黑色 游戏电脑主机海景房机箱",
"brand": "AOC",
"model": "CG391B",
"price": 108.78,
"description": "双面玻璃/支持MATX主板/240水冷/7风扇位/270°全景/左右分仓",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/48415/20/26622/94266/66cee052Fa99d431f/982b9ad1fa55b63e.jpg",
"stock": 26,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"sidePanel\":\"双面玻璃\",\"formFactor\":\"MATX\",\"liquidCooling\":\"240mm\",\"fanSlots\":7,\"viewAngle\":\"270°\",\"layout\":\"左右分仓\"}"
},
{
"name": "EVESKY 玄武MINI 无立柱海景房机箱",
"brand": "EVESKY",
"model": "玄武MINI",
"price": 59.9,
"description": "M-ATX主板/270°广角玻璃侧透 240水冷游戏电脑主机箱 支持4090显卡 玄武MINI黑色【支持M-ATX主板/240水冷】",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t20270511/247000/38/8833/14197/663f3fb9Fb06c601a/835919ab578449b6.jpg",
"stock": 30,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"M-ATX\",\"viewAngle\":\"270°\",\"sidePanel\":\"钢化玻璃\",\"liquidCooling\":\"240mm\",\"gpuLength\":\"4090mm\",\"pillarless\":\"是\"}"
},
{
"name": "长城Great Wall冰霜X3W白色电脑机箱",
"brand": "长城Great Wall",
"model": "冰霜X3W",
"price": 159.58,
"description": "M-ATX/360水冷位/U3/9风扇位/磁吸翻门/五槽PCI/兼容5080金属面板冲细网孔360水冷位+磁吸翻门,轻便装机有一手",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/135879/1/46247/112617/67064bbbF1b63f346/23207ae3e69083db.jpg",
"stock": 15,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"formFactor\":\"M-ATX\",\"liquidCooling\":\"360mm\",\"usbPort\":\"USB3.0\",\"fanSlots\":9,\"doorType\":\"磁吸翻门\",\"pciSlots\":5,\"gpuLength\":\"5080mm\",\"frontPanel\":\"金属冲孔网\"}"
},
{
"name": "半岛铁盒应龙360水冷海景房机箱",
"brand": "半岛铁盒",
"model": "应龙",
"price": 179,
"description": "ATX/MATX无A柱台式主机台式机电脑全景房 白色/ATX/360水冷/9扇位",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/168019/25/49437/396917/6712486eF641bf024/a3630a7a6e7d3d52.jpg",
"stock": 26,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"formFactor\":\"ATX/MATX\",\"liquidCooling\":\"360mm\",\"fanSlots\":9,\"pillarless\":\"是\",\"design\":\"海景房\"}"
},
{
"name": "AOCCG392B黑色 MATX游戏台式电脑主机箱",
"brand": "AOC",
"model": "CG392B",
"price": 138.72,
"description": "支持360水冷/无立柱270°海景房/9风扇位/左右分仓",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/192796/25/50897/107588/6729e04eF7cdc74c7/699d00e76ea0ad7d.jpg",
"stock": 10,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"MATX\",\"liquidCooling\":\"360mm\",\"pillarless\":\"是\",\"viewAngle\":\"270°\",\"design\":\"海景房\",\"fanSlots\":9,\"layout\":\"左右分仓\"}"
},
{
"name": "大水牛BUBALUS追风M 台式电脑游戏主机侧透机箱",
"brand": "大水牛BUBALUS",
"model": "追风M",
"price": 79.74,
"description": "支持ATX主板/240水冷位/电源仓/背线/U3【侧透爆款】支持ATX/M-ATX/ITX主板支持120/240水冷独立电源仓",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/288994/4/8882/88826/6854d769F612872cb/c65df59ddfcff65e.jpg",
"stock": 47,
"typeName": "机箱",
"specifications": "{\"sidePanel\":\"侧透\",\"formFactor\":\"ATX/M-ATX/ITX\",\"liquidCooling\":\"240mm\",\"psuShroud\":\"独立电源仓\",\"cableManagement\":\"支持背线\",\"usbPort\":\"USB3.0\"}"
},
{
"name": "爱国者V60 海景房机箱",
"brand": "爱国者",
"model": "V60",
"price": 108,
"description": "全景无立柱MATX高颜值240水冷台式电脑台式机主机箱 黑色显卡限长305mm",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/10917/4/24871/89836/66b6e2c5Ffac157b6/889ef266a62c3c1a.jpg",
"stock": 47,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"design\":\"海景房\",\"pillarless\":\"是\",\"formFactor\":\"MATX\",\"liquidCooling\":\"240mm\",\"gpuLength\":\"305mm\"}"
},
{
"name": "爱国者YOGO T21大机箱",
"brand": "爱国者",
"model": "YOGO T21",
"price": 139,
"description": "台式ATX大板360水冷",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/231520/34/26887/137294/66dc06bbFdc104e1f/867d19e7ee8dcc87.jpg",
"stock": 31,
"typeName": "机箱",
"specifications": "{\"formFactor\":\"ATX\",\"liquidCooling\":\"360mm\"}"
},
{
"name": "EVESKY积至EVESKY灵悦迷你小机箱",
"brand": "EVESKY积至EVESKY",
"model": "灵悦",
"price": 29.9,
"description": "办公家用电脑机箱(拉丝面板/M-ATX小主板/背线/USB2.0) 刺客,支持中小主板,拉丝面板,迷你小机箱!",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/102551/16/50674/46407/66dd80d6F32afbd86/363f22f382534f62.jpg",
"stock": 58,
"typeName": "机箱",
"specifications": "{\"color\":\"刺客\",\"frontPanel\":\"拉丝面板\",\"formFactor\":\"M-ATX\",\"cableManagement\":\"支持背线\",\"usbPort\":\"USB2.0\",\"size\":\"迷你\"}"
},
{
"name": "AOCCG392W白色 MATX游戏台式电脑主机箱",
"brand": "AOC",
"model": "CG392W",
"price": 148.7,
"description": "支持360水冷/无立柱270°海景房/9风扇位/左右分仓",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/228037/2/29040/116823/6729e092Fc091a6e3/aef5d454d0af35a7.jpg",
"stock": 14,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"formFactor\":\"MATX\",\"liquidCooling\":\"360mm\",\"pillarless\":\"是\",\"viewAngle\":\"270°\",\"design\":\"海景房\",\"fanSlots\":9,\"layout\":\"左右分仓\"}"
},
{
"name": "AOC CG381W海景房机箱",
"brand": "AOC",
"model": "CG381W/CG381B",
"price": 78.55,
"description": "matx无立柱全景侧透ITX电脑主机箱高颜值DIY台式机游戏电竞水冷白色主机箱小型 CG381B黑【M-ATX双面全景侧透】",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/267739/8/19997/53053/67b036b1F84b1b140/e4251cc7958caa0f.jpg",
"stock": 11,
"typeName": "机箱",
"specifications": "{\"color\":\"白色/黑色\",\"formFactor\":\"MATX/ITX\",\"pillarless\":\"是\",\"sidePanel\":\"全景侧透\",\"liquidCooling\":\"支持\",\"design\":\"海景房\"}"
},
{
"name": "航嘉HuntkeyV320初恋 全景版海景房机箱",
"brand": "航嘉Huntkey",
"model": "V320初恋",
"price": 109,
"description": "台式电脑机箱(双面钢化玻璃/左右分区/240水冷/7风扇位/M-ATX主板 V320初恋全景版-白色",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/249069/5/1350/66296/658ff8c4F91d9b107/35aa6ffa36ac3c26.jpg",
"stock": 26,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"design\":\"海景房\",\"sidePanel\":\"双面钢化玻璃\",\"layout\":\"左右分区\",\"liquidCooling\":\"240mm\",\"fanSlots\":7,\"formFactor\":\"M-ATX\"}"
},
{
"name": "瓦尔基里VALKYRIEVK03 WHITE 白色 ATX 游戏电脑台式机箱",
"brand": "瓦尔基里VALKYRIE",
"model": "VK03 WHITE",
"price": 448.1,
"description": "支持360水冷 6.2吋触摸屏 270°海景房",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/308187/6/4960/116283/6836ee73F90ad7198/cc9bf2906bc6b103.jpg",
"stock": 27,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"formFactor\":\"ATX\",\"liquidCooling\":\"360mm\",\"display\":\"6.2吋触摸屏\",\"viewAngle\":\"270°\",\"design\":\"海景房\"}"
},
{
"name": "航嘉Huntkey 暗夜猎手5 电脑机箱",
"brand": "航嘉Huntkey",
"model": "暗夜猎手5",
"price": 85,
"description": "台式DIY全侧透游戏水冷ATX大板背线机箱 暗夜猎手5-黑色,支持大主板/32cm长显卡/支持16.5cm高散热器/宽体五金/240水冷/USB3.0/支持背线!!",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/197941/1/18578/190839/61a1de8dE8cec8e4d/5b6427da506d1fd9.jpg",
"stock": 26,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"sidePanel\":\"全侧透\",\"formFactor\":\"ATX\",\"gpuLength\":\"320mm\",\"cpuCoolerHeight\":\"165mm\",\"liquidCooling\":\"240mm\",\"usbPort\":\"USB3.0\",\"cableManagement\":\"支持背线\"}"
},
{
"name": "爱国者A15电脑机箱",
"brand": "爱国者",
"model": "A15",
"price": 99,
"description": "台式机MATX/ATX大机箱240水冷风冷台式主机箱 黑色(铁板非侧透)",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/237784/35/23257/17509/669f2c28F4b937036/758300f30e92733e.jpg",
"stock": 27,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"MATX/ATX\",\"liquidCooling\":\"240mm\",\"coolingType\":\"风冷/水冷\",\"sidePanel\":\"非侧透\"}"
},
{
"name": "爱国者星璨岚 小岚海景房机箱",
"brand": "爱国者",
"model": "星璨岚 小岚",
"price": 199,
"description": "无立柱MATX/240水冷",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/224659/37/15954/96455/6639c2d5F57e56112/74e0f15f232144ca.jpg",
"stock": 31,
"typeName": "机箱",
"specifications": "{\"design\":\"海景房\",\"pillarless\":\"是\",\"formFactor\":\"MATX\",\"liquidCooling\":\"240mm\"}"
},
{
"name": "长城Great Wall阿基米德9 PRO V2灰色电脑机箱",
"brand": "长城Great Wall",
"model": "阿基米德9 PRO V2",
"price": 234.53,
"description": "360水冷/磁吸玻璃翻门/0.8mm厚侧板/5硬盘位/10风扇位/5090显卡专为高端电竞设计前部和顶部均支持360mm水冷位10风扇位",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/108222/34/21897/104492/64828040F57df1447/9a243731d013ace8.jpg",
"stock": 15,
"typeName": "机箱",
"specifications": "{\"color\":\"灰色\",\"liquidCooling\":\"360mm\",\"doorType\":\"磁吸玻璃翻门\",\"sidePanelThickness\":\"0.8mm\",\"driveBays\":5,\"fanSlots\":10,\"gpuLength\":\"5090mm\"}"
},
{
"name": "长城Great Wall冰霜X7黑色电脑机箱",
"brand": "长城Great Wall",
"model": "冰霜X7",
"price": 228.54,
"description": "背插主板/磁吸金属面板/0.75mm厚五金/四面便拆装/360水冷位/9风扇位/5090",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/314907/1/8615/133150/6848e136F393b7e28/3ad916ac31e5fe7c.jpg",
"stock": 43,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"motherboardMount\":\"背插\",\"frontPanel\":\"磁吸金属面板\",\"materialThickness\":\"0.75mm\",\"disassembly\":\"四面便拆装\",\"liquidCooling\":\"360mm\",\"fanSlots\":9,\"gpuLength\":\"5090mm\"}"
},
{
"name": "EVESKY 棱镜MINI海景房电脑机箱",
"brand": "EVESKY",
"model": "棱镜MINI",
"price": 63,
"description": "无立柱台式主机m-atx白色侧透240水冷游戏机箱非暴风雪S920 棱镜海景房-黑色【支持M-ATX主板/240水冷】全景无立柱海景房机箱、双面玻璃侧透",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/232756/18/23591/75208/66dd8123Fb5924027/c7c741c0826440c8.jpg",
"stock": 42,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"design\":\"海景房\",\"pillarless\":\"是\",\"formFactor\":\"M-ATX\",\"sidePanel\":\"双面玻璃侧透\",\"liquidCooling\":\"240mm\"}"
},
{
"name": "长城Great Wall冰霜X3B黑色电脑机箱",
"brand": "长城Great Wall",
"model": "冰霜X3B",
"price": 158.68,
"description": "MATX主板/细钢网面板/360水冷位/9风扇位/USB3.0/细网孔铁侧板/5080显卡金属面板侧板冲细网孔素雅时尚散热好360水冷位+9风扇位电竞佳选",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/144290/31/42339/99270/65fb91e9F1efd2088/7fa6fc9e7cffba61.jpg",
"stock": 43,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"MATX\",\"frontPanel\":\"细钢网\",\"liquidCooling\":\"360mm\",\"fanSlots\":9,\"usbPort\":\"USB3.0\",\"sidePanel\":\"细网孔铁侧板\",\"gpuLength\":\"5080mm\"}"
},
{
"name": "EVESKY 棱镜MINI海景房电脑机箱",
"brand": "EVESKY",
"model": "棱镜MINI",
"price": 73,
"description": "无立柱台式主机m-atx白色侧透240水冷游戏机箱非暴风雪S920 棱镜海景房-白色【支持M-ATX主板/240水冷】全景无立柱海景房机箱、双面玻璃侧透",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/223607/38/43180/364479/663e00b0F84108c21/9d302013bc08d1c2.png",
"stock": 27,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"design\":\"海景房\",\"pillarless\":\"是\",\"formFactor\":\"M-ATX\",\"sidePanel\":\"双面玻璃侧透\",\"liquidCooling\":\"240mm\"}"
},
{
"name": "AOC机箱CG420B黑色",
"brand": "AOC",
"model": "CG420B",
"price": 148.7,
"description": "游戏办公台式电脑侧透主机箱支持ATX主板/兼容360水冷/10风扇位/7槽PCI/兼容5080",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/307927/18/9116/126859/684b7cbaF807982b0/6279dcc0c31d2811.jpg",
"stock": 15,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"sidePanel\":\"侧透\",\"formFactor\":\"ATX\",\"liquidCooling\":\"360mm\",\"fanSlots\":10,\"pciSlots\":7,\"gpuLength\":\"5080mm\"}"
},
{
"name": "爱国者aigoA15 黑色 台式电脑主机箱",
"brand": "爱国者aigo",
"model": "A15",
"price": 108.78,
"description": "支持ATX主板/USB3.0/左侧透/240冷排/宽体机箱星璨岚屏显版机箱来袭6寸高清屏1480*720高清分辨率晒单即有礼",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/124153/36/2293/352919/5ec49e49E8553ba21/7020ba2fa45b34fc.jpg",
"stock": 11,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"ATX\",\"usbPort\":\"USB3.0\",\"sidePanel\":\"左侧透\",\"liquidCooling\":\"240mm\",\"bodyType\":\"宽体\",\"displayScreen\":\"6寸高清屏\"}"
},
{
"name": "未知玩家幻翼LCD版 MATX海景房机箱",
"brand": "未知玩家",
"model": "幻翼LCD版",
"price": 697.6,
"description": "360水冷主机外壳 Tpye-C接口",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/312400/37/6475/193510/683eb392F112389da/9e1c481b5857ddd8.jpg",
"stock": 42,
"typeName": "机箱",
"specifications": "{\"design\":\"海景房\",\"formFactor\":\"MATX\",\"liquidCooling\":\"360mm\",\"usbPort\":\"Type-C\",\"displayType\":\"LCD\"}"
},
{
"name": "长城Great Wall马里亚纳MT01电脑机箱",
"brand": "长城Great Wall",
"model": "马里亚纳MT01",
"price": 158.68,
"description": "E-ATX主板/360水冷位/7风扇位/0.8mm优质侧板/防尘网/5080显卡高性价比顶置360水冷位机箱E-ATX大板面板隐藏式散热",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/97543/7/41166/120362/65210845Fb4630e3e/043bd8128e7f13b8.jpg",
"stock": 10,
"typeName": "机箱",
"specifications": "{\"formFactor\":\"E-ATX\",\"liquidCooling\":\"360mm\",\"fanSlots\":7,\"sidePanelThickness\":\"0.8mm\",\"dustFilter\":\"有\",\"gpuLength\":\"5080mm\",\"coolingDesign\":\"面板隐藏式散热\"}"
},
{
"name": "爱国者aigoA15 黑色 台式电脑主机箱",
"brand": "爱国者aigo",
"model": "A15",
"price": 108.78,
"description": "支持ATX主板/USB3.0/左侧透/240冷排/宽体机箱星璨岚屏显版机箱来袭6寸高清屏1480*720高清分辨率晒单即有礼",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/124153/36/2293/352919/5ec49e49E8553ba21/7020ba2fa45b34fc.jpg",
"stock": 31,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"ATX\",\"usbPort\":\"USB3.0\",\"sidePanel\":\"左侧透\",\"liquidCooling\":\"240mm\",\"bodyType\":\"宽体\",\"displayScreen\":\"6寸高清屏\"}"
},
{
"name": "长城Great Wall马里亚纳MT01电脑机箱",
"brand": "长城Great Wall",
"model": "马里亚纳MT01",
"price": 158.68,
"description": "E-ATX主板/360水冷位/7风扇位/0.8mm优质侧板/防尘网/5080显卡高性价比顶置360水冷位机箱E-ATX大板面板隐藏式散热",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/97543/7/41166/120362/65210845Fb4630e3e/043bd8128e7f13b8.jpg",
"stock": 31,
"typeName": "机箱",
"specifications": "{\"formFactor\":\"E-ATX\",\"liquidCooling\":\"360mm\",\"fanSlots\":7,\"sidePanelThickness\":\"0.8mm\",\"dustFilter\":\"有\",\"gpuLength\":\"5080mm\",\"coolingDesign\":\"面板隐藏式散热\"}"
},
{
"name": "航嘉HuntkeyV320初恋 全景版海景房机箱",
"brand": "航嘉Huntkey",
"model": "V320初恋",
"price": 99,
"description": "台式电脑机箱(双面钢化玻璃/左右分区/240水冷/7风扇位/M-ATX主板 V320初恋全景版-黑色",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/234566/28/11262/70267/658ff9bbFd21ace61/11fa9c7c07743ed7.jpg",
"stock": 14,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"design\":\"海景房\",\"sidePanel\":\"双面钢化玻璃\",\"layout\":\"左右分区\",\"liquidCooling\":\"240mm\",\"fanSlots\":7,\"formFactor\":\"M-ATX\"}"
},
{
"name": "爱国者V60 海景房机箱",
"brand": "爱国者",
"model": "V60",
"price": 118,
"description": "全景无立柱MATX高颜值240水冷台式电脑台式机主机箱 白色显卡限长305mm",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/80410/17/26715/94744/66b6e2c5F38b04894/e402ec72a1e70bfc.jpg",
"stock": 42,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"design\":\"海景房\",\"pillarless\":\"是\",\"formFactor\":\"MATX\",\"liquidCooling\":\"240mm\",\"gpuLength\":\"305mm\"}"
},
{
"name": "航嘉HuntkeyS900沙尘暴全景版海景房机箱",
"brand": "航嘉Huntkey",
"model": "S900沙尘暴",
"price": 109,
"description": "台式电脑机箱(双面钢化玻璃/左右分区/240水冷/7风扇位/M-ATX主板 S900沙尘暴-全视版-白色【新品机箱上市】M-ATX主板兼容240冷排支持4090显卡7个风扇位桌面海景房机箱",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t20280221/254766/5/23672/55169/67b88d6fFfad84b96/a03d5126d869be3d.jpg",
"stock": 46,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"design\":\"海景房\",\"sidePanel\":\"双面钢化玻璃\",\"layout\":\"左右分区\",\"liquidCooling\":\"240mm\",\"fanSlots\":7,\"formFactor\":\"M-ATX\",\"gpuLength\":\"4090mm\"}"
},
{
"name": "EVESKY 玄武MINI 无立柱海景房机箱",
"brand": "EVESKY",
"model": "玄武MINI",
"price": 69,
"description": "M-ATX主板/270°广角玻璃侧透 240水冷游戏电脑主机箱 支持4090显卡 玄武MINI白色【支持M-ATX主板/240水冷】",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/229596/28/21181/36641/668e5fb3F974555b0/45243653b7d88d9a.jpg",
"stock": 47,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"formFactor\":\"M-ATX\",\"viewAngle\":\"270°\",\"sidePanel\":\"钢化玻璃\",\"liquidCooling\":\"240mm\",\"gpuLength\":\"4090mm\",\"pillarless\":\"是\"}"
},
{
"name": "积至EVESKY) 泰坦PRO 电脑机箱",
"brand": "积至EVESKY)",
"model": "泰坦PRO",
"price": 69,
"description": "(侧透/支持ATX/宽体游戏电脑机箱/支持长显卡/背线) 泰坦PRO-黑色,家用/办公精选,侧透简约造型,独立电源包仓,宽体背部走线,多种散热方案,性价比优选!",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/138741/36/44890/87581/66dd8165Fed4b2b55/16bc856daa4e5573.jpg",
"stock": 10,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"sidePanel\":\"侧透\",\"formFactor\":\"ATX\",\"bodyType\":\"宽体\",\"gpuLength\":\"支持长显卡\",\"cableManagement\":\"支持背线\",\"psuShroud\":\"独立电源包仓\"}"
},
{
"name": "玩嘉孤勇者MINI黑色电脑机箱",
"brand": "玩嘉",
"model": "孤勇者MINI",
"price": 100.8,
"description": "台式机MATX海景房240一体水冷全侧透ITX桌面暴风S920",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/75802/27/22521/109554/637f3105Eda2f28a2/43ff63a5e94d2a33.jpg",
"stock": 43,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"MATX/ITX\",\"design\":\"海景房\",\"liquidCooling\":\"240mm一体水冷\",\"sidePanel\":\"全侧透\"}"
},
{
"name": "大水牛BUBALUS 商悦 中塔商务台式电脑主机机箱",
"brand": "大水牛BUBALUS",
"model": "商悦",
"price": 59.9,
"description": "支持ATX主板/支持29cm显卡/背线 商悦-黑色支持29cm显卡支持背线支持ATX主板",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/226677/18/24789/37265/66dd816fFea7f7967/daf11eb3774dbfa9.jpg",
"stock": 31,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"中塔/ATX\",\"gpuLength\":\"290mm\",\"cableManagement\":\"支持背线\"}"
},
{
"name": "ThermaltakeTt钢影 透EX 海景房机箱",
"brand": "ThermaltakeTt",
"model": "钢影 透EX",
"price": 268.46,
"description": "电脑主机 白色ATX主板/Type-c/支持360水冷/10风扇位/4090显卡【年度限定ip“瑶小喵”系列新品-机电散全覆盖】【一站式搭配组合,好看又省心】",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/300698/3/16662/143843/685521d4Fac88fd2c/103d18113d6e182f.jpg",
"stock": 27,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"design\":\"海景房\",\"formFactor\":\"ATX\",\"usbPort\":\"Type-C\",\"liquidCooling\":\"360mm\",\"fanSlots\":10,\"gpuLength\":\"4090mm\"}"
},
{
"name": "大水牛BUBALUS瑞博 黑色 台式电脑主机中塔机箱",
"brand": "大水牛BUBALUS",
"model": "瑞博",
"price": 79.74,
"description": "支持ATX主板/带光驱位/多硬盘兼容/长显卡支持/背部走线,优质拉丝工艺,悬浮式前板,双片式后窗,打造稳固宽敞箱体",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/300916/30/16611/66319/6854d76fF7e70e311/875bc553eca7415f.jpg",
"stock": 42,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"中塔/ATX\",\"opticalDriveBay\":\"有\",\"driveBays\":\"多硬盘位\",\"gpuLength\":\"支持长显卡\",\"cableManagement\":\"支持背线\",\"frontPanel\":\"拉丝工艺/悬浮式\"}"
},
{
"name": "爱国者aigoJV13黑色 电脑台式主机箱",
"brand": "爱国者aigo",
"model": "JV13",
"price": 99.7,
"description": "桌面matx小机箱透明侧板/ITX主板/240水冷/RGB灯条星璨岚屏显版机箱来袭6寸高清屏1480*720高清分辨率晒单即有礼",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/167713/30/39624/43376/64eefadaFa8b8b37e/7cab53b318088710.jpg",
"stock": 42,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"MATX/ITX\",\"sidePanel\":\"透明侧板\",\"liquidCooling\":\"240mm\",\"lighting\":\"RGB灯条\",\"displayScreen\":\"6寸高清屏\"}"
},
{
"name": "先马SAMA平头哥M2 电脑机箱",
"brand": "先马SAMA",
"model": "平头哥M2",
"price": 89.72,
"description": "台式机matx小机箱 玻璃侧透/支持240水冷/背线/USB3.0/独立电源仓/防尘易清洗支持M-ATX主板240水冷下置电源位背线空间【270°海景房机箱10风扇位机箱】",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/305734/11/11959/122807/6853c468Fe3491263/b47bdc163de96213.jpg",
"stock": 42,
"typeName": "机箱",
"specifications": "{\"formFactor\":\"MATX\",\"sidePanel\":\"钢化玻璃侧透\",\"liquidCooling\":\"240mm\",\"cableManagement\":\"支持背线\",\"usbPort\":\"USB3.0\",\"psuLocation\":\"下置\",\"dustFilter\":\"防尘易清洗\",\"design\":\"海景房\",\"viewAngle\":\"270°\",\"fanSlots\":10}"
},
{
"name": "玩嘉 风琴黑色电脑机箱",
"brand": "玩嘉",
"model": "风琴",
"price": 91.82,
"description": "台式机matx小机箱 玻璃侧透/支持240水冷/背线/USB3.0/独立电源仓/防尘易清洗",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/101062/25/43328/85363/64e4664dFdb3d98a3/40db9a0f88e26b0d.jpg",
"stock": 11,
"typeName": "机箱",
"specifications": "{\"color\":\"黑色\",\"formFactor\":\"MATX\",\"sidePanel\":\"钢化玻璃侧透\",\"liquidCooling\":\"240mm\",\"cableManagement\":\"支持背线\",\"usbPort\":\"USB3.0\",\"psuShroud\":\"独立电源仓\",\"dustFilter\":\"防尘易清洗\"}"
},
{
"name": "大水牛BUBALUS追风M+冰晶L3风扇套装 台式电脑游戏主机侧透机箱",
"brand": "大水牛BUBALUS",
"model": "追风M+冰晶L3",
"price": 99.7,
"description": "支持ATX主板/240水冷位/电源仓/背线/U3【机箱+风扇套装】配3个冰晶L风扇亚克力侧透不易碎",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/302977/7/16443/95648/6854d767Fe054d47e/741d0eea21331b4e.jpg",
"stock": 26,
"typeName": "机箱",
"specifications": "{\"bundle\":\"机箱+风扇套装\",\"fanModel\":\"冰晶L3\",\"sidePanel\":\"亚克力侧透\",\"formFactor\":\"ATX\",\"liquidCooling\":\"240mm\",\"psuShroud\":\"电源仓\",\"cableManagement\":\"支持背线\",\"usbPort\":\"USB3.0\"}"
},
{
"name": "爱国者aigo星璨岚 小岚白色 游戏电脑台式主机箱",
"brand": "爱国者aigo",
"model": "星璨岚 小岚",
"price": 198.6,
"description": "垂直风道风冷/M-ATX主板/240水冷/270°海景房/5090显卡星璨岚屏显版机箱来袭6寸高清屏1480*720高清分辨率晒单即有礼",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/90316/14/40395/109679/6540d89bFabf6da14/cb9c970edd6f2aa9.jpg",
"stock": 10,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"coolingDesign\":\"垂直风道风冷\",\"formFactor\":\"M-ATX\",\"liquidCooling\":\"240mm\",\"viewAngle\":\"270°\",\"design\":\"海景房\",\"gpuLength\":\"5090mm\",\"displayScreen\":\"6寸高清屏\"}"
},
{
"name": "爱国者aigoA15 白色 台式电脑主机箱",
"brand": "爱国者aigo",
"model": "A15",
"price": 118.76,
"description": "支持ATX主板/USB3.0/左侧透/240冷排/宽体机箱星璨岚屏显版机箱来袭6寸高清屏1480*720高清分辨率晒单即有礼",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/125106/36/35548/86257/64b6604dF0b15b573/2a7f726338585998.jpg",
"stock": 15,
"typeName": "机箱",
"specifications": "{\"color\":\"白色\",\"formFactor\":\"ATX\",\"usbPort\":\"USB3.0\",\"sidePanel\":\"左侧透\",\"liquidCooling\":\"240mm\",\"bodyType\":\"宽体\",\"displayScreen\":\"6寸高清屏\"}"
}
]

View File

@ -0,0 +1,475 @@
[
{
"name": "ROG STRIX X870E-E GAMING WIFI主板",
"brand": "ROG",
"model": "STRIX X870E-E GAMING WIFI",
"price": 4010.96,
"description": "支持 CPU 9900X3D/9950X3D/9800X3D (AMD X870E/socket AM5)",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/109669/39/51302/152827/6709d073Fd69cb470/d5b9fb001b9c1c52.png",
"stock": 47,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD X870E\",\"socket\":\"AM5\",\"memoryType\":\"DDR5\",\"wifi\":\"WiFi 6E\"}"
},
{
"name": "华硕TX GAMING B760M WIFI D4 天选主板",
"brand": "华硕",
"model": "TX GAMING B760M WIFI D4 天选",
"price": 1196.6,
"description": "支持 CPU 13700K/13600KF/13400FIntel B760/LGA 1700扫码注册一年换新三年上门服务白条6期免息12+1供电模组WIFI6+2.5G网卡双向AI降噪。",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/209757/32/29594/161779/63fd62c4F9f5d4bf9/ca0d22d42c3b6848.jpg",
"stock": 26,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"powerPhase\":\"12+1\",\"network\":\"WIFI6+2.5G网卡\"}"
},
{
"name": "华硕ASUSB760M-AYW WIFI D4 II 哎呦喂主板",
"brand": "华硕ASUS",
"model": "B760M-AYW WIFI D4 II 哎呦喂",
"price": 887.22,
"description": "支持 CPU 13600KF/13400F12600KFIntel B760/LGA 1700",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/313407/31/510/122533/68232bb7F0686dde7/200f3673b0147fec.jpg",
"stock": 31,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "华硕ASUSTUF GAMING B760M-PLUS WIFI II重炮手二代 DDR5主板",
"brand": "华硕ASUS",
"model": "TUF GAMING B760M-PLUS WIFI II 重炮手二代",
"price": 1316.36,
"description": "支持 CPU 14600KF/14700KFIntel B760/LGA 1700扫码注册一年换新三年上门服务白条6期免息12+1+1供电模组显卡易拆键WIFI6E+易拆式天线。",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/233680/30/12928/126467/65aa1906F72d5c545/8f5efa97e28b6406.jpg",
"stock": 11,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR5\",\"powerPhase\":\"12+1+1\",\"network\":\"WIFI6E\"}"
},
{
"name": "技嘉GIGABYTE小雕Z890 AORUS ELITE WIFI7 DDR5主板",
"brand": "技嘉GIGABYTE",
"model": "Z890 AORUS ELITE WIFI7",
"price": 2394.2,
"description": "支持CPU Ultra U9-285K U7-265K LGA 1851",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/273561/7/16490/166950/67f33bafFcff26f8f/c0689277b3a9a14e.jpg",
"stock": 43,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel Z890\",\"socket\":\"LGA 1851\",\"memoryType\":\"DDR5\",\"wifi\":\"WiFi 7\"}"
},
{
"name": "华硕TUF GAMING B850M-PLUS WIFI重炮手主板",
"brand": "华硕",
"model": "TUF GAMING B850M-PLUS WIFI 重炮手",
"price": 1416.16,
"description": "支持 CPU 7800X3D/9800X3D/9600X(AMD B850/socket AM5)扫码注册一年换新三年上门服务白条6期免息14+2+1供电模组3*M.2接口WIFI6E&2.5G网卡。",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/259603/21/8698/100403/677b4cfaFa69fd526/23fdc1c33c263212.png",
"stock": 31,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD B850\",\"socket\":\"AM5\",\"powerPhase\":\"14+2+1\",\"storage\":\"3*M.2\",\"network\":\"WIFI6E&2.5G网卡\"}"
},
{
"name": "华硕ASUSTX GAMING B760M WIFI 天选主板",
"brand": "华硕ASUS",
"model": "TX GAMING B760M WIFI 天选",
"price": 1296.4,
"description": "支持DDR5 CPU 13700K/13600KF/13400FIntel B760/LGA 1700扫码注册一年换新三年上门服务白条6期免息12+1供电模组支持DDR5双PCIe4.0·M.2插槽。",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/158653/36/37462/145304/644104e2F3eddeda6/f3a6d98ae262b228.jpg",
"stock": 10,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR5\",\"powerPhase\":\"12+1\",\"storage\":\"双PCIe4.0·M.2\"}"
},
{
"name": "微星MSIB760M BOMBER DDR4爆破弹电脑主板",
"brand": "微星MSI",
"model": "B760M BOMBER DDR4 爆破弹",
"price": 677.64,
"description": "支持CPU 12600KF/14400F/13490F/13400F (INTEL B760/LGA 1700)",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/297061/11/14774/163743/684bd41dFe85c3168/fffba768d543b3e8.jpg",
"stock": 58,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\"}"
},
{
"name": "微星MSIB650M GAMING WIFI主板",
"brand": "微星MSI",
"model": "B650M GAMING WIFI",
"price": 877.24,
"description": "支持CPU 7800X3D/9600X/9700X/7500F (AMD B650/AM5接口)",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/291814/6/12019/137871/6852571aFbf2fff3c/7639ddf25de5d1ff.jpg",
"stock": 47,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD B650\",\"socket\":\"AM5\",\"wifi\":\"WIFI\"}"
},
{
"name": "微星MSIMAG B760M MORTAR WIFI II DDR5迫击炮主板",
"brand": "微星MSI",
"model": "MAG B760M MORTAR WIFI II 迫击炮",
"price": 1196.6,
"description": "支持CPU14600K/14600KF/14700KF (Intel B760/LGA 1700)",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/317182/12/8852/137359/684bd20fF3c633368/1cff09a8ae63cdd0.png",
"stock": 47,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR5\",\"wifi\":\"WIFI\"}"
},
{
"name": "七彩虹ColorfulBATTLE-AX B760M-WHITE WIFI V20 DDR4 冷钢主板",
"brand": "七彩虹Colorful",
"model": "BATTLE-AX B760M-WHITE WIFI V20 冷钢",
"price": 727.54,
"description": "支持 12600KF/13490F/14600KFIntel B760/LGA 1700",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/211190/24/37027/126153/65338602Fc10d08e4/aa603865c7ee5874.jpg",
"stock": 42,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "微星MSIMAG B850M MORTAR WIFI 迫击炮主板",
"brand": "微星MSI",
"model": "MAG B850M MORTAR WIFI 迫击炮",
"price": 1496,
"description": "支持CPU AMD 9950X/9800X3D/9700X/9600X(AMD B850/AM5接口)",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/317646/37/8894/136025/684bf1fdFc451a57a/6b0f2a628e4c3350.jpg",
"stock": 43,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD B850\",\"socket\":\"AM5\",\"wifi\":\"WIFI\"}"
},
{
"name": "ROG STRIX X870-A GAMING WIFI主板+AMD 锐龙7 9800X3D CPU 主板+CPU套装",
"brand": "ROG",
"model": "STRIX X870-A GAMING WIFI",
"price": 6698,
"description": "ROG STRIX X870-A GAMING WIFI主板与AMD 锐龙7 9800X3D CPU的套装",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/221684/23/40781/154649/672350a0F34fad613/dcd914ea252df80e.jpg",
"stock": 30,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD X870\",\"socket\":\"AM5\",\"cpuIncluded\":\"AMD Ryzen 7 9800X3D\",\"wifi\":\"WIFI\"}"
},
{
"name": "华硕B760M-AYW WIFI D4 哎呦喂主板",
"brand": "华硕",
"model": "B760M-AYW WIFI D4 哎呦喂",
"price": 867.26,
"description": "支持 CPU 13600KF/13400FIntel B760/LGA 1700扫码注册一年换新三年上门服务白条6期免息8供电模组2个M.2插槽M,2便捷卡扣。",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/201173/9/30199/128829/64535d52F55d69b18/a2c7e2afc80ca3b7.jpg",
"stock": 14,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"powerPhase\":\"8\",\"storage\":\"2*M.2\"}"
},
{
"name": "华硕B650M-AYW WIFI 哎呦喂主板",
"brand": "华硕",
"model": "B650M-AYW WIFI 哎呦喂",
"price": 897.2,
"description": "支持DDR5 CPU 7700X/7600X/7500F (AMD B650/socket AM5)扫码注册一年换新三年上门服务白条6期免息WIFI6&2.5G网卡M.2便捷卡扣双向AI降噪",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/263491/18/7470/81805/6777ac63F52a6ba13/eba8d1a9b824e07c.png",
"stock": 15,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD B650\",\"socket\":\"AM5\",\"memoryType\":\"DDR5\",\"network\":\"WIFI6&2.5G网卡\"}"
},
{
"name": "七彩虹B760M D5 冷钢战斧",
"brand": "七彩虹",
"model": "B760M-WHITE WIFI D4 冷钢战斧",
"price": 704.44,
"description": "支持英特尔 酷睿12代 13代 14代CPU 12600KF/12490F/13600KF/14600K又便宜又好撩客服MM、享更多优惠~七彩虹助力您生产力提升!游戏畅玩不卡顿~",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/293105/25/13583/676121/685672efFbc763f65/1c6e395d98c5833d.png",
"stock": 47,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "长城 GREAT WALL新款H610M-A主板+i5-12400F+4管祥龙散热器套装",
"brand": "长城 GREAT WALL",
"model": "H610M-A",
"price": 1108,
"description": "台式机电脑1700针12代酷睿CPU处理器i3-12100F/i5-12400F套装搭长城散热器双DDR4 H610M-A+i5-12400F+4管祥龙",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/254668/11/27191/178257/67c4183aF8206334b/14a411c46121dde4.jpg",
"stock": 11,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"cpuIncluded\":\"Intel Core i5-12400F\"}"
},
{
"name": "华南金牌H610M-K 单主板",
"brand": "华南金牌",
"model": "H610M-K",
"price": 299,
"description": "华南金牌H610/B760M主板CPU套装搭英特尔i5 12400F/13400F/13490F/i3 12100F板U套装黑神话悟空游戏电脑3/6期免息/全新3年质保H610主板B760主板LGA1700酷睿12100F套装12400F套装12600K等套装~",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/265962/23/3352/149414/676cc693Fee3d33a2/5364e7d522d922ea.jpg",
"stock": 14,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\"}"
},
{
"name": "技嘉/华硕H61 M小板 二手主板",
"brand": "技嘉/华硕/鲲鹄",
"model": "H61 M小板",
"price": 65,
"description": "技嘉/华硕/鲲鹄主板H61/B85/ H81 3/4/7/9/10代英特尔CPU 台式机主板二手主板 8成新",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/107613/39/48531/98161/66c5a414F11ebf38c/b2d7a1d5327cdf4f.jpg",
"stock": 46,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H61\",\"socket\":\"LGA 1155\",\"condition\":\"二手\"}"
},
{
"name": "微星MSIA520M-A PRO DDR4电脑主板",
"brand": "微星MSI",
"model": "A520M-A PRO",
"price": 378.24,
"description": "支持CPU 5600/5600GT/5700XAMD A520/AM4接口",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/302402/38/13853/163308/68525b2aF3d6566e5/9645e6000616c926.jpg",
"stock": 43,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD A520\",\"socket\":\"AM4\",\"memoryType\":\"DDR4\"}"
},
{
"name": "色技师 H470-VH台式机电脑主板",
"brand": "色技师",
"model": "H470-VH",
"price": 299,
"description": "H470台式机电脑主板",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/321363/24/7465/141213/684698d2Fee7c4f90/445a1f4d055eeb60.jpg",
"stock": 31,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H470\"}"
},
{
"name": "铭瑄MAXSUN终结者 B850M PRO WIFI电脑主板",
"brand": "铭瑄MAXSUN",
"model": "终结者 B850M PRO WIFI",
"price": 897.1,
"description": "支持 CPU AMD 9950X/9800X3D/9700X/9600X/7800X3D(AMD B850/AM5接口)",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/303106/18/13979/106778/684a912aF4bcaa02b/7edb7d77c8222637.jpg",
"stock": 31,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD B850\",\"socket\":\"AM5\",\"wifi\":\"WIFI\"}"
},
{
"name": "ROG STRIX Z790-A GAMING WIFI S 吹雪主板",
"brand": "ROG",
"model": "STRIX Z790-A GAMING WIFI S 吹雪",
"price": 2813.36,
"description": "支持DDR5 CPU 14900K/14700KIntel Z790/LGA 1700扫码注册一年换新三年上门服务白条6期免息16+1+2供电模组2.5G网卡&WIFI7AI智能超频。",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/163508/26/39623/183736/677a2b63F49738128/28726f5e450b2c5c.jpg",
"stock": 27,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel Z790\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR5\",\"powerPhase\":\"16+1+2\",\"network\":\"2.5G网卡&WIFI7\"}"
},
{
"name": "长城H610M-A单主板 国货之光)",
"brand": "长城 GREAT WALL",
"model": "H610M-A",
"price": 348,
"description": "新款H610M-A主板台式机电脑1700针12代酷睿CPU处理器i3-12100F/i5-12400F套装搭长城散热器双DDR4",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/256309/26/28111/201703/67c41831F652b5ebc/ff9a97953dc12cdd.jpg",
"stock": 27,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\"}"
},
{
"name": "ROG STRIX X870-A GAMING WIFI吹雪主板",
"brand": "ROG",
"model": "STRIX X870-A GAMING WIFI 吹雪",
"price": 2913.16,
"description": "支持 CPU 9900X3D/9950X3D/9800X3D (AMD X870/socket AM5)",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/285128/40/18062/187050/67f8eab9F45b36274/0f8e08dfed139bea.png",
"stock": 43,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD X870\",\"socket\":\"AM5\",\"wifi\":\"WIFI\"}"
},
{
"name": "华硕TX B760M WIFI D4天选 I5 14600KF盒装主板CPU套装",
"brand": "英特尔Intel",
"model": "TX B760M WIFI D4 天选",
"price": 2099,
"description": "英特尔 14代i5 主板CPU套装华硕TX B760M WIFI D4天选I5 14600KF盒装",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/267390/25/20811/120689/67b2a1a9F014903be/0fff2bf76257117d.jpg",
"stock": 11,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"cpuIncluded\":\"Intel Core i5-14600KF\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "七彩虹战斧B760M-P PRO D4主板",
"brand": "七彩虹",
"model": "B760M-P PRO D4 战斧",
"price": 540,
"description": "支持英特尔12600KF 14600KF 12/13/14代CPU D4 D5 游戏家用台式机电脑主板暑期放假抢满400-10满400减50战斧B760M-PPROV20官方补贴到手价538元",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/174191/16/43871/162239/666190a4Fca201d94/c63261abbc2c1928.jpg",
"stock": 30,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\"}"
},
{
"name": "华硕天选B760M WIFI 单主板无CPU",
"brand": "华硕",
"model": "天选B760M WIFI",
"price": 1099,
"description": "华硕H610M/B760M D4/D5天选/吹雪/TUF 台式机电脑主板CPU套装英特尔i5D4版本单主板无CPU",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/230238/23/26750/183668/66cd6236F121c000c/9e3db2ea88982aed.jpg",
"stock": 10,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "华南金牌B760M-K 搭I5-13400F散片主板CPU套装",
"brand": "华南金牌",
"model": "B760M-K",
"price": 1098,
"description": "华南金牌H610/B760M主板CPU套装搭英特尔i5 12400F/13400F/13490F/i3 12100F板U套装黑神话悟空游戏电脑3/6期免息/全新3年质保H610主板B760主板LGA1700酷睿12100F套装12400F套装12600K等套装~",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/254540/35/4396/110221/676cc6baF6fc01aad/870f5b0125817d9c.jpg",
"stock": 46,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"cpuIncluded\":\"Intel Core i5-13400F\"}"
},
{
"name": "七彩虹ColorfulH610M-E-WIFI D4【战斧】主板",
"brand": "七彩虹Colorful",
"model": "H610M-E-WIFI D4 战斧",
"price": 439,
"description": "H610M 主板台式机电脑支持酷睿12代 13代CPU 12400/12490F游戏家用台式机电脑主板",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/317745/27/10586/200762/68560d2bFdd72d55e/2dc5e4ca47d0852d.jpg",
"stock": 42,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "华硕天选B760M WIFI 单主板无CPU",
"brand": "华硕",
"model": "天选B760M WIFI",
"price": 1099,
"description": "华硕H610M/B760M D4/D5天选/吹雪/TUF 台式机电脑主板CPU套装英特尔i5D4版本单主板无CPU",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/230238/23/26750/183668/66cd6236F121c000c/9e3db2ea88982aed.jpg",
"stock": 27,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "ROG STRIX X870E-E GAMING WIFI主板",
"brand": "ROG",
"model": "STRIX X870E-E GAMING WIFI",
"price": 4010.96,
"description": "支持 CPU 9900X3D/9950X3D/9800X3D (AMD X870E/socket AM5)",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/109669/39/51302/152827/6709d073Fd69cb470/d5b9fb001b9c1c52.png",
"stock": 27,
"typeName": "主板",
"specifications": "{\"chipset\":\"AMD X870E\",\"socket\":\"AM5\",\"memoryType\":\"DDR5\",\"wifi\":\"WiFi 6E\"}"
},
{
"name": "i5-4590+技嘉/华硕B85M 小板二手主板CPU套装",
"brand": "技嘉/华硕/鲲鹄",
"model": "B85M 小板",
"price": 245,
"description": "技嘉/华硕/鲲鹄主板H61/B85/ H81 3/4/7/9/10代英特尔CPU 台式机主板二手主板搭配i5-4590处理器",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/247642/23/16871/96414/66c5a46eF37a9bdec/1cf26002c64dbf18.jpg",
"stock": 47,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B85\",\"socket\":\"LGA 1150\",\"cpuIncluded\":\"Intel Core i5-4590\",\"condition\":\"二手\"}"
},
{
"name": "七彩虹ColorfulBATTLE-AX B760M-G WIFI V20A 小黑刃DDR4主板",
"brand": "七彩虹Colorful",
"model": "BATTLE-AX B760M-G WIFI V20A 小黑刃",
"price": 697.6,
"description": "支持14600K/14600KFIntel B760/LGA 1700",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/317275/12/10148/180527/6853c72fFbc4adf2b/4851ec2210920337.png",
"stock": 11,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"wifi\":\"WIFI\"}"
},
{
"name": "华硕B250 M小板二手主板",
"brand": "技嘉/华硕/鲲鹄",
"model": "B250 M小板",
"price": 153,
"description": "技嘉/华硕/鲲鹄主板H61/B85/ H81 3/4/7/9/10代英特尔CPU 台式机主板二手主板 9成新",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/277016/7/14229/192585/67eb873cFb7cc8f9c/4ad2561d5f5a6a61.jpg",
"stock": 27,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B250\",\"socket\":\"LGA 1151\",\"condition\":\"二手\"}"
},
{
"name": "技嘉GIGABYTE小雕Z890 AORUS ELITE WIFI7 DDR5主板",
"brand": "技嘉GIGABYTE",
"model": "Z890 AORUS ELITE WIFI7",
"price": 2394.2,
"description": "支持CPU Ultra U9-285K U7-265K LGA 1851",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/273561/7/16490/166950/67f33bafFcff26f8f/c0689277b3a9a14e.jpg",
"stock": 42,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel Z890\",\"socket\":\"LGA 1851\",\"memoryType\":\"DDR5\",\"wifi\":\"WiFi 7\"}"
},
{
"name": "精粤H610M-D PLUS主板",
"brand": "精粤",
"model": "H610M-D PLUS",
"price": 328.34,
"description": "1700针DDR4内存电脑游戏主板I3 12100F/12400F/13400F Intel H610主板/LGA 1700",
"imageUrl": "https://img10.360buyimg.com/n7/jfs/t1/280515/30/19640/146525/67fdf8aeFb654baaf/00eaa3147ef86a4e.png",
"stock": 26,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\"}"
},
{
"name": "七彩虹ColorfulBATTLE-AX H610M-E WIFI V21 游戏主板",
"brand": "七彩虹Colorful",
"model": "BATTLE-AX H610M-E WIFI V21",
"price": 478.04,
"description": "支持12400F/13400/14400F (Intel H610/LGA 1700)",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/154562/29/46497/163500/672c5ab1F5450a7c6/45611b3673672fc7.png",
"stock": 10,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\",\"wifi\":\"WIFI\"}"
},
{
"name": "技嘉GIGABYTEH610M K DDR4主板",
"brand": "技嘉GIGABYTE",
"model": "H610M K",
"price": 448.1,
"description": "支持CPU12代酷睿12400F(Intel H610/LGA 1700)注册1年换新4年保修内存超频支持3200/M.2存存储插槽/HDMI",
"imageUrl": "https://img13.360buyimg.com/n7/jfs/t1/234529/30/15172/166954/65f7e7fbF59bc0107/f72a38debd61707c.jpg",
"stock": 26,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\",\"memorySpeed\":\"3200MHz\",\"storage\":\"M.2\"}"
},
{
"name": "长城H610M-A单主板 国货之光)",
"brand": "长城 GREAT WALL",
"model": "H610M-A",
"price": 348,
"description": "官方新款H610M-A电脑台式主板12代CPU套装1700针i5-12400f板u搭长城散热器游戏多开12100f板u套装",
"imageUrl": "https://img14.360buyimg.com/n7/jfs/t1/266170/14/26636/189006/67c41717Fdf0a0078/cd2b1657a3af5dc6.jpg",
"stock": 27,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel H610\",\"socket\":\"LGA 1700\"}"
},
{
"name": "ROG STRIX B760-G GAMING WIFI S小吹雪S主板",
"brand": "ROG",
"model": "STRIX B760-G GAMING WIFI S 小吹雪S",
"price": 1515.96,
"description": "支持DDR5 CPU 14700K/14600KFIntel B760/LGA 1700扫码注册一年换新三年上门服务白条6期免息12+1+1供电模组AI智能优化易拆式天线。",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/95258/2/42982/188564/65d46c66F42266a3c/c6605225a9fd88c7.jpg",
"stock": 11,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR5\",\"powerPhase\":\"12+1+1\"}"
},
{
"name": "微星MAG B460M MORTAR迫击炮二手主板",
"brand": "微星",
"model": "MAG B460M MORTAR 迫击炮",
"price": 249,
"description": "微星 华硕 技嘉七彩虹B460 Z490 B560 Z590二手主板支持Intel10 11代",
"imageUrl": "https://img12.360buyimg.com/n7/jfs/t1/151535/7/42568/144113/65f7a37fFd86d0a83/be92115118549f7e.png",
"stock": 26,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B460\",\"socket\":\"LGA 1200\",\"condition\":\"二手\"}"
},
{
"name": "技嘉GIGABYTEB760M H DDR4主板",
"brand": "技嘉GIGABYTE",
"model": "B760M H",
"price": 577.84,
"description": "支持CPU 13600KF 12600KF Intel LGA 1700",
"imageUrl": "https://img11.360buyimg.com/n7/jfs/t1/88433/20/46603/244125/6715fb1cF779ef2c8/01f98f63bf37d3c7.jpg",
"stock": 10,
"typeName": "主板",
"specifications": "{\"chipset\":\"Intel B760\",\"socket\":\"LGA 1700\",\"memoryType\":\"DDR4\"}"
}
]

34
.dev-tools/fetchDat.js Normal file
View File

@ -0,0 +1,34 @@
let result = [];
let richResult = []
$0.querySelectorAll("li").forEach((ele, index) => {
let price = parseFloat(ele.querySelector("strong").textContent.trim().slice(1));
let title = ele.querySelector(".p-name").textContent.trim();
let img = ele.querySelector("img").src;
result.push([index, title, price]);
richResult.push({
index,
title: title,
price: price,
img: img
});
})
console.log(JSON.stringify(result, null, 2));
let data = []
let res = data.map(item => {
return {
name: item.name,
brand: item.brand,
model: item.model,
price: item.price,
description: item.description,
imageUrl: richResult.find(item_ => item_.index == item.index).img,
stock: Math.random() * 50 | 0 + 10,
typeName: item.typeName,
specifications: item.specifications
};
}).filter(item => item.imageUrl).filter(item => item.typeName == "主板")
console.log(JSON.stringify(res));

2
.gitignore vendored
View File

@ -39,3 +39,5 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
/app/generated/prisma

View File

@ -0,0 +1,727 @@
'use client'
import { useState, useEffect } from 'react'
import { withAdminAuth } from '@/components/admin/AdminAuth'
import { Plus, Edit, Trash2, Search, Upload, Download, FileText } from 'lucide-react'
interface Component {
id: string
name: string
brand: string
model: string
price: number
description?: string
imageUrl?: string
stock: number
specifications?: string
componentType: {
id: string
name: string
}
}
interface ComponentType {
id: string
name: string
}
function AdminComponentsPage() {
const [components, setComponents] = useState<Component[]>([])
const [componentTypes, setComponentTypes] = useState<ComponentType[]>([])
const [isLoading, setIsLoading] = useState(true)
const [showModal, setShowModal] = useState(false)
const [showBatchModal, setShowBatchModal] = useState(false)
const [editingComponent, setEditingComponent] = useState<Component | null>(null)
const [searchTerm, setSearchTerm] = useState('')
const [batchFile, setBatchFile] = useState<File | null>(null)
const [batchData, setBatchData] = useState<any[]>([])
const [batchImportLoading, setBatchImportLoading] = useState(false)
const [validationErrors, setValidationErrors] = useState<string[]>([])
const [validData, setValidData] = useState<any[]>([])
const [invalidData, setInvalidData] = useState<any[]>([])
const [formData, setFormData] = useState({
name: '',
brand: '',
model: '',
price: '',
description: '',
imageUrl: '',
stock: '',
componentTypeId: '',
specifications: ''
})
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
try {
const [componentsRes, typesRes] = await Promise.all([
fetch('/api/components?limit=100'),
fetch('/api/component-types')
])
if (componentsRes.ok) {
const data = await componentsRes.json()
setComponents(data.components)
}
if (typesRes.ok) {
const types = await typesRes.json()
setComponentTypes(types)
}
} catch (error) {
console.error('加载数据失败:', error)
} finally {
setIsLoading(false)
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
try {
const url = editingComponent
? `/api/components/${editingComponent.id}`
: '/api/components'
const method = editingComponent ? 'PUT' : 'POST'
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
if (response.ok) {
await loadData()
setShowModal(false)
resetForm()
} else {
const data = await response.json()
alert(data.message || '操作失败')
}
} catch (error) {
console.error('提交失败:', error)
alert('操作失败')
}
}
const handleDelete = async (id: string) => {
if (!confirm('确定要删除这个配件吗?')) return
try {
const response = await fetch(`/api/components/${id}`, {
method: 'DELETE',
})
if (response.ok) {
await loadData()
} else {
alert('删除失败')
}
} catch (error) {
console.error('删除失败:', error)
alert('删除失败')
}
}
const handleEdit = (component: Component) => {
setEditingComponent(component)
setFormData({
name: component.name,
brand: component.brand,
model: component.model,
price: component.price.toString(),
description: component.description || '',
imageUrl: component.imageUrl || '',
stock: component.stock.toString(),
componentTypeId: component.componentType.id,
specifications: component.specifications || ''
})
setShowModal(true)
}
const resetForm = () => {
setFormData({
name: '',
brand: '',
model: '',
price: '',
description: '',
imageUrl: '',
stock: '',
componentTypeId: '',
specifications: ''
})
setEditingComponent(null)
}
// 批量导入相关函数
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
setBatchFile(file)
parseFile(file)
}
}
const parseFile = async (file: File) => {
const text = await file.text()
try {
let data: any[] = []
if (file.name.endsWith('.json')) {
const parsed = JSON.parse(text)
data = Array.isArray(parsed) ? parsed : [parsed]
} else if (file.name.endsWith('.csv')) {
const lines = text.split('\n')
if (lines.length < 2) {
alert('CSV文件格式错误')
return
}
const headers = lines[0].split(',').map(h => h.trim())
data = lines.slice(1)
.filter(line => line.trim())
.map(line => {
const values = line.split(',').map(v => v.trim().replace(/^"|"$/g, ''))
const obj: any = {}
headers.forEach((header, index) => {
obj[header] = values[index] || ''
})
return obj
})
}
setBatchData(data)
validateBatchData(data)
} catch (error) {
alert('文件解析失败,请检查文件格式')
console.error('File parse error:', error)
}
}
const validateBatchData = (data: any[]) => {
const errors: string[] = []
const valid: any[] = []
const invalid: any[] = []
data.forEach((item, index) => {
const itemErrors: string[] = []
// 验证必需字段
if (!item.name || typeof item.name !== 'string' || item.name.trim() === '') {
itemErrors.push('缺少商品名称')
}
if (!item.brand || typeof item.brand !== 'string' || item.brand.trim() === '') {
itemErrors.push('缺少品牌')
}
if (!item.model || typeof item.model !== 'string' || item.model.trim() === '') {
itemErrors.push('缺少型号')
}
// 验证价格
const price = typeof item.price === 'string' ? parseFloat(item.price) : item.price
if (isNaN(price) || price < 0) {
itemErrors.push('价格必须是有效的正数')
}
// 验证库存
const stock = typeof item.stock === 'string' ? parseInt(item.stock) : item.stock
if (isNaN(stock) || stock < 0) {
itemErrors.push('库存必须是有效的非负整数')
}
// 验证配件类型
const hasTypeId = item.componentTypeId && item.componentTypeId.trim() !== ''
const hasTypeName = (item.typeName || item.type) && (item.typeName || item.type).trim() !== ''
if (!hasTypeId && !hasTypeName) {
itemErrors.push('缺少配件类型信息 (componentTypeId, typeName 或 type)')
}
if (itemErrors.length > 0) {
invalid.push({ ...item, _errors: itemErrors, _index: index + 1 })
errors.push(`${index + 1}行: ${itemErrors.join(', ')}`)
} else {
valid.push(item)
}
})
setValidationErrors(errors)
setValidData(valid)
setInvalidData(invalid)
}
const handleBatchImport = async () => {
if (validData.length === 0) {
alert('没有有效的数据可导入')
return
}
if (invalidData.length > 0) {
const proceed = confirm(`发现${invalidData.length}行无效数据,是否只导入${validData.length}行有效数据?`)
if (!proceed) return
}
setBatchImportLoading(true)
try {
const response = await fetch('/api/components/batch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
components: validData
}),
})
const result = await response.json()
if (response.ok && result.success) {
const { summary, results } = result
// 显示详细的导入结果
const failedItems = results.filter((r: any) => !r.success)
let message = `导入完成!\n成功: ${summary.successful}\n失败: ${summary.failed}`
if (summary.newTypesCreated > 0) {
message += `\n新创建配件类型: ${summary.newTypesCreated}`
}
if (failedItems.length > 0 && failedItems.length <= 5) {
message += '\n\n失败项目:'
failedItems.forEach((item: any, index: number) => {
message += `\n${index + 1}. ${item.item.name || '未知'}: ${item.error}`
})
} else if (failedItems.length > 5) {
message += `\n\n失败项目过多请检查数据格式或查看控制台详情`
console.log('批量导入失败项目:', failedItems)
}
alert(message)
if (summary.successful > 0) {
await loadData()
setShowBatchModal(false)
setBatchFile(null)
setBatchData([])
setValidData([])
setInvalidData([])
setValidationErrors([])
}
} else {
alert(`批量导入失败: ${result.message || '未知错误'}`)
console.error('Batch import error:', result)
}
} catch (error) {
alert('批量导入失败:网络或服务器错误')
console.error('Batch import error:', error)
} finally {
setBatchImportLoading(false)
}
}
const downloadTemplate = (format: 'csv' | 'json') => {
const sampleData = [
{
name: 'Intel Core i5-13400F',
brand: 'Intel',
model: 'i5-13400F',
price: 1299,
description: '10核16线程基础频率2.5GHz最大睿频4.6GHz',
imageUrl: '',
stock: 50,
typeName: 'CPU',
specifications: '{"cores":10,"threads":16,"baseClock":"2.5GHz","boostClock":"4.6GHz","socket":"LGA1700"}'
}
]
if (format === 'csv') {
const headers = ['name', 'brand', 'model', 'price', 'description', 'imageUrl', 'stock', 'typeName', 'specifications']
const csvContent = [
headers.join(','),
...sampleData.map(item => headers.map(header => `"${item[header as keyof typeof item] || ''}"`).join(','))
].join('\n')
const blob = new Blob([csvContent], { type: 'text/csv' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'components_template.csv'
a.click()
URL.revokeObjectURL(url)
} else {
const jsonContent = JSON.stringify(sampleData, null, 2)
const blob = new Blob([jsonContent], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'components_template.json'
a.click()
URL.revokeObjectURL(url)
}
}
const filteredComponents = components.filter(component =>
component.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
component.brand.toLowerCase().includes(searchTerm.toLowerCase()) ||
component.model.toLowerCase().includes(searchTerm.toLowerCase())
)
if (isLoading) {
return (
<div className="flex justify-center items-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
)
}
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900"></h1>
<div className="flex gap-3">
<button
onClick={() => setShowBatchModal(true)}
className="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700 flex items-center gap-2"
>
<Upload className="h-5 w-5" />
</button>
<button
onClick={() => {
resetForm()
setShowModal(true)
}}
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 flex items-center gap-2"
>
<Plus className="h-5 w-5" />
</button>
</div>
</div>
{/* Search */}
<div className="mb-6">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<input
type="text"
placeholder="搜索配件..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
{/* Components Table */}
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{filteredComponents.map((component) => (
<tr key={component.id}>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="h-10 w-10 flex-shrink-0">
{component.imageUrl ? (
<img loading='lazy'
className="h-10 w-10 rounded-full object-cover"
src={component.imageUrl}
alt={component.name}
/>
) : (
<div className="h-10 w-10 rounded-full bg-gray-300 flex items-center justify-center">
📦
</div>
)}
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{component.name}
</div>
<div className="text-sm text-gray-500">
{component.brand} {component.model}
</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{component.componentType.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
¥{component.price.toLocaleString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs rounded-full ${
component.stock > 0
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{component.stock}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
onClick={() => handleEdit(component)}
className="text-blue-600 hover:text-blue-900 mr-4"
>
<Edit className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(component.id)}
className="text-red-600 hover:text-red-900"
>
<Trash2 className="h-4 w-4" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Modal */}
{showModal && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md max-h-[90vh] overflow-y-auto">
<h2 className="text-xl font-bold mb-4">
{editingComponent ? '编辑配件' : '添加配件'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<select
value={formData.componentTypeId}
onChange={(e) => setFormData({ ...formData, componentTypeId: e.target.value })}
required
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
>
<option value=""></option>
{componentTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="text"
value={formData.brand}
onChange={(e) => setFormData({ ...formData, brand: e.target.value })}
required
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="text"
value={formData.model}
onChange={(e) => setFormData({ ...formData, model: e.target.value })}
required
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="number"
step="0.01"
value={formData.price}
onChange={(e) => setFormData({ ...formData, price: e.target.value })}
required
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="number"
value={formData.stock}
onChange={(e) => setFormData({ ...formData, stock: e.target.value })}
required
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<textarea
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={3}
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">URL</label>
<input
type="url"
value={formData.imageUrl}
onChange={(e) => setFormData({ ...formData, imageUrl: e.target.value })}
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
<div className="flex justify-end space-x-4 pt-4">
<button
type="button"
onClick={() => setShowModal(false)}
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"
>
</button>
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
{editingComponent ? '更新' : '创建'}
</button>
</div>
</form>
</div>
</div>
)}
{/* Batch Import Modal */}
{showBatchModal && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md max-h-[90vh] overflow-y-auto">
<h2 className="text-xl font-bold mb-4"></h2>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700"></label>
<input
type="file"
onChange={handleFileChange}
accept=".json,.csv"
className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2"
/>
</div>
{batchFile && (
<div className="mb-4">
<p className="text-sm text-gray-500">
: <span className="font-medium">{batchFile.name}</span>
</p>
</div>
)}
<div className="mb-4">
<button
onClick={() => downloadTemplate('csv')}
className="mr-2 px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 flex items-center gap-2"
>
<Download className="h-5 w-5" />
CSV模板
</button>
<button
onClick={() => downloadTemplate('json')}
className="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 flex items-center gap-2"
>
<Download className="h-5 w-5" />
JSON模板
</button>
</div>
<div className="flex justify-end space-x-4">
<button
type="button"
onClick={() => setShowBatchModal(false)}
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"
>
</button>
<button
onClick={handleBatchImport}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 flex items-center gap-2"
disabled={batchImportLoading}
>
{batchImportLoading ? (
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
) : (
<Upload className="h-5 w-5" />
)}
</button>
</div>
{batchData.length > 0 && (
<div className="mt-4">
<h3 className="text-lg font-semibold mb-2"></h3>
<div className="bg-gray-100 p-4 rounded-md max-h-[300px] overflow-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
{Object.keys(batchData[0]).map((key) => (
<th key={key} className="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
{key}
</th>
))}
</tr>
</thead> <tbody className="bg-white divide-y divide-gray-200">
{batchData.map((item, index) => (
<tr key={index}>
{Object.values(item).map((value, i) => (
<td key={i} className="px-4 py-2 whitespace-nowrap text-sm text-gray-900">
{String(value || '')}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
</div>
)}
</div>
)
}
export default withAdminAuth(AdminComponentsPage)

278
app/admin/orders/page.tsx Normal file
View File

@ -0,0 +1,278 @@
'use client'
import { useState, useEffect } from 'react'
import { withAdminAuth } from '@/components/admin/AdminAuth'
import { Eye, Package, Calendar, User } from 'lucide-react'
interface Order {
id: string
orderNumber: string
totalAmount: number
status: string
createdAt: string
user: {
id: string
email: string
name?: string
username: string
}
orderItems: {
id: string
quantity: number
price: number
component: {
id: string
name: string
brand: string
model: string
}
}[]
}
const statusLabels: { [key: string]: string } = {
PENDING: '待处理',
CONFIRMED: '已确认',
PROCESSING: '处理中',
SHIPPED: '已发货',
DELIVERED: '已送达',
CANCELLED: '已取消'
}
const statusColors: { [key: string]: string } = {
PENDING: 'bg-yellow-100 text-yellow-800',
CONFIRMED: 'bg-blue-100 text-blue-800',
PROCESSING: 'bg-purple-100 text-purple-800',
SHIPPED: 'bg-green-100 text-green-800',
DELIVERED: 'bg-green-100 text-green-800',
CANCELLED: 'bg-red-100 text-red-800'
}
function AdminOrdersPage() {
const [orders, setOrders] = useState<Order[]>([])
const [isLoading, setIsLoading] = useState(true)
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null)
const [showModal, setShowModal] = useState(false)
useEffect(() => {
loadOrders()
}, [])
const loadOrders = async () => {
try {
const response = await fetch('/api/admin/orders')
if (response.ok) {
const data = await response.json()
setOrders(data)
}
} catch (error) {
console.error('加载订单失败:', error)
} finally {
setIsLoading(false)
}
}
const updateOrderStatus = async (orderId: string, newStatus: string) => {
try {
const response = await fetch(`/api/admin/orders/${orderId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ status: newStatus }),
})
if (response.ok) {
await loadOrders()
if (selectedOrder && selectedOrder.id === orderId) {
setSelectedOrder({ ...selectedOrder, status: newStatus })
}
} else {
alert('更新订单状态失败')
}
} catch (error) {
console.error('更新订单状态失败:', error)
alert('更新订单状态失败')
}
}
const viewOrderDetails = (order: Order) => {
setSelectedOrder(order)
setShowModal(true)
}
if (isLoading) {
return (
<div className="flex justify-center items-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
)
}
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex justify-between items-center mb-6">
<h1 className="text-3xl font-bold text-gray-900"></h1>
<div className="text-sm text-gray-600">
{orders.length}
</div>
</div>
{/* Orders Table */}
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{orders.map((order) => (
<tr key={order.id}>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">
{order.orderNumber}
</div>
<div className="text-sm text-gray-500">
{order.orderItems.length}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<User className="h-4 w-4 text-gray-400 mr-2" /> <div>
<div className="text-sm font-medium text-gray-900">
{order.user?.name || order.user?.username || '未知用户'}
</div>
<div className="text-sm text-gray-500">
{order.user?.email || '无邮箱'}
</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
¥{order.totalAmount.toLocaleString()}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<select
value={order.status}
onChange={(e) => updateOrderStatus(order.id, e.target.value)}
className={`inline-flex px-2 py-1 text-xs rounded-full border-0 ${statusColors[order.status]}`}
>
{Object.entries(statusLabels).map(([status, label]) => (
<option key={status} value={status}>
{label}
</option>
))}
</select>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<div className="flex items-center">
<Calendar className="h-4 w-4 mr-2" />
{new Date(order.createdAt).toLocaleDateString('zh-CN')}
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
onClick={() => viewOrderDetails(order)}
className="text-blue-600 hover:text-blue-900"
>
<Eye className="h-4 w-4" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Order Details Modal */}
{showModal && selectedOrder && (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[90vh] overflow-y-auto">
<h2 className="text-xl font-bold mb-4"></h2>
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<div className="text-sm text-gray-900">{selectedOrder.orderNumber}</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<span className={`inline-flex px-2 py-1 text-xs rounded-full ${statusColors[selectedOrder.status]}`}>
{statusLabels[selectedOrder.status]}
</span>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label> <div className="text-sm text-gray-900">
{selectedOrder.user?.name || selectedOrder.user?.username || '未知用户'}
</div>
<div className="text-sm text-gray-500">{selectedOrder.user?.email || '无邮箱'}</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<div className="text-sm text-gray-900">¥{selectedOrder.totalAmount.toLocaleString()}</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2"></label>
<div className="border rounded-lg">
{selectedOrder.orderItems.map((item) => (
<div key={item.id} className="p-4 border-b last:border-b-0">
<div className="flex justify-between items-center"> <div>
<div className="font-medium">{item.component?.name || '未知商品'}</div>
<div className="text-sm text-gray-500">
{item.component?.brand || '未知品牌'} {item.component?.model || ''}
</div>
</div>
<div className="text-right">
<div className="font-medium">¥{item.price.toLocaleString()}</div>
<div className="text-sm text-gray-500">: {item.quantity}</div>
</div>
</div>
</div>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700"></label>
<div className="text-sm text-gray-900">
{new Date(selectedOrder.createdAt).toLocaleString('zh-CN')}
</div>
</div>
</div>
<div className="flex justify-end space-x-4 pt-6">
<button
onClick={() => setShowModal(false)}
className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50"
>
</button>
</div>
</div>
</div>
)}
</div>
)
}
export default withAdminAuth(AdminOrdersPage)

442
app/admin/page.tsx Normal file
View File

@ -0,0 +1,442 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import {
Users,
Package,
ShoppingCart,
DollarSign,
TrendingUp,
Eye,
BarChart3,
Settings,
Plus
} from 'lucide-react'
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'
interface StatsData {
overview: {
totalUsers: number
totalOrders: number
totalComponents: number
totalRevenue: number
}
topUsers: Array<{
id: string
username: string
name: string
email: string
totalSpending: number
orderCount: number
}>
topComponents: Array<{
id: string
name: string
brand: string
price: number
typeName: string
totalSales: number
totalRevenue: number
}>
recentOrders: Array<{
id: string
orderNumber: string
totalAmount: number
status: string
createdAt: string
user: {
username: string
name?: string
}
orderItems: Array<{
component: {
name: string
}
}>
}>
}
const COLORS = ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899']
export default function AdminDashboard() {
const [stats, setStats] = useState<StatsData | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [activeTab, setActiveTab] = useState('overview')
useEffect(() => {
checkAdminAccess()
loadStats()
}, [])
const checkAdminAccess = () => {
const user = JSON.parse(localStorage.getItem('user') || 'null')
if (!user || !user.isAdmin) {
alert('权限不足,需要管理员权限')
window.location.href = '/'
return
}
}
const loadStats = async () => {
try {
const token = localStorage.getItem('token')
const response = await fetch('/api/admin/stats', {
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
const data = await response.json()
setStats(data)
} else if (response.status === 403) {
alert('权限不足')
window.location.href = '/'
}
} catch (error) {
console.error('加载统计数据失败:', error)
} finally {
setIsLoading(false)
}
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-600"></p>
</div>
<div className="flex space-x-4">
<Link
href="/admin/components"
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
<Plus className="h-4 w-4 inline mr-2" />
</Link>
<Link
href="/admin/orders"
className="border border-blue-600 text-blue-600 px-4 py-2 rounded-lg hover:bg-blue-50 transition-colors"
>
<Eye className="h-4 w-4 inline mr-2" />
</Link>
</div>
</div>
{/* Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-600 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">{stats?.overview.totalUsers || 0}</p>
</div>
<Users className="h-12 w-12 text-blue-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-600 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">{stats?.overview.totalOrders || 0}</p>
</div>
<ShoppingCart className="h-12 w-12 text-green-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-600 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">{stats?.overview.totalComponents || 0}</p>
</div>
<Package className="h-12 w-12 text-purple-600" />
</div>
</div>
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-gray-600 text-sm"></p>
<p className="text-2xl font-bold text-gray-900">¥{stats?.overview.totalRevenue?.toFixed(2) || '0.00'}</p>
</div>
<DollarSign className="h-12 w-12 text-red-600" />
</div>
</div>
</div>
{/* Tab Navigation */}
<div className="mb-8">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8">
<button
onClick={() => setActiveTab('overview')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'overview'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
<BarChart3 className="h-4 w-4 inline mr-2" />
</button>
<button
onClick={() => setActiveTab('users')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'users'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
<Users className="h-4 w-4 inline mr-2" />
</button>
<button
onClick={() => setActiveTab('products')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'products'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
<Package className="h-4 w-4 inline mr-2" />
</button>
<button
onClick={() => setActiveTab('orders')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'orders'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
<ShoppingCart className="h-4 w-4 inline mr-2" />
</button>
</nav>
</div>
</div>
{/* Tab Content */}
{activeTab === 'overview' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Top Users Chart */}
<div className="bg-white rounded-lg shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={stats?.topUsers.slice(0, 5) || []}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip formatter={(value) => [`¥${value}`, '消费金额']} />
<Bar dataKey="totalSpending" fill="#3B82F6" />
</BarChart>
</ResponsiveContainer>
</div>
{/* Top Components Chart */}
<div className="bg-white rounded-lg shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={stats?.topComponents.slice(0, 5) || []}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip formatter={(value) => [`${value}`, '销量']} />
<Bar dataKey="totalSales" fill="#EF4444" />
</BarChart>
</ResponsiveContainer>
</div>
</div>
)}
{activeTab === 'users' && (
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900"></h3>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{stats?.topUsers.map((user, index) => (
<tr key={user.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
#{index + 1}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{user.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{user.email}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{user.orderCount}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-red-600">
¥{user.totalSpending.toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{activeTab === 'products' && (
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900"></h3>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{stats?.topComponents.map((component, index) => (
<tr key={component.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
#{index + 1}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{component.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{component.brand}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{component.typeName}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{component.totalSales}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-red-600">
¥{component.totalRevenue.toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{activeTab === 'orders' && (
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900"></h3>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{stats?.recentOrders.map((order) => (
<tr key={order.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{order.orderNumber}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{order.user.name || order.user.username}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{order.orderItems.length}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-red-600">
¥{order.totalAmount}
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="inline-flex px-2 py-1 text-xs font-medium rounded-full bg-blue-100 text-blue-800">
{order.status}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{new Date(order.createdAt).toLocaleDateString('zh-CN')}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,102 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { requireAdmin } from '@/lib/admin-auth'
// 获取所有组件
export async function GET(request: NextRequest) {
try {
await requireAdmin(request)
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '20')
const type = searchParams.get('type')
const search = searchParams.get('search')
const where: any = {}
if (type) {
where.componentType = {
name: type
}
}
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ brand: { contains: search, mode: 'insensitive' } },
]
}
const [components, total] = await Promise.all([
prisma.component.findMany({
where,
include: {
componentType: true
},
orderBy: {
createdAt: 'desc'
},
skip: (page - 1) * limit,
take: limit
}),
prisma.component.count({ where })
])
return NextResponse.json({
components,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
})
} catch (error: any) {
return NextResponse.json(
{ message: error.message || '获取组件列表失败' },
{ status: error.message === '权限不足' ? 403 : 500 }
)
}
}
// 创建新组件
export async function POST(request: NextRequest) {
try {
await requireAdmin(request)
const body = await request.json()
const { name, brand, model, price, description, imageUrl, stock, specifications, componentTypeId } = body
if (!name || !brand || !model || !price || !componentTypeId) {
return NextResponse.json(
{ message: '请填写所有必填字段' },
{ status: 400 }
)
}
const component = await prisma.component.create({
data: {
name,
brand,
model,
price: parseFloat(price),
description,
imageUrl,
stock: parseInt(stock) || 0,
specifications: specifications ? JSON.stringify(specifications) : null,
componentTypeId
},
include: {
componentType: true
}
})
return NextResponse.json(component, { status: 201 })
} catch (error: any) {
return NextResponse.json(
{ message: error.message || '创建组件失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const { status } = await request.json()
const order = await prisma.order.update({
where: { id: id },
data: { status },
include: {
user: {
select: {
id: true,
email: true,
username: true,
name: true
}
},
orderItems: {
include: {
component: {
select: {
id: true,
name: true,
brand: true,
model: true
}
}
}
}
}
})
return NextResponse.json(order)
} catch (error) {
console.error('更新订单状态失败:', error)
return NextResponse.json(
{ message: '更新订单状态失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,38 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET() {
try {
const orders = await prisma.order.findMany({
include: {
user: {
select: {
id: true,
email: true,
username: true,
name: true
}
}, orderItems: {
include: {
component: {
include: {
componentType: true
}
}
}
}
},
orderBy: {
createdAt: 'desc'
}
})
return NextResponse.json(orders)
} catch (error) {
console.error('获取订单列表失败:', error)
return NextResponse.json(
{ message: '获取订单列表失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,123 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { requireAdmin } from '@/lib/admin-auth'
export async function GET(request: NextRequest) {
try {
await requireAdmin(request)
// 按消费金额倒排的前十用户
const topUsersBySpending = await prisma.user.findMany({
where: {
isAdmin: false,
orders: {
some: {}
}
},
select: {
id: true,
username: true,
name: true,
email: true,
orders: {
select: {
totalAmount: true
}
}
},
take: 10
})
const usersWithSpending = topUsersBySpending.map(user => ({
id: user.id,
username: user.username,
name: user.name || user.username,
email: user.email,
totalSpending: user.orders.reduce((sum, order) => sum + order.totalAmount, 0),
orderCount: user.orders.length
})).sort((a, b) => b.totalSpending - a.totalSpending)
// 按销售量倒排序的前十配件
const topComponentsBySales = await prisma.component.findMany({
select: {
id: true,
name: true,
brand: true,
price: true,
componentType: {
select: {
name: true
}
},
orderItems: {
select: {
quantity: true
}
}
}
})
const componentsWithSales = topComponentsBySales.map(component => ({
id: component.id,
name: component.name,
brand: component.brand,
price: component.price,
typeName: component.componentType.name,
totalSales: component.orderItems.reduce((sum, item) => sum + item.quantity, 0),
totalRevenue: component.orderItems.reduce((sum, item) => sum + (item.quantity * component.price), 0)
})).sort((a, b) => b.totalSales - a.totalSales).slice(0, 10)
// 总体统计
const totalUsers = await prisma.user.count({ where: { isAdmin: false } })
const totalOrders = await prisma.order.count()
const totalComponents = await prisma.component.count()
const totalRevenue = await prisma.order.aggregate({
_sum: {
totalAmount: true
}
})
const recentOrders = await prisma.order.findMany({
take: 5,
orderBy: {
createdAt: 'desc'
},
include: {
user: {
select: {
username: true,
name: true
}
},
orderItems: {
include: {
component: {
select: {
name: true
}
}
}
}
}
})
return NextResponse.json({
overview: {
totalUsers,
totalOrders,
totalComponents,
totalRevenue: totalRevenue._sum.totalAmount || 0
},
topUsers: usersWithSpending,
topComponents: componentsWithSales,
recentOrders
})
} catch (error: any) {
console.error('获取统计数据失败:', error)
return NextResponse.json(
{ message: error.message || '获取统计数据失败' },
{ status: error.message === '权限不足' ? 403 : 500 }
)
}
}

View File

@ -0,0 +1,59 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyPassword, generateToken } from '@/lib/auth'
export async function POST(request: NextRequest) {
try {
const { email, password } = await request.json()
if (!email || !password) {
return NextResponse.json(
{ message: '邮箱和密码不能为空' },
{ status: 400 }
)
}
// 查找用户
const user = await prisma.user.findUnique({
where: { email }
})
if (!user) {
return NextResponse.json(
{ message: '用户不存在' },
{ status: 401 }
)
}
// 验证密码
const isValidPassword = await verifyPassword(password, user.password)
if (!isValidPassword) {
return NextResponse.json(
{ message: '密码错误' },
{ status: 401 }
)
}
// 生成token
const token = generateToken({
userId: user.id,
email: user.email,
isAdmin: user.isAdmin
})
// 返回用户信息(不包含密码)
const { password: _, ...userWithoutPassword } = user
return NextResponse.json({
message: '登录成功',
token,
user: userWithoutPassword
})
} catch (error) {
console.error('Login error:', error)
return NextResponse.json(
{ message: '服务器错误' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,70 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { hashPassword } from '@/lib/auth'
export async function POST(request: NextRequest) {
try {
const { email, username, password, name, phone, address } = await request.json()
if (!email || !username || !password) {
return NextResponse.json(
{ message: '邮箱、用户名和密码不能为空' },
{ status: 400 }
)
}
// 检查邮箱是否已存在
const existingUserByEmail = await prisma.user.findUnique({
where: { email }
})
if (existingUserByEmail) {
return NextResponse.json(
{ message: '该邮箱已被注册' },
{ status: 400 }
)
}
// 检查用户名是否已存在
const existingUserByUsername = await prisma.user.findUnique({
where: { username }
})
if (existingUserByUsername) {
return NextResponse.json(
{ message: '该用户名已被使用' },
{ status: 400 }
)
}
// 加密密码
const hashedPassword = await hashPassword(password)
// 创建用户
const user = await prisma.user.create({
data: {
email,
username,
password: hashedPassword,
name: name || null,
phone: phone || null,
address: address || null,
isAdmin: false
}
})
// 返回用户信息(不包含密码)
const { password: _, ...userWithoutPassword } = user
return NextResponse.json({
message: '注册成功',
user: userWithoutPassword
}, { status: 201 })
} catch (error) {
console.error('Register error:', error)
return NextResponse.json(
{ message: '服务器错误' },
{ status: 500 }
)
}
}

139
app/api/cart/[id]/route.ts Normal file
View File

@ -0,0 +1,139 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyToken } from '@/lib/auth'
// 更新购物车项数量
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
const { quantity } = await request.json()
if (!quantity || quantity < 1) {
return NextResponse.json(
{ message: '数量必须大于0' },
{ status: 400 }
)
}
// 检查购物车项是否属于当前用户
const cartItem = await prisma.cartItem.findFirst({
where: {
id: id,
userId: decoded.userId
},
include: {
component: true
}
})
if (!cartItem) {
return NextResponse.json(
{ message: '购物车项不存在' },
{ status: 404 }
)
}
// 检查库存
if (quantity > cartItem.component.stock) {
return NextResponse.json(
{ message: '库存不足' },
{ status: 400 }
)
}
const updatedCartItem = await prisma.cartItem.update({
where: { id: id },
data: { quantity: quantity },
include: {
component: {
include: {
componentType: true
}
}
}
})
return NextResponse.json(updatedCartItem)
} catch (error) {
console.error('更新购物车项失败:', error)
return NextResponse.json(
{ message: '更新购物车项失败' },
{ status: 500 }
)
}
}
// 删除购物车项
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
// 检查购物车项是否属于当前用户
const cartItem = await prisma.cartItem.findFirst({
where: {
id: id,
userId: decoded.userId
}
})
if (!cartItem) {
return NextResponse.json(
{ message: '购物车项不存在' },
{ status: 404 }
)
}
await prisma.cartItem.delete({
where: { id: id }
})
return NextResponse.json({ message: '已从购物车移除' })
} catch (error) {
console.error('删除购物车项失败:', error)
return NextResponse.json(
{ message: '删除购物车项失败' },
{ status: 500 }
)
}
}

188
app/api/cart/route.ts Normal file
View File

@ -0,0 +1,188 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyToken } from '@/lib/auth'
// 获取购物车
export async function GET(request: NextRequest) {
try {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
const cartItems = await prisma.cartItem.findMany({
where: { userId: decoded.userId },
include: {
component: {
include: {
componentType: true
}
}
},
orderBy: { createdAt: 'desc' }
})
return NextResponse.json(cartItems)
} catch (error) {
console.error('获取购物车失败:', error)
return NextResponse.json(
{ message: '获取购物车失败' },
{ status: 500 }
)
}
}
// 添加商品到购物车
export async function POST(request: NextRequest) {
try {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
const { componentId, quantity = 1 } = await request.json()
if (!componentId) {
return NextResponse.json(
{ message: '缺少商品ID' },
{ status: 400 }
)
}
// 检查商品是否存在
const component = await prisma.component.findUnique({
where: { id: componentId }
})
if (!component) {
return NextResponse.json(
{ message: '商品不存在' },
{ status: 404 }
)
}
if (component.stock < quantity) {
return NextResponse.json(
{ message: '库存不足' },
{ status: 400 }
)
}
// 检查购物车中是否已存在该商品
const existingCartItem = await prisma.cartItem.findUnique({
where: {
userId_componentId: {
userId: decoded.userId,
componentId: componentId
}
}
})
let cartItem
if (existingCartItem) {
// 更新数量
const newQuantity = existingCartItem.quantity + quantity
if (newQuantity > component.stock) {
return NextResponse.json(
{ message: '超出库存限制' },
{ status: 400 }
)
}
cartItem = await prisma.cartItem.update({
where: { id: existingCartItem.id },
data: { quantity: newQuantity },
include: {
component: {
include: {
componentType: true
}
}
}
})
} else {
// 创建新的购物车项
cartItem = await prisma.cartItem.create({
data: {
userId: decoded.userId,
componentId: componentId,
quantity: quantity
},
include: {
component: {
include: {
componentType: true
}
}
}
})
}
return NextResponse.json(cartItem, { status: 201 })
} catch (error) {
console.error('添加到购物车失败:', error)
return NextResponse.json(
{ message: '添加到购物车失败' },
{ status: 500 }
)
}
}
// 清空购物车
export async function DELETE(request: NextRequest) {
try {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
await prisma.cartItem.deleteMany({
where: { userId: decoded.userId }
})
return NextResponse.json({ message: '购物车已清空' })
} catch (error) {
console.error('清空购物车失败:', error)
return NextResponse.json(
{ message: '清空购物车失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,27 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET() {
try {
const componentTypes = await prisma.componentType.findMany({
include: {
_count: {
select: {
components: true
}
}
},
orderBy: {
name: 'asc'
}
})
return NextResponse.json(componentTypes)
} catch (error) {
console.error('Component types fetch error:', error)
return NextResponse.json(
{ message: '获取配件类型失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,90 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const component = await prisma.component.findUnique({
where: { id: id },
include: {
componentType: true
}
})
if (!component) {
return NextResponse.json(
{ message: '配件不存在' },
{ status: 404 }
)
}
return NextResponse.json(component)
} catch (error) {
console.error('Component fetch error:', error)
return NextResponse.json(
{ message: '获取配件详情失败' },
{ status: 500 }
)
}
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const data = await request.json()
const component = await prisma.component.update({
where: { id: id },
data: {
name: data.name,
brand: data.brand,
model: data.model,
price: parseFloat(data.price),
description: data.description,
imageUrl: data.imageUrl,
stock: parseInt(data.stock),
specifications: data.specifications,
componentTypeId: data.componentTypeId
},
include: {
componentType: true
}
})
return NextResponse.json(component)
} catch (error) {
console.error('Component update error:', error)
return NextResponse.json(
{ message: '更新配件失败' },
{ status: 500 }
)
}
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
await prisma.component.delete({
where: { id: id }
})
return NextResponse.json({ message: '删除成功' })
} catch (error) {
console.error('Component delete error:', error)
return NextResponse.json(
{ message: '删除配件失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,215 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
interface BatchComponentData {
name: string
brand: string
model: string
price: number
description?: string
imageUrl?: string
stock: number
specifications?: string
componentTypeId?: string
typeName?: string
type?: string
}
interface BatchResult {
success: boolean
item: BatchComponentData
error?: string
componentId?: string
}
export async function POST(request: NextRequest) {
try {
// 这个API需要管理员权限实际应用中需要验证
const { components } = await request.json()
if (!Array.isArray(components) || components.length === 0) {
return NextResponse.json(
{ message: '请提供有效的配件数据数组' },
{ status: 400 }
)
}
const results: BatchResult[] = []
const createdTypes: { [key: string]: string } = {} // 缓存新创建的类型
// 获取现有的配件类型,用于匹配
const existingTypes = await prisma.componentType.findMany()
const typeMap = new Map(existingTypes.map(type => [type.name.toLowerCase(), type.id]))
for (const componentData of components) {
try {
// 验证必需字段
if (!componentData.name || !componentData.brand || !componentData.model) {
results.push({
success: false,
item: componentData,
error: '缺少必需字段: name, brand, model'
})
continue
}
// 处理价格和库存
const price = typeof componentData.price === 'string'
? parseFloat(componentData.price)
: componentData.price
const stock = typeof componentData.stock === 'string'
? parseInt(componentData.stock)
: componentData.stock
if (isNaN(price) || price < 0) {
results.push({
success: false,
item: componentData,
error: '价格必须是有效的正数'
})
continue
}
if (isNaN(stock) || stock < 0) {
results.push({
success: false,
item: componentData,
error: '库存必须是有效的非负整数'
})
continue
}
// 处理配件类型
let componentTypeId = componentData.componentTypeId
if (!componentTypeId) {
const typeName = componentData.typeName || componentData.type
if (!typeName) {
results.push({
success: false,
item: componentData,
error: '缺少配件类型信息 (componentTypeId, typeName 或 type)'
})
continue
}
const lowerTypeName = typeName.toLowerCase()
// 检查是否已存在
if (typeMap.has(lowerTypeName)) {
componentTypeId = typeMap.get(lowerTypeName)
} else if (createdTypes[lowerTypeName]) {
// 检查本次批量导入中是否已创建
componentTypeId = createdTypes[lowerTypeName]
} else {
// 创建新的配件类型
try {
const newType = await prisma.componentType.create({
data: {
name: typeName,
description: `自动创建的${typeName}类型`
}
})
componentTypeId = newType.id
createdTypes[lowerTypeName] = newType.id
typeMap.set(lowerTypeName, newType.id)
} catch (typeError) {
results.push({
success: false,
item: componentData,
error: `创建配件类型失败: ${typeError instanceof Error ? typeError.message : '未知错误'}`
})
continue
}
}
}
// 验证配件类型ID
if (!componentTypeId) {
results.push({
success: false,
item: componentData,
error: '无法确定配件类型'
})
continue
}
// 检查是否已存在相同的配件
const existingComponent = await prisma.component.findFirst({
where: {
name: componentData.name,
brand: componentData.brand,
model: componentData.model
}
})
if (existingComponent) {
results.push({
success: false,
item: componentData,
error: `配件已存在: ${componentData.name} - ${componentData.brand} ${componentData.model}`
})
continue
}
// 创建配件
const newComponent = await prisma.component.create({
data: {
name: componentData.name.trim(),
brand: componentData.brand.trim(),
model: componentData.model.trim(),
price,
description: componentData.description?.trim() || '',
imageUrl: componentData.imageUrl?.trim() || '',
stock,
specifications: componentData.specifications?.trim() || '',
componentTypeId
},
include: {
componentType: true
}
})
results.push({
success: true,
item: componentData,
componentId: newComponent.id
})
} catch (error) {
console.error('处理单个配件时出错:', error)
results.push({
success: false,
item: componentData,
error: error instanceof Error ? error.message : '创建配件失败'
})
}
}
// 统计结果
const successCount = results.filter(r => r.success).length
const failCount = results.filter(r => !r.success).length
return NextResponse.json({
success: true,
results,
summary: {
total: components.length,
successful: successCount,
failed: failCount,
newTypesCreated: Object.keys(createdTypes).length
}
})
} catch (error) {
console.error('批量导入配件失败:', error)
return NextResponse.json(
{
success: false,
message: '批量导入失败',
error: error instanceof Error ? error.message : '未知错误'
},
{ status: 500 }
)
}
}

110
app/api/components/route.ts Normal file
View File

@ -0,0 +1,110 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const typeId = searchParams.get('type')
const brand = searchParams.get('brand')
const minPrice = searchParams.get('minPrice')
const maxPrice = searchParams.get('maxPrice')
const search = searchParams.get('search')
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '12')
const where: any = {}
if (typeId) {
where.componentTypeId = typeId
}
if (brand) {
where.brand = {
contains: brand,
mode: 'insensitive'
}
}
if (minPrice || maxPrice) {
where.price = {}
if (minPrice) where.price.gte = parseFloat(minPrice)
if (maxPrice) where.price.lte = parseFloat(maxPrice)
}
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ brand: { contains: search, mode: 'insensitive' } },
{ model: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } }
]
}
const [components, total] = await Promise.all([
prisma.component.findMany({
where,
include: {
componentType: true
},
orderBy: {
createdAt: 'desc'
},
skip: (page - 1) * limit,
take: limit
}),
prisma.component.count({ where })
])
const totalPages = Math.ceil(total / limit)
return NextResponse.json({
components,
pagination: {
page,
limit,
total,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
})
} catch (error) {
console.error('Components fetch error:', error)
return NextResponse.json(
{ message: '获取配件列表失败' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
try {
// 这个API需要管理员权限暂时简化处理
const data = await request.json()
const component = await prisma.component.create({
data: {
name: data.name,
brand: data.brand,
model: data.model,
price: parseFloat(data.price),
description: data.description,
imageUrl: data.imageUrl,
stock: parseInt(data.stock),
specifications: data.specifications,
componentTypeId: data.componentTypeId
},
include: {
componentType: true
}
})
return NextResponse.json(component, { status: 201 })
} catch (error) {
console.error('Component create error:', error)
return NextResponse.json(
{ message: '创建配件失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,165 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyToken } from '@/lib/auth'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
const order = await prisma.order.findFirst({
where: {
id: id,
userId: decoded.userId
},
include: {
orderItems: {
include: {
component: {
include: {
componentType: true
}
}
}
},
user: {
select: {
username: true,
email: true
}
}
}
})
if (!order) {
return NextResponse.json(
{ message: '订单不存在' },
{ status: 404 }
)
}
return NextResponse.json(order)
} catch (error) {
console.error('获取订单详情失败:', error)
return NextResponse.json(
{ message: '获取订单详情失败' },
{ status: 500 }
)
}
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
const { action } = await request.json()
// 查找订单
const order = await prisma.order.findFirst({
where: {
id: id,
userId: decoded.userId
},
include: {
orderItems: true
}
})
if (!order) {
return NextResponse.json(
{ message: '订单不存在' },
{ status: 404 }
)
}
// 处理不同的操作
if (action === 'cancel') {
// 只有待确认状态的订单可以取消
if (order.status !== 'PENDING') {
return NextResponse.json(
{ message: '当前订单状态不允许取消' },
{ status: 400 }
)
} // 更新订单状态为取消
const updatedOrder = await prisma.order.update({
where: { id: id },
data: { status: 'CANCELLED' },
include: {
orderItems: {
include: {
component: {
include: {
componentType: true
}
}
}
}
}
})
// 恢复库存
for (const item of order.orderItems) {
await prisma.component.update({
where: { id: item.componentId },
data: {
stock: {
increment: item.quantity
}
}
})
}
return NextResponse.json(updatedOrder)
}
return NextResponse.json(
{ message: '不支持的操作' },
{ status: 400 }
)
} catch (error) {
console.error('更新订单失败:', error)
return NextResponse.json(
{ message: '更新订单失败' },
{ status: 500 }
)
}
}

155
app/api/orders/route.ts Normal file
View File

@ -0,0 +1,155 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyToken } from '@/lib/auth'
export async function POST(request: NextRequest) {
try {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
const { items } = await request.json()
if (!items || items.length === 0) {
return NextResponse.json(
{ message: '订单商品不能为空' },
{ status: 400 }
)
}
// 生成订单号
const orderNumber = `ORD${Date.now()}`
// 计算总金额
let totalAmount = 0
const orderItems = []
for (const item of items) {
const component = await prisma.component.findUnique({
where: { id: item.componentId }
})
if (!component) {
return NextResponse.json(
{ message: `配件不存在: ${item.componentId}` },
{ status: 400 }
)
}
if (component.stock < item.quantity) {
return NextResponse.json(
{ message: `配件库存不足: ${component.name}` },
{ status: 400 }
)
}
const itemTotal = component.price * item.quantity
totalAmount += itemTotal
orderItems.push({
componentId: component.id,
quantity: item.quantity,
price: component.price
})
}
// 创建订单
const order = await prisma.order.create({
data: {
orderNumber,
totalAmount,
userId: decoded.userId,
orderItems: {
create: orderItems
}
}, include: {
orderItems: {
include: {
component: {
include: {
componentType: true
}
}
}
}
}
})
// 更新库存
for (const item of items) {
await prisma.component.update({
where: { id: item.componentId },
data: {
stock: {
decrement: item.quantity
}
}
})
}
return NextResponse.json(order, { status: 201 })
} catch (error) {
console.error('创建订单失败:', error)
return NextResponse.json(
{ message: '创建订单失败' },
{ status: 500 }
)
}
}
export async function GET(request: NextRequest) {
try {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
} const orders = await prisma.order.findMany({
where: { userId: decoded.userId },
include: {
orderItems: {
include: {
component: {
include: {
componentType: true
}
}
}
}
},
orderBy: {
createdAt: 'desc'
}
})
return NextResponse.json(orders)
} catch (error) {
console.error('获取订单列表失败:', error)
return NextResponse.json(
{ message: '获取订单列表失败' },
{ status: 500 }
)
}
}

View File

@ -0,0 +1,61 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyToken, hashPassword, verifyPassword } from '@/lib/auth'
export async function PUT(request: NextRequest) {
try {
const authHeader = request.headers.get('Authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ message: '未提供认证令牌' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json({ message: '无效的认证令牌' }, { status: 401 })
}
const { currentPassword, newPassword } = await request.json()
if (!currentPassword || !newPassword) {
return NextResponse.json({ message: '请提供当前密码和新密码' }, { status: 400 })
}
if (newPassword.length < 6) {
return NextResponse.json({ message: '新密码长度至少6位' }, { status: 400 })
}
// 获取用户当前信息
const user = await prisma.user.findUnique({
where: { id: decoded.userId }
})
if (!user) {
return NextResponse.json({ message: '用户不存在' }, { status: 404 })
}
// 验证当前密码
const isCurrentPasswordValid = await verifyPassword(currentPassword, user.password)
if (!isCurrentPasswordValid) {
return NextResponse.json({ message: '当前密码错误' }, { status: 400 })
}
// 哈希新密码
const hashedNewPassword = await hashPassword(newPassword)
// 更新密码
await prisma.user.update({
where: { id: decoded.userId },
data: {
password: hashedNewPassword
}
})
return NextResponse.json({ message: '密码修改成功' })
} catch (error) {
console.error('修改密码失败:', error)
return NextResponse.json({ message: '修改密码失败' }, { status: 500 })
}
}

View File

@ -0,0 +1,86 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyToken } from '@/lib/auth'
export async function GET(request: NextRequest) {
try {
const authHeader = request.headers.get('Authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ message: '未提供认证令牌' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json({ message: '无效的认证令牌' }, { status: 401 })
}
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
select: {
id: true,
email: true,
username: true,
name: true,
phone: true,
address: true,
isAdmin: true,
createdAt: true
}
})
if (!user) {
return NextResponse.json({ message: '用户不存在' }, { status: 404 })
}
return NextResponse.json(user)
} catch (error) {
console.error('获取用户资料失败:', error)
return NextResponse.json({ message: '获取用户资料失败' }, { status: 500 })
}
}
export async function PUT(request: NextRequest) {
try {
const authHeader = request.headers.get('Authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ message: '未提供认证令牌' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json({ message: '无效的认证令牌' }, { status: 401 })
}
const { name, phone, address } = await request.json()
const updatedUser = await prisma.user.update({
where: { id: decoded.userId },
data: {
name,
phone,
address
},
select: {
id: true,
email: true,
username: true,
name: true,
phone: true,
address: true,
isAdmin: true,
createdAt: true
}
})
return NextResponse.json(updatedUser)
} catch (error) {
console.error('更新用户资料失败:', error)
return NextResponse.json({ message: '更新用户资料失败' }, { status: 500 })
}
}

170
app/api/user/stats/route.ts Normal file
View File

@ -0,0 +1,170 @@
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { verifyToken } from '@/lib/auth'
export async function GET(request: NextRequest) {
try {
const authHeader = request.headers.get('authorization')
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return NextResponse.json(
{ message: '请先登录' },
{ status: 401 }
)
}
const token = authHeader.substring(7)
const decoded = verifyToken(token)
if (!decoded) {
return NextResponse.json(
{ message: '登录已过期' },
{ status: 401 }
)
}
// 获取用户基本信息
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
select: {
id: true,
createdAt: true,
username: true,
email: true
}
})
if (!user) {
return NextResponse.json(
{ message: '用户不存在' },
{ status: 404 }
)
}
// 获取订单统计
const orderStats = await prisma.order.groupBy({
by: ['status'],
where: {
userId: decoded.userId
},
_count: {
id: true
},
_sum: {
totalAmount: true
}
})
// 获取总订单数和总消费
const totalOrders = await prisma.order.count({
where: {
userId: decoded.userId
}
})
const totalSpent = await prisma.order.aggregate({
where: {
userId: decoded.userId,
status: {
in: ['CONFIRMED', 'SHIPPED', 'DELIVERED']
}
},
_sum: {
totalAmount: true
}
}) // 获取购物车商品数量
const cartItemsCount = await prisma.cartItem.count({
where: {
userId: decoded.userId
}
})
// 获取最近订单
const recentOrders = await prisma.order.findMany({
where: {
userId: decoded.userId
},
orderBy: {
createdAt: 'desc'
},
take: 5,
select: {
id: true,
status: true,
totalAmount: true,
createdAt: true
}
})
// 按状态分组订单统计
const ordersByStatus = {
PENDING: 0,
CONFIRMED: 0,
SHIPPED: 0,
DELIVERED: 0,
CANCELLED: 0
}
orderStats.forEach(stat => {
ordersByStatus[stat.status as keyof typeof ordersByStatus] = stat._count.id
})
// 获取用户最喜欢的配件类型(购买最多的类型)
const favoriteComponentType = await prisma.orderItem.groupBy({
by: ['componentId'],
where: {
order: {
userId: decoded.userId,
status: {
in: ['CONFIRMED', 'SHIPPED', 'DELIVERED']
}
}
},
_sum: {
quantity: true
},
orderBy: {
_sum: {
quantity: 'desc'
}
},
take: 1
})
let favoriteType = null
if (favoriteComponentType.length > 0) {
const component = await prisma.component.findUnique({
where: { id: favoriteComponentType[0].componentId },
include: {
componentType: true
}
})
favoriteType = component?.componentType?.name || null
} return NextResponse.json({
user: {
id: user.id,
username: user.username,
email: user.email,
memberSince: user.createdAt
},
orderStats: {
total: totalOrders,
byStatus: ordersByStatus,
totalSpent: totalSpent._sum.totalAmount || 0
},
recentOrders,
favoriteComponentType: favoriteType, summary: {
totalOrders,
totalSpent: totalSpent._sum.totalAmount || 0,
pendingOrders: ordersByStatus.PENDING,
completedOrders: ordersByStatus.DELIVERED,
cartItems: cartItemsCount
}
})
} catch (error) {
console.error('获取用户统计失败:', error)
return NextResponse.json(
{ message: '获取统计数据失败' },
{ status: 500 }
)
}
}

456
app/build/page.tsx Normal file
View File

@ -0,0 +1,456 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { Check, Plus, Package, ShoppingCart, Cpu, HardDrive, MemoryStick, Zap, Monitor, Box, Search, X } from 'lucide-react'
interface ComponentType {
id: string
name: string
description?: string
}
interface Component {
id: string
name: string
brand: string
price: number
imageUrl?: string
componentType: ComponentType
}
interface BuildConfiguration {
[key: string]: Component | null
}
const componentIcons: { [key: string]: any } = {
'CPU': Cpu,
'内存': MemoryStick,
'硬盘': HardDrive,
'主板': Zap,
'显卡': Monitor,
'机箱': Box,
}
export default function BuildPage() { const [componentTypes, setComponentTypes] = useState<ComponentType[]>([])
const [availableComponents, setAvailableComponents] = useState<{ [key: string]: Component[] }>({})
const [buildConfig, setBuildConfig] = useState<BuildConfiguration>({})
const [isLoading, setIsLoading] = useState(true)
const [selectedType, setSelectedType] = useState<string | null>(null)
const [searchTerm, setSearchTerm] = useState('')
useEffect(() => {
loadData()
}, [])
const loadData = async () => {
try {
// 加载配件类型
const typesResponse = await fetch('/api/component-types')
if (typesResponse.ok) {
const types = await typesResponse.json()
setComponentTypes(types) // 为每种类型加载组件
const componentsData: { [key: string]: Component[] } = {}
for (const type of types) {
const componentsResponse = await fetch(`/api/components?type=${encodeURIComponent(type.id)}&limit=1000`)
if (componentsResponse.ok) {
const components = await componentsResponse.json()
componentsData[type.name] = components.components || []
}
}
setAvailableComponents(componentsData)
}
} catch (error) {
console.error('加载数据失败:', error)
} finally {
setIsLoading(false)
}
}
const selectComponent = (typeName: string, component: Component) => {
setBuildConfig(prev => ({
...prev,
[typeName]: component
}))
setSelectedType(null)
setSearchTerm('')
}
const removeComponent = (typeName: string) => {
setBuildConfig(prev => ({
...prev,
[typeName]: null
}))
}
const getTotalPrice = () => {
return Object.values(buildConfig).reduce((total, component) => {
return total + (component?.price || 0)
}, 0)
}
const getCompletionRate = () => {
const totalTypes = componentTypes.length
const selectedTypes = Object.values(buildConfig).filter(Boolean).length
return totalTypes > 0 ? Math.round((selectedTypes / totalTypes) * 100) : 0
}
const addAllToCart = async () => {
const selectedComponents = Object.values(buildConfig).filter(Boolean)
if (selectedComponents.length === 0) {
alert('请先选择配件')
return
}
const token = localStorage.getItem('token')
if (!token) {
alert('请先登录')
return
}
try {
// 批量添加到购物车
for (const component of selectedComponents) {
await fetch('/api/cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
componentId: component!.id,
quantity: 1
})
})
}
window.dispatchEvent(new Event('cart-updated'))
alert('配置已添加到购物车!')
} catch (error) {
console.error('添加到购物车失败:', error)
alert('添加失败,请重试')
}
}
const createOrder = async () => {
const selectedComponents = Object.values(buildConfig).filter(Boolean)
if (selectedComponents.length !== componentTypes.length) {
alert('请完成所有配件的选择后再下单')
return
}
try {
const user = JSON.parse(localStorage.getItem('user') || 'null')
if (!user) {
alert('请先登录')
window.location.href = '/login'
return
}
const orderItems = selectedComponents.map(component => ({
componentId: component!.id,
quantity: 1,
price: component!.price
}))
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({
items: orderItems,
totalAmount: getTotalPrice()
})
})
if (response.ok) {
alert('订单创建成功!')
setBuildConfig({})
window.location.href = '/orders'
} else {
alert('订单创建失败,请重试')
}
} catch (error) {
console.error('创建订单失败:', error)
alert('订单创建失败,请重试')
}
}
const getFilteredComponents = (typeName: string) => {
console.log(availableComponents,typeName);
const components = availableComponents[typeName] || []
if (!searchTerm) return components
return components.filter(component =>
component.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
component.brand.toLowerCase().includes(searchTerm.toLowerCase())
)
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">PC装机配置</h1>
<p className="text-xl text-gray-600 mb-8"></p>
{/* Progress */}
<div className="max-w-md mx-auto">
<div className="flex justify-between text-sm text-gray-600 mb-2">
<span></span>
<span>{getCompletionRate()}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${getCompletionRate()}%` }}
></div>
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
{/* Components Selection */}
<div className="lg:col-span-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{componentTypes.map((type) => {
const Icon = componentIcons[type.name] || Package
const selectedComponent = buildConfig[type.name]
return (
<div key={type.id} className="bg-white rounded-lg shadow-sm p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center space-x-3">
<Icon className="h-6 w-6 text-blue-600" />
<div>
<h3 className="font-semibold text-gray-900">{type.name}</h3>
<p className="text-sm text-gray-600">{type.description}</p>
</div>
</div>
{selectedComponent && (
<Check className="h-6 w-6 text-green-600" />
)}
</div>
{selectedComponent ? (
<div className="space-y-4">
<div className="border rounded-lg p-4 bg-green-50">
<div className="flex items-center space-x-4">
<div className="w-16 h-16 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
{selectedComponent.imageUrl ? (
<img loading='lazy'
src={selectedComponent.imageUrl}
alt={selectedComponent.name}
className="max-w-full max-h-full object-contain"
/>
) : (
<Package className="h-8 w-8 text-gray-400" />
)}
</div>
<div className="flex-1">
<h4 className="font-medium text-gray-900">{selectedComponent.name}</h4>
<p className="text-sm text-gray-600">{selectedComponent.brand}</p>
<p className="text-lg font-bold text-red-600">¥{selectedComponent.price}</p>
</div>
</div>
</div>
<div className="flex space-x-2">
<button
onClick={() => setSelectedType(type.name)}
className="flex-1 text-blue-600 border border-blue-600 py-2 px-4 rounded-lg hover:bg-blue-50 transition-colors"
>
</button>
<button
onClick={() => removeComponent(type.name)}
className="text-red-600 border border-red-600 py-2 px-4 rounded-lg hover:bg-red-50 transition-colors"
>
</button>
</div>
</div>
) : (
<button
onClick={() => setSelectedType(type.name)}
className="w-full border-2 border-dashed border-gray-300 rounded-lg py-8 text-gray-500 hover:border-blue-300 hover:text-blue-600 transition-colors"
>
<Plus className="h-8 w-8 mx-auto mb-2" />
<p> {type.name}</p>
</button>
)}
</div>
)
})}
</div>
</div>
{/* Summary */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-sm p-6 sticky top-4">
<h2 className="text-lg font-semibold text-gray-900 mb-4"></h2>
<div className="space-y-3 mb-6">
{componentTypes.map((type) => {
const component = buildConfig[type.name]
return (
<div key={type.id} className="flex justify-between text-sm">
<span className="text-gray-600">{type.name}:</span>
<span className={component ? "text-gray-900 font-medium" : "text-gray-400"}>
{component ? `¥${component.price}` : '未选择'}
</span>
</div>
)
})}
<div className="border-t border-gray-200 pt-3">
<div className="flex justify-between font-semibold">
<span>:</span>
<span className="text-red-600">¥{getTotalPrice().toFixed(2)}</span>
</div>
</div>
</div>
<div className="space-y-3">
<button
onClick={addAllToCart}
disabled={Object.values(buildConfig).filter(Boolean).length === 0}
className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
>
<ShoppingCart className="h-5 w-5 inline mr-2" />
</button>
<button
onClick={createOrder}
disabled={getCompletionRate() < 100}
className="w-full border border-green-600 text-green-600 py-3 px-4 rounded-lg hover:bg-green-50 disabled:border-gray-300 disabled:text-gray-400 disabled:cursor-not-allowed transition-colors"
>
</button>
</div>
<p className="text-xs text-gray-500 mt-4">
: {getCompletionRate()}% ({Object.values(buildConfig).filter(Boolean).length}/{componentTypes.length})
</p>
</div>
</div>
</div>
{/* Component Selection Modal */}
{selectedType && (
<div className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm flex items-center justify-center p-4 z-50"> <div className="bg-white rounded-xl max-w-5xl w-full max-h-[85vh] overflow-hidden shadow-2xl">
<div className="p-6 border-b border-gray-200 bg-gray-50">
<div className="flex justify-between items-center mb-4">
<h3 className="text-xl font-semibold text-gray-900"> {selectedType}</h3>
<button
onClick={() => {
setSelectedType(null)
setSearchTerm('')
}}
className="text-gray-400 hover:text-gray-600 p-2 hover:bg-gray-200 rounded-full transition-colors"
>
<X className="h-5 w-5" />
</button>
</div>
{/* Search Box */}
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search className="h-5 w-5 text-gray-400" />
</div>
<input
type="text"
placeholder={`搜索${selectedType}配件...`}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-colors"
/>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="absolute inset-y-0 right-0 pr-3 flex items-center"
>
<X className="h-4 w-4 text-gray-400 hover:text-gray-600" />
</button>
)}
</div>
{/* Results count */}
<div className="mt-3 text-sm text-gray-600">
{getFilteredComponents(selectedType).length}
{searchTerm && ` (搜索: "${searchTerm}")`}
</div>
</div>
<div className="p-6 overflow-y-auto" style={{ maxHeight: 'calc(85vh - 200px)' }}>
{getFilteredComponents(selectedType).length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{getFilteredComponents(selectedType).map((component) => (
<div
key={component.id}
className="border border-gray-200 rounded-lg p-5 hover:shadow-lg hover:border-blue-300 cursor-pointer transition-all duration-200 bg-white"
onClick={() => selectComponent(selectedType, component)}
>
<div className="w-full h-36 bg-gray-50 rounded-lg mb-4 flex items-center justify-center overflow-hidden">
{component.imageUrl ? (
<img loading='lazy'
src={component.imageUrl}
alt={component.name}
className="max-w-full max-h-full object-contain"
/>
) : (
<Package className="h-12 w-12 text-gray-400" />
)}
</div>
<h4 className="font-medium text-gray-900 mb-2 line-clamp-2 text-sm">
{component.name}
</h4>
<p className="text-sm text-gray-600 mb-3">{component.brand}</p>
<div className="flex items-center justify-between">
<p className="text-lg font-bold text-red-600">¥{component.price}</p>
<button className="text-blue-600 hover:text-blue-800 text-sm font-medium">
</button>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12">
<Package className="h-16 w-16 text-gray-300 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
{searchTerm ? '没有找到匹配的配件' : '暂无配件'}
</h3>
<p className="text-gray-500">
{searchTerm ? '尝试调整搜索关键词' : '此类型暂无可用配件'}
</p>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="mt-4 text-blue-600 hover:text-blue-800 font-medium"
>
</button>
)}
</div>
)}
</div>
</div>
</div>
)}
</div>
</div>
)
}

393
app/cart/page.tsx Normal file
View File

@ -0,0 +1,393 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { Trash2, Plus, Minus, ShoppingBag, ArrowLeft } from 'lucide-react'
interface CartItem {
id: string
quantity: number
component: {
id: string
name: string
brand: string
price: number
imageUrl?: string | null
stock: number
componentType: {
name: string
}
}
}
export default function CartPage() {
const [cartItems, setCartItems] = useState<CartItem[]>([])
const [isLoading, setIsLoading] = useState(true)
const [isUpdating, setIsUpdating] = useState<string | null>(null)
useEffect(() => {
loadCartItems()
}, [])
const loadCartItems = async () => {
try {
const token = localStorage.getItem('token')
if (!token) {
setIsLoading(false)
return
}
const response = await fetch('/api/cart', {
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
const items = await response.json()
setCartItems(items)
} else if (response.status === 401) {
// 登录过期清除token
localStorage.removeItem('token')
localStorage.removeItem('user')
window.location.href = '/login'
}
} catch (error) {
console.error('加载购物车失败:', error)
} finally {
setIsLoading(false)
}
}
const updateQuantity = async (cartItemId: string, newQuantity: number) => {
if (newQuantity < 1) return
setIsUpdating(cartItemId)
try {
const token = localStorage.getItem('token')
const response = await fetch(`/api/cart/${cartItemId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ quantity: newQuantity })
})
if (response.ok) {
// 重新加载购物车
await loadCartItems()
} else {
const data = await response.json()
alert(data.message || '更新失败')
}
} catch (error) {
console.error('更新数量失败:', error)
alert('更新失败')
} finally {
setIsUpdating(null)
}
}
const removeFromCart = async (cartItemId: string) => {
if (!confirm('确定要从购物车中移除这个商品吗?')) return
setIsUpdating(cartItemId)
try {
const token = localStorage.getItem('token')
const response = await fetch(`/api/cart/${cartItemId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
// 重新加载购物车
await loadCartItems()
} else {
const data = await response.json()
alert(data.message || '删除失败')
}
} catch (error) {
console.error('删除失败:', error)
alert('删除失败')
} finally {
setIsUpdating(null)
}
}
const clearCart = async () => {
if (!confirm('确定要清空购物车吗?')) return
try {
const token = localStorage.getItem('token')
const response = await fetch('/api/cart', {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
setCartItems([])
} else {
const data = await response.json()
alert(data.message || '清空失败')
}
} catch (error) {
console.error('清空购物车失败:', error)
alert('清空失败')
}
}
const getTotalPrice = () => {
return cartItems.reduce((total, item) => {
return total + (item.component.price * item.quantity)
}, 0)
}
const getTotalItems = () => {
return cartItems.reduce((total, item) => total + item.quantity, 0)
}
const createOrder = async () => {
if (cartItems.length === 0) {
alert('购物车为空')
return
}
try {
const user = JSON.parse(localStorage.getItem('user') || 'null')
if (!user) {
alert('请先登录')
window.location.href = '/login'
return
}
const orderItems = cartItems.map(item => ({
componentId: item.component.id,
quantity: item.quantity,
price: item.component.price
}))
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({
items: orderItems,
totalAmount: getTotalPrice()
})
})
if (response.ok) {
// 清空购物车
await clearCart()
alert('订单创建成功!')
window.location.href = '/orders'
} else {
const data = await response.json()
alert(data.message || '订单创建失败')
}
} catch (error) {
console.error('创建订单失败:', error)
alert('订单创建失败,请重试')
}
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
</div>
)
}
const user = JSON.parse(localStorage.getItem('user') || 'null')
if (!user) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<ShoppingBag className="h-16 w-16 text-gray-300 mx-auto mb-4" />
<h2 className="text-2xl font-semibold text-gray-900 mb-4"></h2>
<p className="text-gray-600 mb-8"></p>
<Link
href="/login"
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
>
</Link>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div className="flex items-center space-x-4">
<Link
href="/components"
className="flex items-center text-blue-600 hover:text-blue-800"
>
<ArrowLeft className="h-5 w-5 mr-2" />
</Link>
<h1 className="text-3xl font-bold text-gray-900"></h1>
</div>
{cartItems.length > 0 && (
<button
onClick={clearCart}
className="text-red-600 hover:text-red-800 text-sm font-medium"
>
</button>
)}
</div>
{cartItems.length === 0 ? (
<div className="text-center py-16">
<ShoppingBag className="h-16 w-16 text-gray-300 mx-auto mb-4" />
<h2 className="text-2xl font-semibold text-gray-900 mb-4"></h2>
<p className="text-gray-600 mb-8"></p>
<Link
href="/components"
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
>
</Link>
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Cart Items */}
<div className="lg:col-span-2">
<div className="bg-white rounded-lg shadow-sm">
<div className="p-6 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">
({getTotalItems()} )
</h2>
</div>
<div className="divide-y divide-gray-200">
{cartItems.map((item) => (
<div key={item.id} className="p-6">
<div className="flex items-center space-x-4">
<div className="flex-shrink-0 w-20 h-20 bg-gray-100 rounded-lg overflow-hidden">
{item.component.imageUrl ? (
<img
src={item.component.imageUrl}
alt={item.component.name}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<ShoppingBag className="h-8 w-8 text-gray-400" />
</div>
)}
</div>
<div className="flex-1">
<div className="flex justify-between">
<div>
<h3 className="font-medium text-gray-900">{item.component.name}</h3>
<p className="text-sm text-gray-600">{item.component.brand}</p>
<p className="text-xs text-gray-500">{item.component.componentType.name}</p>
</div>
<div className="text-right">
<p className="font-semibold text-gray-900">¥{item.component.price}</p>
<p className="text-sm text-gray-600">: {item.component.stock}</p>
</div>
</div>
<div className="flex items-center justify-between mt-4">
s <div className="flex items-center space-x-2">
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
disabled={item.quantity <= 1 || isUpdating === item.id}
className="p-1 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Minus className="h-4 w-4" />
</button>
<span className="w-12 text-center">{item.quantity}</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
disabled={item.quantity >= item.component.stock || isUpdating === item.id}
className="p-1 rounded-md hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Plus className="h-4 w-4" />
</button>
</div>
<div className="flex items-center space-x-4">
<p className="font-semibold text-lg">
¥{(item.component.price * item.quantity).toFixed(2)}
</p>
<button
onClick={() => removeFromCart(item.id)}
disabled={isUpdating === item.id}
className="text-red-600 hover:text-red-800 disabled:opacity-50"
>
<Trash2 className="h-5 w-5" />
</button>
</div>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Order Summary */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-sm p-6 sticky top-4">
<h2 className="text-lg font-semibold text-gray-900 mb-4"></h2>
<div className="space-y-3 mb-6">
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium">{getTotalItems()} </span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium">¥{getTotalPrice().toFixed(2)}</span>
</div>
<div className="border-t border-gray-200 pt-3">
<div className="flex justify-between">
<span className="font-semibold text-lg">:</span>
<span className="font-semibold text-lg text-red-600">
¥{getTotalPrice().toFixed(2)}
</span>
</div>
</div>
</div>
<button
onClick={createOrder}
className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg hover:bg-blue-700 transition-colors font-medium"
>
</button>
<p className="text-xs text-gray-500 mt-4 text-center">
</p>
</div>
</div>
</div>
)}
</div>
</div>
)
}

View File

@ -0,0 +1,255 @@
import { notFound } from 'next/navigation'
import { prisma } from '@/lib/prisma'
import { AddToCartButton } from '@/components/AddToCartButton'
import { ArrowLeft, Package, Star, Shield, Truck, RotateCcw } from 'lucide-react'
import Link from 'next/link'
interface ComponentDetailPageProps {
params: Promise<{
id: string
}>
}
export default async function ComponentDetailPage({ params }: ComponentDetailPageProps) {
const component = await prisma.component.findUnique({
where: { id: (await params).id },
include: {
componentType: true
}
})
if (!component) {
notFound()
}
const specifications = component.specifications ? JSON.parse(component.specifications) : {}
// 获取同类型的其他产品推荐
const relatedComponents = await prisma.component.findMany({
where: {
componentTypeId: component.componentTypeId,
id: { not: component.id }
},
take: 4,
orderBy: {
createdAt: 'desc'
}
})
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Breadcrumb */}
<nav className="flex items-center space-x-2 text-sm text-gray-500 mb-8">
<Link href="/" className="hover:text-blue-600"></Link>
<span>/</span>
<Link href="/components" className="hover:text-blue-600"></Link>
<span>/</span>
<Link href={`/components?type=${component.componentType.name}`} className="hover:text-blue-600">
{component.componentType.name}
</Link>
<span>/</span>
<span className="text-gray-900">{component.name}</span>
</nav>
{/* Back Button */}
<Link
href="/components"
className="inline-flex items-center text-blue-600 hover:text-blue-800 mb-6"
>
<ArrowLeft className="h-4 w-4 mr-2" />
</Link>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
{/* Product Image */}
<div className="space-y-4">
<div className="aspect-square bg-white rounded-lg border border-gray-200 flex items-center justify-center">
{component.imageUrl ? (
<img loading='lazy'
src={component.imageUrl}
alt={component.name}
className="max-w-full max-h-full object-contain grow"
/>
) : (
<div className="text-center text-gray-400">
<Package className="h-24 w-24 mx-auto mb-4" />
<p></p>
</div>
)}
</div>
</div>
{/* Product Info */}
<div className="space-y-6">
<div>
<div className="flex items-center space-x-2 text-sm text-gray-500 mb-2">
<span className="bg-blue-100 text-blue-800 px-2 py-1 rounded">
{component.componentType.name}
</span>
<span className="bg-gray-100 text-gray-800 px-2 py-1 rounded">
{component.brand}
</span>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-4">
{component.name}
</h1>
<div className="flex items-center space-x-4 mb-4">
<div className="flex items-center">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`h-5 w-5 ${i < 4 ? 'text-yellow-400 fill-current' : 'text-gray-300'}`}
/>
))}
<span className="ml-2 text-sm text-gray-600">(128 )</span>
</div>
</div>
</div>
{/* Price */}
<div className="border-t border-b border-gray-200 py-6">
<div className="flex items-baseline space-x-2">
<span className="text-3xl font-bold text-red-600">
¥{component.price}
</span>
<span className="text-lg text-gray-500 line-through">
¥{Math.round(component.price * 1.2)}
</span>
<span className="bg-red-100 text-red-800 text-sm px-2 py-1 rounded">
8
</span>
</div>
<p className="text-sm text-gray-600 mt-2"></p>
</div>
{/* Stock Status */}
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-600">:</span>
{component.stock > 0 ? (
<span className="text-green-600 font-medium">
({component.stock} )
</span>
) : (
<span className="text-red-600 font-medium"></span>
)}
</div>
{/* Add to Cart */}
<div className="space-y-4">
<AddToCartButton
componentId={component.id}
disabled={component.stock === 0}
className="w-full"
/>
<button className="w-full border border-gray-300 text-gray-700 py-3 px-6 rounded-lg hover:bg-gray-50 transition-colors">
</button>
</div>
{/* Service Promises */}
<div className="grid grid-cols-3 gap-4 pt-6 border-t border-gray-200">
<div className="text-center">
<Truck className="h-8 w-8 text-blue-600 mx-auto mb-2" />
<p className="text-xs text-gray-600"></p>
</div>
<div className="text-center">
<Shield className="h-8 w-8 text-green-600 mx-auto mb-2" />
<p className="text-xs text-gray-600"></p>
</div>
<div className="text-center">
<RotateCcw className="h-8 w-8 text-orange-600 mx-auto mb-2" />
<p className="text-xs text-gray-600">7退</p>
</div>
</div>
</div>
</div>
{/* Product Details */}
<div className="mt-12 grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Description */}
<div className="lg:col-span-2">
<h2 className="text-2xl font-bold text-gray-900 mb-6"></h2>
<div className="bg-white rounded-lg p-6 shadow-sm">
<h3 className="font-semibold text-gray-900 mb-4"></h3>
<p className="text-gray-700 leading-relaxed mb-6">
{component.description || '暂无详细描述'}
</p>
{Object.keys(specifications).length > 0 && (
<>
<h3 className="font-semibold text-gray-900 mb-4"></h3>
<div className="space-y-3">
{Object.entries(specifications).map(([key, value]) => (
<div key={key} className="flex justify-between py-2 border-b border-gray-100">
<span className="text-gray-600">{key}:</span>
<span className="font-medium text-gray-900">{String(value)}</span>
</div>
))}
</div>
</>
)}
</div>
</div>
{/* Product Info */}
<div>
<h2 className="text-2xl font-bold text-gray-900 mb-6"></h2>
<div className="bg-white rounded-lg p-6 shadow-sm space-y-4">
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium">{component.brand}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium">{component.model}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium">{component.componentType.name}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium">{component.stock} </span>
</div>
</div>
</div>
</div>
{/* Related Products */}
{relatedComponents.length > 0 && (
<div className="mt-12">
<h2 className="text-2xl font-bold text-gray-900 mb-6"></h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{relatedComponents.map((item) => (
<Link
key={item.id}
href={`/components/${item.id}`}
className="bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow"
>
<div className="p-4">
<div className="aspect-square bg-gray-100 rounded-lg mb-4 flex items-center justify-center">
{item.imageUrl ? (
<img loading='lazy'
src={item.imageUrl}
alt={item.name}
className="max-w-full max-h-full object-contain"
/>
) : (
<Package className="h-12 w-12 text-gray-400" />
)}
</div>
<h3 className="font-medium text-gray-900 mb-2 line-clamp-2">
{item.name}
</h3>
<p className="text-lg font-bold text-red-600">¥{item.price}</p>
</div>
</Link>
))}
</div>
</div>
)}
</div>
</div>
)
}

294
app/components/page.tsx Normal file
View File

@ -0,0 +1,294 @@
'use client'
import { useState, useEffect } from 'react'
import { ComponentCard } from '@/components/ComponentCard'
import { Search, Filter } from 'lucide-react'
interface Component {
id: string
name: string
brand: string
model: string
price: number
description?: string
imageUrl?: string
stock: number
componentType: {
id: string
name: string
}
}
interface ComponentType {
id: string
name: string
_count: {
components: number
}
}
interface Pagination {
page: number
limit: number
total: number
totalPages: number
hasNext: boolean
hasPrev: boolean
}
export default function ComponentsPage() {
const [components, setComponents] = useState<Component[]>([])
const [componentTypes, setComponentTypes] = useState<ComponentType[]>([])
const [pagination, setPagination] = useState<Pagination>({
page: 1,
limit: 12,
total: 0,
totalPages: 0,
hasNext: false,
hasPrev: false
})
const [filters, setFilters] = useState({
search: '',
type: '',
brand: '',
minPrice: '',
maxPrice: ''
})
const [isLoading, setIsLoading] = useState(true)
const [showFilters, setShowFilters] = useState(false)
useEffect(() => {
fetchComponentTypes()
}, [])
useEffect(() => {
fetchComponents()
}, [filters, pagination.page])
const fetchComponentTypes = async () => {
try {
const response = await fetch('/api/component-types')
if (response.ok) {
const data = await response.json()
setComponentTypes(data)
}
} catch (error) {
console.error('Failed to fetch component types:', error)
}
}
const fetchComponents = async () => {
setIsLoading(true)
try {
const searchParams = new URLSearchParams()
if (filters.search) searchParams.set('search', filters.search)
if (filters.type) searchParams.set('type', filters.type)
if (filters.brand) searchParams.set('brand', filters.brand)
if (filters.minPrice) searchParams.set('minPrice', filters.minPrice)
if (filters.maxPrice) searchParams.set('maxPrice', filters.maxPrice)
searchParams.set('page', pagination.page.toString())
searchParams.set('limit', pagination.limit.toString())
const response = await fetch(`/api/components?${searchParams}`)
if (response.ok) {
const data = await response.json()
setComponents(data.components)
setPagination(data.pagination)
}
} catch (error) {
console.error('Failed to fetch components:', error)
} finally {
setIsLoading(false)
}
}
const handleFilterChange = (key: string, value: string) => {
setFilters({ ...filters, [key]: value })
setPagination({ ...pagination, page: 1 })
}
const handlePageChange = (newPage: number) => {
setPagination({ ...pagination, page: newPage })
window.scrollTo({ top: 0, behavior: 'smooth' })
}
const resetFilters = () => {
setFilters({
search: '',
type: '',
brand: '',
minPrice: '',
maxPrice: ''
})
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-4"></h1>
{/* Search Bar */}
<div className="flex flex-col md:flex-row gap-4 mb-6">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<input
type="text"
placeholder="搜索配件名称、品牌、型号..."
value={filters.search}
onChange={(e) => handleFilterChange('search', e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<button
onClick={() => setShowFilters(!showFilters)}
className="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50"
>
<Filter className="h-5 w-5" />
</button>
</div>
{/* Filters */}
{showFilters && (
<div className="bg-white p-6 rounded-lg shadow-sm border mb-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<select
value={filters.type}
onChange={(e) => handleFilterChange('type', e.target.value)}
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value=""></option>
{componentTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.name} ({type._count.components})
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
placeholder="品牌名称"
value={filters.brand}
onChange={(e) => handleFilterChange('brand', e.target.value)}
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="number"
placeholder="0"
value={filters.minPrice}
onChange={(e) => handleFilterChange('minPrice', e.target.value)}
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="number"
placeholder="10000"
value={filters.maxPrice}
onChange={(e) => handleFilterChange('maxPrice', e.target.value)}
className="w-full border border-gray-300 rounded-md px-3 py-2 focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div className="mt-4 flex justify-end">
<button
onClick={resetFilters}
className="px-4 py-2 text-gray-600 hover:text-gray-800"
>
</button>
</div>
</div>
)}
</div>
{/* Results */}
<div className="mb-6">
<p className="text-gray-600">
{pagination.total}
</p>
</div>
{/* Loading */}
{isLoading ? (
<div className="flex justify-center items-center py-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
) : (
<>
{/* Components Grid */}
{components.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 mb-8">
{components.map((component) => (
<ComponentCard key={component.id} component={component} />
))}
</div>
) : (
<div className="text-center py-12">
<p className="text-gray-500 text-lg"></p>
</div>
)}
{/* Pagination */}
{pagination.totalPages > 1 && (
<div className="flex justify-center items-center space-x-2">
<button
onClick={() => handlePageChange(pagination.page - 1)}
disabled={!pagination.hasPrev}
className="px-3 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
</button>
<div className="flex space-x-1">
{Array.from({ length: pagination.totalPages }, (_, i) => i + 1).map((page) => (
<button
key={page}
onClick={() => handlePageChange(page)}
className={`px-3 py-2 border rounded-md ${
page === pagination.page
? 'bg-blue-600 text-white border-blue-600'
: 'border-gray-300 hover:bg-gray-50'
}`}
>
{page}
</button>
))}
</div>
<button
onClick={() => handlePageChange(pagination.page + 1)}
disabled={!pagination.hasNext}
className="px-3 py-2 border border-gray-300 rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50"
>
</button>
</div>
)}
</>
)}
</div>
</div>
)
}

View File

@ -1,6 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { Navbar } from "@/components/Navbar";
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
@ -13,8 +14,8 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "PC DIY商城 - 电脑配件在线选购",
description: "Generated by create next app", description: "专业的电脑DIY配件商城提供CPU、内存、硬盘、主板、显卡、机箱等优质配件",
}; };
export default function RootLayout({ export default function RootLayout({
@ -23,11 +24,14 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="zh-CN">
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased`} className={`${geistSans.variable} ${geistMono.variable} antialiased`}
> >
<Navbar />
<main className="min-h-screen">
{children} {children}
</main>
</body> </body>
</html> </html>
); );

130
app/login/page.tsx Normal file
View File

@ -0,0 +1,130 @@
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
export default function LoginPage() {
const [formData, setFormData] = useState({
email: '',
password: ''
})
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError('')
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
const data = await response.json()
if (response.ok) {
localStorage.setItem('token', data.token)
localStorage.setItem('user', JSON.stringify(data.user))
// 触发用户更新事件
window.dispatchEvent(new Event('user-updated'))
router.push('/')
} else {
setError(data.message || '登录失败')
}
} catch (error) {
setError('网络错误,请稍后重试')
} finally {
setIsLoading(false)
}
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
{' '}
<Link href="/register" className="font-medium text-blue-600 hover:text-blue-500">
</Link>
</p>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
<div className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
</label>
<input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入邮箱地址"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
</label>
<input
id="password"
name="password"
type="password"
required
value={formData.password}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入密码"
/>
</div>
</div>
<div>
<button
type="submit"
disabled={isLoading}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
>
{isLoading ? '登录中...' : '登录'}
</button>
</div>
<div className="text-center">
<p className="text-sm text-gray-600">
admin@pcdiy.comadmin123
</p>
</div>
</form>
</div>
</div>
)
}

398
app/orders/[id]/page.tsx Normal file
View File

@ -0,0 +1,398 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import {
ArrowLeft,
Package,
Calendar,
DollarSign,
User,
MapPin,
Phone,
Mail,
Truck,
CheckCircle,
XCircle,
Clock
} from 'lucide-react'
interface OrderItem {
id: string
quantity: number
price: number
component: {
id: string
name: string
brand: string
model: string
imageUrl?: string
componentType: {
name: string
}
}
}
interface Order {
id: string
orderNumber: string
totalAmount: number
status: string
createdAt: string
updatedAt: string
orderItems: OrderItem[]
user: {
username: string
email: string
}
}
const statusMap: { [key: string]: { text: string; color: string; icon: any } } = {
PENDING: { text: '待确认', color: 'bg-yellow-100 text-yellow-800 border-yellow-200', icon: Clock },
CONFIRMED: { text: '已确认', color: 'bg-blue-100 text-blue-800 border-blue-200', icon: CheckCircle },
PROCESSING: { text: '处理中', color: 'bg-purple-100 text-purple-800 border-purple-200', icon: Package },
SHIPPED: { text: '已发货', color: 'bg-green-100 text-green-800 border-green-200', icon: Truck },
DELIVERED: { text: '已送达', color: 'bg-green-100 text-green-800 border-green-200', icon: CheckCircle },
CANCELLED: { text: '已取消', color: 'bg-red-100 text-red-800 border-red-200', icon: XCircle },
}
export default function OrderDetailPage({ params }: { params: Promise<{ id: string }> }) {
const [order, setOrder] = useState<Order | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [isCancelling, setIsCancelling] = useState(false)
const [orderId, setOrderId] = useState<string>('')
const router = useRouter()
useEffect(() => {
const getParams = async () => {
const { id } = await params
setOrderId(id)
}
getParams()
}, [params])
useEffect(() => {
if (orderId) {
loadOrderDetail()
}
}, [orderId])
const loadOrderDetail = async () => {
try {
const token = localStorage.getItem('token')
if (!token) {
router.push('/login')
return
}
const response = await fetch(`/api/orders/${(await params).id}`, {
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
const data = await response.json()
setOrder(data)
} else if (response.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('user')
router.push('/login')
} else if (response.status === 404) {
router.push('/orders')
}
} catch (error) {
console.error('加载订单详情失败:', error)
} finally {
setIsLoading(false)
}
}
const handleCancelOrder = async () => {
if (!order || order.status !== 'PENDING') {
alert('当前订单状态不允许取消')
return
}
if (!confirm('确定要取消这个订单吗?取消后将恢复商品库存。')) {
return
}
setIsCancelling(true)
try {
const token = localStorage.getItem('token')
const response = await fetch(`/api/orders/${orderId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ action: 'cancel' })
})
if (response.ok) {
const updatedOrder = await response.json()
setOrder(updatedOrder)
alert('订单已成功取消')
} else {
const error = await response.json()
alert(error.message || '取消订单失败')
}
} catch (error) {
console.error('取消订单失败:', error)
alert('取消订单失败,请重试')
} finally {
setIsCancelling(false)
}
}
const handleReorder = async () => {
if (!order) return
const token = localStorage.getItem('token')
if (!token) {
alert('请先登录')
return
}
try {
// 批量添加订单商品到购物车
for (const item of order.orderItems) {
await fetch('/api/cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
componentId: item.component.id,
quantity: item.quantity
})
})
}
window.dispatchEvent(new Event('cart-updated'))
alert('商品已添加到购物车!')
router.push('/cart')
} catch (error) {
console.error('添加到购物车失败:', error)
alert('添加失败,请重试')
}
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
</div>
)
}
if (!order) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<Package className="h-24 w-24 text-gray-300 mx-auto mb-6" />
<h2 className="text-2xl font-semibold text-gray-900 mb-4"></h2>
<Link
href="/orders"
className="inline-flex items-center bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
>
<ArrowLeft className="h-5 w-5 mr-2" />
</Link>
</div>
</div>
)
}
const statusConfig = statusMap[order.status] || { text: order.status, color: 'bg-gray-100 text-gray-800 border-gray-200', icon: Package }
const StatusIcon = statusConfig.icon
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div className="flex items-center space-x-4">
<Link
href="/orders"
className="text-gray-600 hover:text-gray-900 transition-colors"
>
<ArrowLeft className="h-6 w-6" />
</Link>
<div>
<h1 className="text-3xl font-bold text-gray-900"></h1>
<p className="text-gray-600">: {order.orderNumber}</p>
</div>
</div>
</div>
<div className="space-y-6">
{/* Order Status */}
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className={`p-3 rounded-full ${statusConfig.color.replace('text-', 'bg-').replace('bg-', 'bg-').replace('-100', '-200')}`}>
<StatusIcon className="h-6 w-6" />
</div>
<div>
<h2 className="text-xl font-semibold text-gray-900"></h2>
<span className={`inline-flex px-3 py-1 text-sm font-medium rounded-full border ${statusConfig.color}`}>
{statusConfig.text}
</span>
</div>
</div>
<div className="text-right">
<p className="text-sm text-gray-600"></p>
<p className="font-medium text-gray-900">
{new Date(order.createdAt).toLocaleString('zh-CN')}
</p>
{order.updatedAt !== order.createdAt && (
<>
<p className="text-sm text-gray-600 mt-2"></p>
<p className="font-medium text-gray-900">
{new Date(order.updatedAt).toLocaleString('zh-CN')}
</p>
</>
)}
</div>
</div>
</div>
{/* Order Items */}
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900"></h3>
</div>
<div className="p-6">
<div className="space-y-6">
{order.orderItems.map((item, index) => (
<div key={item.id} className={`flex items-center space-x-4 ${index > 0 ? 'pt-6 border-t border-gray-200' : ''}`}>
<div className="w-20 h-20 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
{item.component?.imageUrl ? (
<img loading='lazy'
src={item.component.imageUrl}
alt={item.component?.name || '商品图片'}
className="max-w-full max-h-full object-contain"
/>
) : (
<Package className="h-10 w-10 text-gray-400" />
)}
</div>
<div className="flex-1">
<h4 className="font-medium text-gray-900 text-lg">{item.component?.name || '未知商品'}</h4>
<div className="mt-1 space-y-1">
<p className="text-sm text-gray-600">
: {item.component?.brand || '未知品牌'}
</p>
<p className="text-sm text-gray-600">
: {item.component?.model || '未知型号'}
</p>
<p className="text-sm text-gray-600">
: {item.component?.componentType?.name || '未知类型'}
</p>
</div>
</div>
<div className="text-right">
<p className="text-lg font-medium text-gray-900">¥{item.price}</p>
<p className="text-sm text-gray-600">: {item.quantity}</p>
<p className="text-sm font-medium text-red-600">
: ¥{(item.price * item.quantity).toFixed(2)}
</p>
</div>
</div>
))}
</div>
</div>
</div>
{/* Order Summary */}
<div className="bg-white rounded-lg shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-600">:</span>
<span className="text-gray-900">{order.orderItems.length} </span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">:</span>
<span className="text-gray-900">¥{order.totalAmount}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-600">:</span>
<span className="text-gray-900"></span>
</div>
<div className="border-t border-gray-200 pt-3">
<div className="flex justify-between">
<span className="text-lg font-semibold text-gray-900">:</span>
<span className="text-xl font-bold text-red-600">¥{order.totalAmount}</span>
</div>
</div>
</div>
</div>
{/* User Info */}
<div className="bg-white rounded-lg shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-center space-x-3">
<User className="h-5 w-5 text-gray-400" />
<div>
<p className="text-sm text-gray-600"></p>
<p className="font-medium text-gray-900">{order.user.username}</p>
</div>
</div>
<div className="flex items-center space-x-3">
<Mail className="h-5 w-5 text-gray-400" />
<div>
<p className="text-sm text-gray-600"></p>
<p className="font-medium text-gray-900">{order.user.email}</p>
</div>
</div>
</div>
</div>
{/* Actions */}
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex justify-between items-center">
<div className="text-sm text-gray-600">
<Calendar className="h-4 w-4 inline mr-1" />
{new Date(order.createdAt).toLocaleDateString('zh-CN')}
</div>
<div className="flex space-x-3">
{order.status === 'PENDING' && (
<button
onClick={handleCancelOrder}
disabled={isCancelling}
className="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 disabled:bg-red-300 disabled:cursor-not-allowed transition-colors"
>
{isCancelling ? '取消中...' : '取消订单'}
</button>
)}
{order.status === 'DELIVERED' && (
<button
onClick={handleReorder}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
</button>
)}
<Link
href="/orders"
className="bg-gray-100 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-200 transition-colors"
>
</Link>
</div>
</div>
</div>
</div>
</div>
</div>
)
}

334
app/orders/page.tsx Normal file
View File

@ -0,0 +1,334 @@
'use client'
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { Package, Calendar, DollarSign, Eye, Filter } from 'lucide-react'
interface OrderItem {
id: string
quantity: number
price: number
component: {
id: string
name: string
brand: string
imageUrl?: string
componentType: {
name: string
}
}
}
interface Order {
id: string
orderNumber: string
totalAmount: number
status: string
createdAt: string
orderItems: OrderItem[]
}
const statusMap: { [key: string]: { text: string; color: string } } = {
PENDING: { text: '待确认', color: 'bg-yellow-100 text-yellow-800' },
CONFIRMED: { text: '已确认', color: 'bg-blue-100 text-blue-800' },
PROCESSING: { text: '处理中', color: 'bg-purple-100 text-purple-800' },
SHIPPED: { text: '已发货', color: 'bg-green-100 text-green-800' },
DELIVERED: { text: '已送达', color: 'bg-green-100 text-green-800' },
CANCELLED: { text: '已取消', color: 'bg-red-100 text-red-800' },
}
export default function OrdersPage() {
const [orders, setOrders] = useState<Order[]>([])
const [isLoading, setIsLoading] = useState(true)
const [selectedStatus, setSelectedStatus] = useState<string>('ALL')
const [cancellingOrderId, setCancellingOrderId] = useState<string | null>(null)
useEffect(() => {
loadOrders()
}, [])
const loadOrders = async () => {
try {
const token = localStorage.getItem('token')
if (!token) {
window.location.href = '/login'
return
}
const response = await fetch('/api/orders', {
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
const data = await response.json()
setOrders(data)
} else if (response.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('user')
window.location.href = '/login'
}
} catch (error) {
console.error('加载订单失败:', error)
} finally {
setIsLoading(false)
}
}
const filteredOrders = selectedStatus === 'ALL'
? orders
: orders.filter(order => order.status === selectedStatus)
const handleCancelOrder = async (orderId: string) => {
if (!confirm('确定要取消这个订单吗?取消后将恢复商品库存。')) {
return
}
setCancellingOrderId(orderId)
try {
const token = localStorage.getItem('token')
const response = await fetch(`/api/orders/${orderId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ action: 'cancel' })
})
if (response.ok) {
// 重新加载订单列表
await loadOrders()
alert('订单已成功取消')
} else {
const error = await response.json()
alert(error.message || '取消订单失败')
}
} catch (error) {
console.error('取消订单失败:', error)
alert('取消订单失败,请重试')
} finally {
setCancellingOrderId(null)
} }
const handleReorder = async (order: Order) => {
const token = localStorage.getItem('token')
if (!token) {
alert('请先登录')
return
}
try {
// 批量添加订单商品到购物车
for (const item of order.orderItems) {
await fetch('/api/cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
componentId: item.component.id,
quantity: item.quantity
})
})
}
window.dispatchEvent(new Event('cart-updated'))
alert('商品已添加到购物车!')
window.location.href = '/cart'
} catch (error) {
console.error('添加到购物车失败:', error)
alert('添加失败,请重试')
}
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold text-gray-900"></h1>
<Link
href="/components"
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
>
</Link>
</div>
{/* Status Filter */}
<div className="bg-white rounded-lg shadow-sm p-4 mb-6">
<div className="flex items-center space-x-4">
<Filter className="h-5 w-5 text-gray-600" />
<span className="text-gray-700">:</span>
<div className="flex flex-wrap gap-2">
<button
onClick={() => setSelectedStatus('ALL')}
className={`px-3 py-1 rounded-full text-sm transition-colors ${
selectedStatus === 'ALL'
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
</button>
{Object.entries(statusMap).map(([status, config]) => (
<button
key={status}
onClick={() => setSelectedStatus(status)}
className={`px-3 py-1 rounded-full text-sm transition-colors ${
selectedStatus === status
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{config.text}
</button>
))}
</div>
</div>
</div>
{/* Orders List */}
{filteredOrders.length === 0 ? (
<div className="text-center py-16">
<Package className="h-24 w-24 text-gray-300 mx-auto mb-6" />
<h2 className="text-2xl font-semibold text-gray-900 mb-4">
{selectedStatus === 'ALL' ? '暂无订单' : '暂无该状态的订单'}
</h2>
<p className="text-gray-600 mb-8">
{selectedStatus === 'ALL'
? '还没有任何订单,去看看有什么好东西吧!'
: '可以试试其他状态筛选'
}
</p>
<Link
href="/components"
className="inline-flex items-center bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
>
<Package className="h-5 w-5 mr-2" />
</Link>
</div>
) : (
<div className="space-y-6">
{filteredOrders.map((order) => (
<div key={order.id} className="bg-white rounded-lg shadow-sm overflow-hidden">
{/* Order Header */}
<div className="bg-gray-50 px-6 py-4 border-b border-gray-200">
<div className="flex justify-between items-center">
<div className="flex items-center space-x-6">
<div>
<p className="text-sm text-gray-600"></p>
<p className="font-medium text-gray-900">{order.orderNumber}</p>
</div>
<div>
<p className="text-sm text-gray-600"></p>
<p className="font-medium text-gray-900">
{new Date(order.createdAt).toLocaleString('zh-CN')}
</p>
</div>
<div>
<p className="text-sm text-gray-600"></p>
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${
statusMap[order.status]?.color || 'bg-gray-100 text-gray-800'
}`}>
{statusMap[order.status]?.text || order.status}
</span>
</div>
</div>
<div className="text-right">
<p className="text-sm text-gray-600"></p>
<p className="text-xl font-bold text-red-600">¥{order.totalAmount.toFixed(2)}</p>
</div>
</div>
</div>
{/* Order Items */}
<div className="p-6">
<div className="space-y-4">
{order.orderItems.map((item) => (
<div key={item.id} className="flex items-center space-x-4"> <div className="w-16 h-16 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
{item.component?.imageUrl ? (
<img loading='lazy'
src={item.component.imageUrl}
alt={item.component?.name || '商品图片'}
className="max-w-full max-h-full object-contain"
/>
) : (
<Package className="h-8 w-8 text-gray-400" />
)}
</div><div className="flex-1">
<h3 className="font-medium text-gray-900">{item.component?.name || '未知商品'}</h3>
<p className="text-sm text-gray-600">
{item.component?.brand || '未知品牌'} | {item.component?.componentType?.name || '未知类型'}
</p>
<p className="text-sm text-gray-600">: {item.quantity}</p>
</div>
<div className="text-right">
<p className="font-medium text-gray-900">¥{item.price}</p>
<p className="text-sm text-gray-600">
: ¥{(item.price * item.quantity).toFixed(2)}
</p>
</div>
</div>
))}
</div>
{/* Order Actions */}
<div className="flex justify-between items-center mt-6 pt-6 border-t border-gray-200">
<div className="flex items-center space-x-2 text-sm text-gray-600">
<Calendar className="h-4 w-4" />
<span> {order.orderItems.length} </span>
<DollarSign className="h-4 w-4 ml-4" />
<span>: ¥{order.totalAmount}</span>
</div>
<div className="flex space-x-3">
<Link
href={`/orders/${order.id}`}
className="inline-flex items-center text-blue-600 hover:text-blue-800 text-sm"
>
<Eye className="h-4 w-4 mr-1" />
</Link> {order.status === 'PENDING' && (
<button
onClick={() => handleCancelOrder(order.id)}
disabled={cancellingOrderId === order.id}
className="text-red-600 hover:text-red-800 text-sm disabled:text-red-300 disabled:cursor-not-allowed"
>
{cancellingOrderId === order.id ? '取消中...' : '取消订单'}
</button>
)}
{order.status === 'DELIVERED' && (
<button
onClick={() => handleReorder(order)}
className="text-blue-600 hover:text-blue-800 text-sm"
>
</button>
)}
</div>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div>
)
}

View File

@ -1,103 +1,126 @@
import Image from "next/image"; import Link from 'next/link'
import { prisma } from '@/lib/prisma'
import { ComponentCard } from '@/components/ComponentCard'
export default async function Home() {
// 获取每种配件类型的前几个商品
const componentTypes = await prisma.componentType.findMany({
include: {
components: {
take: 6,
orderBy: {
createdAt: 'desc'
}
}
}
})
export default function Home() {
return ( return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]"> <div className="min-h-screen bg-gray-50">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start"> {/* Hero Section */}
<Image <section className="bg-gradient-to-r from-blue-600 to-purple-600 text-white py-20">
className="dark:invert" <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
src="/next.svg" <h1 className="text-4xl md:text-6xl font-bold mb-6">
alt="Next.js logo" PC DIY商城
width={180} </h1>
height={38} <p className="text-xl md:text-2xl mb-8">
priority 线
/> </p>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]"> <div className="space-x-4">
<li className="mb-2 tracking-[-.01em]"> <Link
Get started by editing{" "} href="/components"
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold"> className="bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-gray-100 transition-colors"
app/page.tsx >
</code>
. </Link>
</li> <Link
<li className="tracking-[-.01em]"> href="/build"
Save and see your changes instantly. className="border-2 border-white text-white px-8 py-3 rounded-lg font-semibold hover:bg-white hover:text-blue-600 transition-colors"
</li> >
</ol> 线
</Link>
</div>
</div>
</section>
<div className="flex gap-4 items-center flex-col sm:flex-row"> {/* Features Section */}
<a <section className="py-16">
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto" <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" <h2 className="text-3xl font-bold text-center mb-12"></h2>
target="_blank" <div className="grid md:grid-cols-3 gap-8">
rel="noopener noreferrer" <div className="text-center">
> <div className="bg-blue-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<Image <span className="text-2xl">🔧</span>
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div> </div>
</main> <h3 className="text-xl font-semibold mb-2"></h3>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center"> <p className="text-gray-600">
<a CPU
className="flex items-center gap-2 hover:underline hover:underline-offset-4" </p>
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div> </div>
); <div className="text-center">
<div className="bg-green-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl">💰</span>
</div>
<h3 className="text-xl font-semibold mb-2"></h3>
<p className="text-gray-600">
</p>
</div>
<div className="text-center">
<div className="bg-purple-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-2xl">🚀</span>
</div>
<h3 className="text-xl font-semibold mb-2">线</h3>
<p className="text-gray-600">
</p>
</div>
</div>
</div>
</section>
{/* Product Categories */}
<section className="py-16 bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-center mb-12"></h2>
{componentTypes.map((type) => (
<div key={type.id} className="mb-12">
<div className="flex justify-between items-center mb-6">
<h3 className="text-2xl font-semibold">{type.name}</h3>
<Link
href={`/components?type=${type.id}`}
className="text-blue-600 hover:text-blue-800"
>
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{type.components.map((component) => (
<ComponentCard key={component.id} component={component} />
))}
</div>
</div>
))}
</div>
</section>
{/* CTA Section */}
<section className="py-16 bg-gray-800 text-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-3xl font-bold mb-4"></h2>
<p className="text-xl mb-8">
</p>
<Link
href="/register"
className="bg-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors"
>
</Link>
</div>
</section>
</div>
)
} }

566
app/profile/page.tsx Normal file
View File

@ -0,0 +1,566 @@
'use client'
import { useState, useEffect } from 'react'
import { User, Mail, Phone, MapPin, Save, Edit, Key } from 'lucide-react'
interface UserProfile {
id: string
email: string
username: string
name?: string
phone?: string
address?: string
}
interface UserStats {
user: {
id: string
username: string
email: string
memberSince: string
}
orderStats: {
total: number
byStatus: {
PENDING: number
CONFIRMED: number
SHIPPED: number
DELIVERED: number
CANCELLED: number
}
totalSpent: number
}
recentOrders: Array<{
id: string
status: string
totalAmount: number
createdAt: string
}>
favoriteComponentType: string | null
summary: {
totalOrders: number
totalSpent: number
pendingOrders: number
completedOrders: number
cartItems: number
}
}
export default function ProfilePage() {
const [user, setUser] = useState<UserProfile | null>(null)
const [userStats, setUserStats] = useState<UserStats | null>(null)
const [isEditing, setIsEditing] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [isSaving, setIsSaving] = useState(false)
const [formData, setFormData] = useState({
name: '',
phone: '',
address: ''
})
const [passwordForm, setPasswordForm] = useState({
currentPassword: '',
newPassword: '',
confirmPassword: ''
})
const [showPasswordForm, setShowPasswordForm] = useState(false)
useEffect(() => {
loadUserProfile()
loadUserStats()
}, [])
const loadUserProfile = async () => {
try {
const token = localStorage.getItem('token')
if (!token) {
window.location.href = '/login'
return
}
const response = await fetch('/api/user/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
const userData = await response.json()
setUser(userData)
setFormData({
name: userData.name || '',
phone: userData.phone || '',
address: userData.address || ''
})
} else if (response.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('user')
window.location.href = '/login'
}
} catch (error) {
console.error('加载用户资料失败:', error)
} finally {
setIsLoading(false)
}
}
const loadUserStats = async () => {
try {
const token = localStorage.getItem('token')
if (!token) {
return
}
const response = await fetch('/api/user/stats', {
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
const stats = await response.json()
setUserStats(stats)
}
} catch (error) {
console.error('加载用户统计失败:', error)
}
}
const handleSaveProfile = async () => {
setIsSaving(true)
try {
const token = localStorage.getItem('token')
const response = await fetch('/api/user/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(formData)
})
if (response.ok) {
const updatedUser = await response.json()
setUser(updatedUser)
setIsEditing(false)
// 更新localStorage中的用户信息
localStorage.setItem('user', JSON.stringify(updatedUser))
alert('资料更新成功!')
} else {
alert('更新失败,请重试')
}
} catch (error) {
console.error('更新用户资料失败:', error)
alert('更新失败,请重试')
} finally {
setIsSaving(false)
}
}
const handleChangePassword = async () => {
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
alert('新密码和确认密码不匹配')
return
}
if (passwordForm.newPassword.length < 6) {
alert('新密码长度至少6位')
return
}
try {
const token = localStorage.getItem('token')
const response = await fetch('/api/user/change-password', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
currentPassword: passwordForm.currentPassword,
newPassword: passwordForm.newPassword
})
})
if (response.ok) {
alert('密码修改成功!')
setPasswordForm({
currentPassword: '',
newPassword: '',
confirmPassword: ''
})
setShowPasswordForm(false)
} else {
const error = await response.json()
alert(error.message || '密码修改失败')
}
} catch (error) {
console.error('修改密码失败:', error)
alert('修改失败,请重试')
}
}
const handleCancel = () => {
setFormData({
name: user?.name || '',
phone: user?.phone || '',
address: user?.address || ''
})
setIsEditing(false)
}
if (isLoading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="text-center mb-8">
<div className="w-24 h-24 bg-blue-600 rounded-full flex items-center justify-center mx-auto mb-4">
<User className="h-12 w-12 text-white" />
</div>
<h1 className="text-3xl font-bold text-gray-900">{user?.name || user?.username}</h1>
<p className="text-gray-600">{user?.email}</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Profile Info */}
<div className="lg:col-span-2">
<div className="bg-white rounded-lg shadow-sm p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-semibold text-gray-900"></h2>
{!isEditing ? (
<button
onClick={() => setIsEditing(true)}
className="inline-flex items-center text-blue-600 hover:text-blue-800"
>
<Edit className="h-4 w-4 mr-2" />
</button>
) : (
<div className="flex space-x-2">
<button
onClick={handleSaveProfile}
disabled={isSaving}
className="inline-flex items-center bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
<Save className="h-4 w-4 mr-2" />
{isSaving ? '保存中...' : '保存'}
</button>
<button
onClick={handleCancel}
className="text-gray-600 hover:text-gray-800 px-4 py-2"
>
</button>
</div>
)}
</div>
<div className="space-y-6">
{/* Email (readonly) */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<Mail className="h-4 w-4 inline mr-2" />
</label>
<input
type="email"
value={user?.email || ''}
disabled
className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-50 text-gray-500"
/>
<p className="text-xs text-gray-500 mt-1"></p>
</div>
{/* Username (readonly) */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<User className="h-4 w-4 inline mr-2" />
</label>
<input
type="text"
value={user?.username || ''}
disabled
className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-50 text-gray-500"
/>
<p className="text-xs text-gray-500 mt-1"></p>
</div>
{/* Name */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
disabled={!isEditing}
className={`w-full px-3 py-2 border border-gray-300 rounded-lg ${
isEditing ? 'focus:ring-2 focus:ring-blue-500 focus:border-blue-500' : 'bg-gray-50'
}`}
placeholder="请输入真实姓名"
/>
</div>
{/* Phone */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<Phone className="h-4 w-4 inline mr-2" />
</label>
<input
type="tel"
value={formData.phone}
onChange={(e) => setFormData({...formData, phone: e.target.value})}
disabled={!isEditing}
className={`w-full px-3 py-2 border border-gray-300 rounded-lg ${
isEditing ? 'focus:ring-2 focus:ring-blue-500 focus:border-blue-500' : 'bg-gray-50'
}`}
placeholder="请输入手机号码"
/>
</div>
{/* Address */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
<MapPin className="h-4 w-4 inline mr-2" />
</label>
<textarea
value={formData.address}
onChange={(e) => setFormData({...formData, address: e.target.value})}
disabled={!isEditing}
rows={3}
className={`w-full px-3 py-2 border border-gray-300 rounded-lg ${
isEditing ? 'focus:ring-2 focus:ring-blue-500 focus:border-blue-500' : 'bg-gray-50'
}`}
placeholder="请输入详细地址"
/>
</div>
</div>
</div>
{/* Password Change */}
<div className="bg-white rounded-lg shadow-sm p-6 mt-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-semibold text-gray-900"></h2>
<button
onClick={() => setShowPasswordForm(!showPasswordForm)}
className="inline-flex items-center text-blue-600 hover:text-blue-800"
>
<Key className="h-4 w-4 mr-2" />
</button>
</div>
{showPasswordForm && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="password"
value={passwordForm.currentPassword}
onChange={(e) => setPasswordForm({...passwordForm, currentPassword: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入当前密码"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="password"
value={passwordForm.newPassword}
onChange={(e) => setPasswordForm({...passwordForm, newPassword: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入新密码至少6位"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="password"
value={passwordForm.confirmPassword}
onChange={(e) => setPasswordForm({...passwordForm, confirmPassword: e.target.value})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请再次输入新密码"
/>
</div>
<div className="flex space-x-3">
<button
onClick={handleChangePassword}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
>
</button>
<button
onClick={() => {
setShowPasswordForm(false)
setPasswordForm({
currentPassword: '',
newPassword: '',
confirmPassword: ''
})
}}
className="text-gray-600 hover:text-gray-800 px-4 py-2"
>
</button>
</div>
</div>
)}
</div>
</div>
{/* Quick Actions */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<div className="space-y-3">
<a
href="/orders"
className="block w-full text-left px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors"
>
</a>
<a
href="/cart"
className="block w-full text-left px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors"
>
</a>
<a
href="/build"
className="block w-full text-left px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors"
>
</a>
<a
href="/components"
className="block w-full text-left px-4 py-3 text-gray-700 hover:bg-gray-50 rounded-lg transition-colors"
>
</a>
</div>
</div> {/* Account Stats */}
<div className="bg-white rounded-lg shadow-sm p-6 mt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<div className="space-y-3">
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-gray-900">
{userStats?.user?.memberSince ?
new Date(userStats.user.memberSince).toLocaleDateString('zh-CN') :
'--'
}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-gray-900">
{userStats?.summary?.totalOrders || 0}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-red-600">
¥{userStats?.summary?.totalSpent?.toFixed(2) || '0.00'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-orange-600">
{userStats?.summary?.pendingOrders || 0}
</span>
</div> <div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-green-600">
{userStats?.summary?.completedOrders || 0}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-blue-600">
{userStats?.summary?.cartItems || 0}
</span>
</div>
{userStats?.favoriteComponentType && (
<div className="flex justify-between">
<span className="text-gray-600">:</span>
<span className="font-medium text-blue-600">
{userStats.favoriteComponentType}
</span>
</div> )}
</div>
</div>
{/* Recent Orders */}
{userStats?.recentOrders && userStats.recentOrders.length > 0 && (
<div className="bg-white rounded-lg shadow-sm p-6 mt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4"></h3>
<div className="space-y-3">
{userStats.recentOrders.map((order) => (
<div key={order.id} className="flex justify-between items-center p-3 border border-gray-200 rounded-lg">
<div>
<p className="font-medium text-gray-900"> #{order.id.slice(-8)}</p>
<p className="text-sm text-gray-500">
{new Date(order.createdAt).toLocaleDateString('zh-CN')}
</p>
</div>
<div className="text-right">
<p className="font-medium text-red-600">¥{order.totalAmount.toFixed(2)}</p>
<span className={`text-xs px-2 py-1 rounded-full ${
order.status === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :
order.status === 'CONFIRMED' ? 'bg-blue-100 text-blue-800' :
order.status === 'SHIPPED' ? 'bg-purple-100 text-purple-800' :
order.status === 'PROCESSING' ? 'bg-purple-100 text-purple-800' :
order.status === 'DELIVERED' ? 'bg-green-100 text-green-800' :
'bg-red-100 text-red-800'
}`}>
{order.status === 'PENDING' ? '待确认' :
order.status === 'CONFIRMED' ? '已确认' :
order.status === 'SHIPPED' ? '已发货' :
order.status === 'DELIVERED' ? '已送达' :
order.status === 'PROCESSING' ? '处理中' :
'已取消'}
</span>
</div>
</div>
))}
<div className="text-center">
<a
href="/orders"
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
>
</a>
</div>
</div>
</div>
)}
</div>
</div>
</div>
</div>
)
}

215
app/register/page.tsx Normal file
View File

@ -0,0 +1,215 @@
'use client'
import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
export default function RegisterPage() {
const [formData, setFormData] = useState({
email: '',
username: '',
password: '',
confirmPassword: '',
name: '',
phone: '',
address: ''
})
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError('')
if (formData.password !== formData.confirmPassword) {
setError('两次输入的密码不一致')
setIsLoading(false)
return
}
try {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: formData.email,
username: formData.username,
password: formData.password,
name: formData.name,
phone: formData.phone,
address: formData.address
}),
})
const data = await response.json()
if (response.ok) {
router.push('/login?message=注册成功,请登录')
} else {
setError(data.message || '注册失败')
}
} catch (error) {
setError('网络错误,请稍后重试')
} finally {
setIsLoading(false)
}
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
{' '}
<Link href="/login" className="font-medium text-blue-600 hover:text-blue-500">
</Link>
</p>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
<div className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
*
</label>
<input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入邮箱地址"
/>
</div>
<div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
*
</label>
<input
id="username"
name="username"
type="text"
required
value={formData.username}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入用户名"
/>
</div>
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
</label>
<input
id="name"
name="name"
type="text"
value={formData.name}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入真实姓名"
/>
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700">
</label>
<input
id="phone"
name="phone"
type="tel"
value={formData.phone}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入联系电话"
/>
</div>
<div>
<label htmlFor="address" className="block text-sm font-medium text-gray-700">
</label>
<input
id="address"
name="address"
type="text"
value={formData.address}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入收货地址"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
*
</label>
<input
id="password"
name="password"
type="password"
required
value={formData.password}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入密码"
/>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700">
*
</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
required
value={formData.confirmPassword}
onChange={handleChange}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="请再次输入密码"
/>
</div>
</div>
<div>
<button
type="submit"
disabled={isLoading}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
>
{isLoading ? '注册中...' : '注册'}
</button>
</div>
</form>
</div>
</div>
)
}

179
bun.lock
View File

@ -4,9 +4,20 @@
"": { "": {
"name": "pc-diy-store", "name": "pc-diy-store",
"dependencies": { "dependencies": {
"@prisma/client": "^6.10.1",
"@types/bcryptjs": "^3.0.0",
"@types/jsonwebtoken": "^9.0.10",
"bcryptjs": "^3.0.2",
"chart.js": "^4.5.0",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.522.0",
"next": "15.3.4", "next": "15.3.4",
"next-auth": "^4.24.11",
"prisma": "^6.10.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"recharts": "^2.15.4",
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
@ -23,6 +34,8 @@
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
"@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="], "@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg=="],
@ -79,6 +92,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
"@next/env": ["@next/env@15.3.4", "", {}, "sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ=="], "@next/env": ["@next/env@15.3.4", "", {}, "sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg=="], "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg=="],
@ -97,6 +112,22 @@
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg=="], "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg=="],
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
"@prisma/client": ["@prisma/client@6.10.1", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-Re4pMlcUsQsUTAYMK7EJ4Bw2kg3WfZAAlr8GjORJaK4VOP6LxRQUQ1TuLnxcF42XqGkWQ36q5CQF1yVadANQ6w=="],
"@prisma/config": ["@prisma/config@6.10.1", "", { "dependencies": { "jiti": "2.4.2" } }, "sha512-kz4/bnqrOrzWo8KzYguN0cden4CzLJJ+2VSpKtF8utHS3l1JS0Lhv6BLwpOX6X9yNreTbZQZwewb+/BMPDCIYQ=="],
"@prisma/debug": ["@prisma/debug@6.10.1", "", {}, "sha512-k2YT53cWxv9OLjW4zSYTZ6Z7j0gPfCzcr2Mj99qsuvlxr8WAKSZ2NcSR0zLf/mP4oxnYG842IMj3utTgcd7CaA=="],
"@prisma/engines": ["@prisma/engines@6.10.1", "", { "dependencies": { "@prisma/debug": "6.10.1", "@prisma/engines-version": "6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c", "@prisma/fetch-engine": "6.10.1", "@prisma/get-platform": "6.10.1" } }, "sha512-Q07P5rS2iPwk2IQr/rUQJ42tHjpPyFcbiH7PXZlV81Ryr9NYIgdxcUrwgVOWVm5T7ap02C0dNd1dpnNcSWig8A=="],
"@prisma/engines-version": ["@prisma/engines-version@6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c", "", {}, "sha512-ZJFTsEqapiTYVzXya6TUKYDFnSWCNegfUiG5ik9fleQva5Sk3DNyyUi7X1+0ZxWFHwHDr6BZV5Vm+iwP+LlciA=="],
"@prisma/fetch-engine": ["@prisma/fetch-engine@6.10.1", "", { "dependencies": { "@prisma/debug": "6.10.1", "@prisma/engines-version": "6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c", "@prisma/get-platform": "6.10.1" } }, "sha512-clmbG/Jgmrc/n6Y77QcBmAUlq9LrwI9Dbgy4pq5jeEARBpRCWJDJ7PWW1P8p0LfFU0i5fsyO7FqRzRB8mkdS4g=="],
"@prisma/get-platform": ["@prisma/get-platform@6.10.1", "", { "dependencies": { "@prisma/debug": "6.10.1" } }, "sha512-4CY5ndKylcsce9Mv+VWp5obbR2/86SHOLVV053pwIkhVtT9C9A83yqiqI/5kJM9T1v1u1qco/bYjDKycmei9HA=="],
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@ -131,20 +162,52 @@
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.10", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.10", "@tailwindcss/oxide": "4.1.10", "postcss": "^8.4.41", "tailwindcss": "4.1.10" } }, "sha512-B+7r7ABZbkXJwpvt2VMnS6ujcDoR2OOcFaqrLIo1xbcdxje4Vf+VgJdBzNNbrAjBj/rLZ66/tlQ1knIGNLKOBQ=="], "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.10", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.10", "@tailwindcss/oxide": "4.1.10", "postcss": "^8.4.41", "tailwindcss": "4.1.10" } }, "sha512-B+7r7ABZbkXJwpvt2VMnS6ujcDoR2OOcFaqrLIo1xbcdxje4Vf+VgJdBzNNbrAjBj/rLZ66/tlQ1knIGNLKOBQ=="],
"@types/bcryptjs": ["@types/bcryptjs@3.0.0", "", { "dependencies": { "bcryptjs": "*" } }, "sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg=="],
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
"@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="],
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
"@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@20.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA=="], "@types/node": ["@types/node@20.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="], "@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
"bcryptjs": ["bcryptjs@3.0.2", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001724", "", {}, "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA=="], "caniuse-lite": ["caniuse-lite@1.0.30001724", "", {}, "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA=="],
"chart.js": ["chart.js@4.5.0", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@ -153,18 +216,64 @@
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
"d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="],
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="], "enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"fast-equals": ["fast-equals@5.2.2", "", {}, "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
"jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
"jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
@ -187,6 +296,28 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
"lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
"lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"lucide-react": ["lucide-react@0.522.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-jnJbw974yZ7rQHHEFKJOlWAefG3ATSCZHANZxIdx8Rk/16siuwjgA4fBULpXEAWx/RlTs3FzmKW/udWUuO0aRw=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@ -195,18 +326,56 @@
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"next": ["next@15.3.4", "", { "dependencies": { "@next/env": "15.3.4", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.4", "@next/swc-darwin-x64": "15.3.4", "@next/swc-linux-arm64-gnu": "15.3.4", "@next/swc-linux-arm64-musl": "15.3.4", "@next/swc-linux-x64-gnu": "15.3.4", "@next/swc-linux-x64-musl": "15.3.4", "@next/swc-win32-arm64-msvc": "15.3.4", "@next/swc-win32-x64-msvc": "15.3.4", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA=="], "next": ["next@15.3.4", "", { "dependencies": { "@next/env": "15.3.4", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.4", "@next/swc-darwin-x64": "15.3.4", "@next/swc-linux-arm64-gnu": "15.3.4", "@next/swc-linux-arm64-musl": "15.3.4", "@next/swc-linux-x64-gnu": "15.3.4", "@next/swc-linux-x64-musl": "15.3.4", "@next/swc-win32-arm64-msvc": "15.3.4", "@next/swc-win32-x64-msvc": "15.3.4", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA=="],
"next-auth": ["next-auth@4.24.11", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", "cookie": "^0.7.0", "jose": "^4.15.5", "oauth": "^0.9.15", "openid-client": "^5.4.0", "preact": "^10.6.3", "preact-render-to-string": "^5.1.19", "uuid": "^8.3.2" }, "peerDependencies": { "@auth/core": "0.34.2", "next": "^12.2.5 || ^13 || ^14 || ^15", "nodemailer": "^6.6.5", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, "optionalPeers": ["@auth/core", "nodemailer"] }, "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw=="],
"oauth": ["oauth@0.9.15", "", {}, "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="],
"oidc-token-hash": ["oidc-token-hash@5.1.0", "", {}, "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA=="],
"openid-client": ["openid-client@5.7.1", "", { "dependencies": { "jose": "^4.15.9", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"preact": ["preact@10.26.9", "", {}, "sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA=="],
"preact-render-to-string": ["preact-render-to-string@5.2.6", "", { "dependencies": { "pretty-format": "^3.8.0" }, "peerDependencies": { "preact": ">=10" } }, "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw=="],
"pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="],
"prisma": ["prisma@6.10.1", "", { "dependencies": { "@prisma/config": "6.10.1", "@prisma/engines": "6.10.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-khhlC/G49E4+uyA3T3H5PRBut486HD2bDqE2+rvkU0pwk9IAqGFacLFUyIx9Uw+W2eCtf6XGwsp+/strUwMNPw=="],
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
"react-chartjs-2": ["react-chartjs-2@5.3.0", "", { "peerDependencies": { "chart.js": "^4.1.1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw=="],
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
"react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="],
"react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="],
"recharts": ["recharts@2.15.4", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw=="],
"recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
@ -227,12 +396,18 @@
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
@ -247,6 +422,10 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
} }
} }

View File

@ -0,0 +1,103 @@
'use client'
import { useState } from 'react'
import { ShoppingCart, Plus, Minus } from 'lucide-react'
interface AddToCartButtonProps {
componentId: string
disabled?: boolean
className?: string
}
export function AddToCartButton({ componentId, disabled = false, className = '' }: AddToCartButtonProps) {
const [quantity, setQuantity] = useState(1)
const [isLoading, setIsLoading] = useState(false)
const handleAddToCart = async () => {
const token = localStorage.getItem('token')
if (!token) {
alert('请先登录')
return
}
setIsLoading(true)
try {
const response = await fetch('/api/cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
componentId,
quantity
})
})
if (response.ok) {
// 触发购物车更新事件
window.dispatchEvent(new Event('cart-updated'))
alert('已添加到购物车!')
} else if (response.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('user')
alert('登录已过期,请重新登录')
window.location.href = '/login'
} else {
const data = await response.json()
alert(data.message || '添加失败')
}
} catch (error) {
console.error('添加到购物车失败:', error)
alert('添加失败,请重试')
} finally {
setIsLoading(false)
}
}
return (
<div className={`space-y-4 ${className}`}>
{/* Quantity Selector */}
<div className="flex items-center space-x-4">
<span className="text-sm text-gray-600">:</span>
<div className="flex items-center border border-gray-300 rounded-lg">
<button
type="button"
onClick={() => setQuantity(Math.max(1, quantity - 1))}
disabled={quantity <= 1 || disabled}
className="p-2 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Minus className="h-4 w-4" />
</button>
<span className="px-4 py-2 text-center min-w-[50px]">{quantity}</span>
<button
type="button"
onClick={() => setQuantity(quantity + 1)}
disabled={disabled}
className="p-2 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Plus className="h-4 w-4" />
</button>
</div>
</div>
{/* Add to Cart Button */}
<button
onClick={handleAddToCart}
disabled={disabled || isLoading}
className={`
flex items-center justify-center space-x-2 w-full py-3 px-6 rounded-lg font-medium transition-colors
${disabled
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-blue-600 text-white hover:bg-blue-700'
}
${isLoading ? 'opacity-50 cursor-not-allowed' : ''}
`}
>
<ShoppingCart className="h-5 w-5" />
<span>
{isLoading ? '添加中...' : disabled ? '缺货' : '加入购物车'}
</span>
</button>
</div>
)
}

View File

@ -0,0 +1,150 @@
'use client'
import Link from 'next/link'
import Image from 'next/image'
import { useState } from 'react'
import { ShoppingCart, Check } from 'lucide-react'
interface Component {
id: string
name: string
brand: string
model: string
price: number
description?: string | null
imageUrl?: string | null
stock: number
componentType?: {
id: string
name: string
}
}
interface ComponentCardProps {
component: Component
}
export function ComponentCard({ component }: ComponentCardProps) {
const [isAdding, setIsAdding] = useState(false)
const [isAdded, setIsAdded] = useState(false)
const addToCart = async (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (component.stock <= 0 || isAdding) return
const token = localStorage.getItem('token')
if (!token) {
alert('请先登录')
return
}
setIsAdding(true)
try {
const response = await fetch('/api/cart', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
componentId: component.id,
quantity: 1
})
})
if (response.ok) {
setIsAdded(true)
setTimeout(() => setIsAdded(false), 2000)
} else if (response.status === 401) {
localStorage.removeItem('token')
localStorage.removeItem('user')
alert('登录已过期,请重新登录')
window.location.href = '/login'
} else {
const data = await response.json()
alert(data.message || '添加失败')
}
} catch (error) {
console.error('添加到购物车失败:', error)
alert('添加失败')
} finally {
setIsAdding(false)
}
}
return (
<div className="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300 overflow-hidden">
<Link href={`/components/${component.id}`}>
<div className="relative h-48 bg-gray-100">
{component.imageUrl ? (
<Image
src={component.imageUrl}
alt={component.name}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
/>
) : (
<div className="flex items-center justify-center h-full">
<span className="text-gray-400 text-4xl">📦</span>
</div>
)}
</div>
</Link>
<div className="p-4">
<div className="text-sm text-gray-500 mb-1">{component.brand}</div>
<Link href={`/components/${component.id}`}>
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 transition-colors">
{component.name}
</h3>
</Link>
{component.description && (
<p className="text-gray-600 text-sm mb-3 line-clamp-2">
{component.description}
</p>
)}
<div className="flex justify-between items-center mb-3">
<span className="text-2xl font-bold text-blue-600">
¥{component.price.toLocaleString()}
</span>
<span className={`text-sm px-2 py-1 rounded ${component.stock > 0
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{component.stock > 0 ? `库存 ${component.stock}` : '缺货'}
</span>
</div>
<button
onClick={addToCart}
disabled={component.stock <= 0 || isAdding}
className={`w-full py-2 px-4 rounded-lg font-medium transition-colors flex items-center justify-center ${component.stock <= 0
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: isAdded
? 'bg-green-600 text-white'
: 'bg-blue-600 text-white hover:bg-blue-700'
}`}
>
{isAdding ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
) : isAdded ? (
<>
<Check className="h-4 w-4 mr-2" />
</>
) : (
<>
<ShoppingCart className="h-4 w-4 mr-2" />
{component.stock <= 0 ? '缺货' : '加入购物车'}
</>
)}
</button>
</div>
</div>
)
}

251
components/Navbar.tsx Normal file
View File

@ -0,0 +1,251 @@
'use client'
import Link from 'next/link'
import { useState, useEffect } from 'react'
import { usePathname, useRouter } from 'next/navigation'
import { User, ShoppingCart, Menu, X } from 'lucide-react'
interface User {
id: string
email: string
username: string
name?: string
isAdmin: boolean
}
export function Navbar() {
const [user, setUser] = useState<User | null>(null)
const [isMenuOpen, setIsMenuOpen] = useState(false)
const pathname = usePathname()
const router = useRouter()
useEffect(() => {
// 检查用户登录状态
const checkUserStatus = () => {
const token = localStorage.getItem('token')
if (token) {
const userData = localStorage.getItem('user')
if (userData) {
setUser(JSON.parse(userData))
}
} else {
setUser(null)
}
}
checkUserStatus()
// 监听localStorage变化
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'user' || e.key === 'token') {
checkUserStatus()
}
}
// 监听自定义用户更新事件
const handleUserUpdate = () => {
checkUserStatus()
}
window.addEventListener('storage', handleStorageChange)
window.addEventListener('user-updated', handleUserUpdate)
return () => {
window.removeEventListener('storage', handleStorageChange)
window.removeEventListener('user-updated', handleUserUpdate)
}
}, [])
const handleLogout = () => {
localStorage.removeItem('token')
localStorage.removeItem('user')
setUser(null)
// 触发用户更新事件
window.dispatchEvent(new Event('user-updated'))
router.push('/')
}
// 检查当前路径是否匹配
const isActive = (path: string) => {
if (path === '/') {
return pathname === '/'
}
return pathname.startsWith(path)
}
// 导航链接样式
const getLinkStyle = (path: string) => {
return isActive(path)
? 'text-blue-600 font-medium'
: 'text-gray-700 hover:text-blue-600'
}
return (
<nav className="bg-white shadow-md sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<Link href="/" className="text-2xl font-bold text-blue-600">
PC DIY商城
</Link> {/* Desktop Navigation */}
<div className="hidden md:flex space-x-8">
<Link href="/" className={getLinkStyle('/')}>
</Link>
<Link href="/components" className={getLinkStyle('/components')}>
</Link>
<Link href="/build" className={getLinkStyle('/build')}>
</Link>
</div>
{/* User Actions */}
<div className="hidden md:flex items-center space-x-4">
{user ? (
<div className="flex items-center space-x-4">
<Link href="/cart" className="text-gray-700 hover:text-blue-600">
<ShoppingCart className="h-6 w-6" />
</Link>
<div className="relative group">
<button className="flex items-center space-x-2 text-gray-700 hover:text-blue-600">
<User className="h-6 w-6" />
<span>{user.name || user.username}</span>
</button>
<div className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
<Link href="/profile" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
</Link>
<Link href="/orders" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
</Link>
{user.isAdmin && (
<Link href="/admin" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
</Link>
)}
<button
onClick={handleLogout}
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
>
退
</button>
</div>
</div>
</div>
) : (
<div className="flex items-center space-x-4">
<Link
href="/login"
className="text-gray-700 hover:text-blue-600"
>
</Link>
<Link
href="/register"
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
>
</Link>
</div>
)}
</div>
{/* Mobile menu button */}
<div className="md:hidden">
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="text-gray-700 hover:text-blue-600"
>
{isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</button>
</div>
</div> {/* Mobile Navigation */}
{isMenuOpen && (
<div className="md:hidden">
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<Link
href="/"
className={`block px-3 py-2 ${getLinkStyle('/')}`}
onClick={() => setIsMenuOpen(false)}
>
</Link>
<Link
href="/components"
className={`block px-3 py-2 ${getLinkStyle('/components')}`}
onClick={() => setIsMenuOpen(false)}
>
</Link>
<Link
href="/build"
className={`block px-3 py-2 ${getLinkStyle('/build')}`}
onClick={() => setIsMenuOpen(false)}
>
</Link>
{user ? (
<>
<Link
href="/cart"
className="block px-3 py-2 text-gray-700 hover:text-blue-600"
onClick={() => setIsMenuOpen(false)}
>
</Link>
<Link
href="/profile"
className="block px-3 py-2 text-gray-700 hover:text-blue-600"
onClick={() => setIsMenuOpen(false)}
>
</Link>
<Link
href="/orders"
className="block px-3 py-2 text-gray-700 hover:text-blue-600"
onClick={() => setIsMenuOpen(false)}
>
</Link>
{user.isAdmin && (
<Link
href="/admin"
className="block px-3 py-2 text-gray-700 hover:text-blue-600"
onClick={() => setIsMenuOpen(false)}
>
</Link>
)}
<button
onClick={handleLogout}
className="block w-full text-left px-3 py-2 text-gray-700 hover:text-blue-600"
>
退
</button>
</>
) : (
<>
<Link
href="/login"
className="block px-3 py-2 text-gray-700 hover:text-blue-600"
onClick={() => setIsMenuOpen(false)}
>
</Link>
<Link
href="/register"
className="block px-3 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
onClick={() => setIsMenuOpen(false)}
>
</Link>
</>
)}
</div>
</div>
)}
</div>
</nav>
)
}

View File

@ -0,0 +1,55 @@
'use client'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
export function withAdminAuth<T extends object>(WrappedComponent: React.ComponentType<T>) {
return function AdminProtectedComponent(props: T) {
const [isLoading, setIsLoading] = useState(true)
const [isAuthorized, setIsAuthorized] = useState(false)
const router = useRouter()
useEffect(() => {
const checkAuth = () => {
const token = localStorage.getItem('token')
const user = localStorage.getItem('user')
if (!token || !user) {
router.push('/login')
return
}
try {
const userData = JSON.parse(user)
if (!userData.isAdmin) {
router.push('/')
return
}
setIsAuthorized(true)
} catch (error) {
router.push('/login')
return
} finally {
setIsLoading(false)
}
}
checkAuth()
}, [router])
if (isLoading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
)
}
if (!isAuthorized) {
return null
}
return <WrappedComponent {...props} />
}
}

28
lib/admin-auth.ts Normal file
View File

@ -0,0 +1,28 @@
import { NextRequest } from 'next/server'
import { verifyToken } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
export async function requireAdmin(request: NextRequest) {
const authHeader = request.headers.get('Authorization')
const token = authHeader?.replace('Bearer ', '')
if (!token) {
throw new Error('未提供认证令牌')
}
const decoded = verifyToken(token)
if (!decoded) {
throw new Error('无效的认证令牌')
}
const user = await prisma.user.findUnique({
where: { id: decoded.userId },
select: { id: true, isAdmin: true }
})
if (!user || !user.isAdmin) {
throw new Error('权限不足')
}
return user
}

24
lib/auth.ts Normal file
View File

@ -0,0 +1,24 @@
import bcrypt from 'bcryptjs'
import jwt from 'jsonwebtoken'
const JWT_SECRET = process.env.JWT_SECRET || 'your-jwt-secret-here'
export async function hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, 12)
}
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
return await bcrypt.compare(password, hashedPassword)
}
export function generateToken(payload: any): string {
return jwt.sign(payload, JWT_SECRET, { expiresIn: '7d' })
}
export function verifyToken(token: string): any {
try {
return jwt.verify(token, JWT_SECRET)
} catch (error) {
return null
}
}

9
lib/prisma.ts Normal file
View File

@ -0,0 +1,9 @@
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

View File

@ -1,7 +1,9 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ images: {
remotePatterns: [new URL('https://**360buyimg.com/**')],
},
}; };
export default nextConfig; export default nextConfig;

View File

@ -6,12 +6,26 @@
"dev": "next dev --turbopack", "dev": "next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:seed": "bun run seed.ts"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.10.1",
"@types/bcryptjs": "^3.0.0",
"@types/jsonwebtoken": "^9.0.10",
"bcryptjs": "^3.0.2",
"chart.js": "^4.5.0",
"jsonwebtoken": "^9.0.2",
"lucide-react": "^0.522.0",
"next": "15.3.4",
"next-auth": "^4.24.11",
"prisma": "^6.10.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"next": "15.3.4" "recharts": "^2.15.4"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5", "typescript": "^5",

View File

@ -0,0 +1,93 @@
-- CreateEnum
CREATE TYPE "OrderStatus" AS ENUM ('PENDING', 'CONFIRMED', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CANCELLED');
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"username" TEXT NOT NULL,
"password" TEXT NOT NULL,
"name" TEXT,
"phone" TEXT,
"address" TEXT,
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "component_types" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
CONSTRAINT "component_types_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "components" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"brand" TEXT NOT NULL,
"model" TEXT NOT NULL,
"price" DOUBLE PRECISION NOT NULL,
"description" TEXT,
"imageUrl" TEXT,
"stock" INTEGER NOT NULL DEFAULT 0,
"specifications" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"componentTypeId" TEXT NOT NULL,
CONSTRAINT "components_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "orders" (
"id" TEXT NOT NULL,
"orderNumber" TEXT NOT NULL,
"totalAmount" DOUBLE PRECISION NOT NULL,
"status" "OrderStatus" NOT NULL DEFAULT 'PENDING',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "orders_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "order_items" (
"id" TEXT NOT NULL,
"quantity" INTEGER NOT NULL DEFAULT 1,
"price" DOUBLE PRECISION NOT NULL,
"orderId" TEXT NOT NULL,
"componentId" TEXT NOT NULL,
CONSTRAINT "order_items_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
-- CreateIndex
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
-- CreateIndex
CREATE UNIQUE INDEX "component_types_name_key" ON "component_types"("name");
-- CreateIndex
CREATE UNIQUE INDEX "orders_orderNumber_key" ON "orders"("orderNumber");
-- AddForeignKey
ALTER TABLE "components" ADD CONSTRAINT "components_componentTypeId_fkey" FOREIGN KEY ("componentTypeId") REFERENCES "component_types"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "orders" ADD CONSTRAINT "orders_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "order_items" ADD CONSTRAINT "order_items_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "orders"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "order_items" ADD CONSTRAINT "order_items_componentId_fkey" FOREIGN KEY ("componentId") REFERENCES "components"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,20 @@
-- CreateTable
CREATE TABLE "cart_items" (
"id" TEXT NOT NULL,
"quantity" INTEGER NOT NULL DEFAULT 1,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" TEXT NOT NULL,
"componentId" TEXT NOT NULL,
CONSTRAINT "cart_items_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "cart_items_userId_componentId_key" ON "cart_items"("userId", "componentId");
-- AddForeignKey
ALTER TABLE "cart_items" ADD CONSTRAINT "cart_items_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "cart_items" ADD CONSTRAINT "cart_items_componentId_fkey" FOREIGN KEY ("componentId") REFERENCES "components"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

116
prisma/schema.prisma Normal file
View File

@ -0,0 +1,116 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
username String @unique
password String
name String?
phone String?
address String?
isAdmin Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
orders Order[]
cartItems CartItem[]
@@map("users")
}
model ComponentType {
id String @id @default(cuid())
name String @unique // CPU、内存、硬盘、主板、显卡、机箱
description String?
components Component[]
@@map("component_types")
}
model Component {
id String @id @default(cuid())
name String
brand String
model String
price Float
description String?
imageUrl String?
stock Int @default(0)
specifications String? // JSON格式存储规格参数
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
componentTypeId String
componentType ComponentType @relation(fields: [componentTypeId], references: [id])
orderItems OrderItem[]
cartItems CartItem[]
@@map("components")
}
model Order {
id String @id @default(cuid())
orderNumber String @unique
totalAmount Float
status OrderStatus @default(PENDING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
user User @relation(fields: [userId], references: [id])
orderItems OrderItem[]
@@map("orders")
}
model OrderItem {
id String @id @default(cuid())
quantity Int @default(1)
price Float
orderId String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
componentId String
component Component @relation(fields: [componentId], references: [id])
@@map("order_items")
}
model CartItem {
id String @id @default(cuid())
quantity Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
componentId String
component Component @relation(fields: [componentId], references: [id], onDelete: Cascade)
@@unique([userId, componentId]) // 一个用户对同一个商品只能有一条购物车记录
@@map("cart_items")
}
enum OrderStatus {
PENDING
CONFIRMED
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}

261
seed.ts Normal file
View File

@ -0,0 +1,261 @@
import { PrismaClient } from '@prisma/client'
import { hashPassword } from './lib/auth'
const prisma = new PrismaClient()
async function main() {
// 创建管理员用户
const adminPassword = await hashPassword('admin123')
const admin = await prisma.user.upsert({
where: { email: 'admin@pcdiy.com' },
update: {},
create: {
email: 'admin@pcdiy.com',
username: 'admin',
password: adminPassword,
name: '系统管理员',
isAdmin: true,
},
})
// 创建配件类型
const componentTypes = [
{ name: 'CPU', description: '中央处理器' },
{ name: '内存', description: '内存条' },
{ name: '硬盘', description: '存储设备' },
{ name: '主板', description: '主板' },
{ name: '显卡', description: '显卡' },
{ name: '机箱', description: '机箱' },
]
for (const type of componentTypes) {
await prisma.componentType.upsert({
where: { name: type.name },
update: {},
create: type,
})
}
// 获取已创建的配件类型
const cpuType = await prisma.componentType.findUnique({ where: { name: 'CPU' } })
const memoryType = await prisma.componentType.findUnique({ where: { name: '内存' } })
const storageType = await prisma.componentType.findUnique({ where: { name: '硬盘' } })
const motherboardType = await prisma.componentType.findUnique({ where: { name: '主板' } })
const gpuType = await prisma.componentType.findUnique({ where: { name: '显卡' } })
const caseType = await prisma.componentType.findUnique({ where: { name: '机箱' } })
// 创建示例配件
const components = [
// CPU
{
name: 'Intel Core i5-13400F',
brand: 'Intel',
model: 'i5-13400F',
price: 1299,
description: '10核16线程基础频率2.5GHz最大睿频4.6GHz',
stock: 50,
componentTypeId: cpuType!.id,
specifications: JSON.stringify({
cores: 10,
threads: 16,
baseClock: '2.5GHz',
boostClock: '4.6GHz',
socket: 'LGA1700'
})
},
{
name: 'AMD Ryzen 5 7600X',
brand: 'AMD',
model: '7600X',
price: 1599,
description: '6核12线程基础频率4.7GHz最大睿频5.3GHz',
stock: 30,
componentTypeId: cpuType!.id,
specifications: JSON.stringify({
cores: 6,
threads: 12,
baseClock: '4.7GHz',
boostClock: '5.3GHz',
socket: 'AM5'
})
},
// 内存
{
name: 'Kingston FURY Beast DDR4 3200MHz 16GB',
brand: 'Kingston',
model: 'FURY Beast',
price: 299,
description: 'DDR4 3200MHz 16GB套装8GBx2',
stock: 100,
componentTypeId: memoryType!.id,
specifications: JSON.stringify({
type: 'DDR4',
speed: '3200MHz',
capacity: '16GB',
modules: 2
})
},
{
name: 'Corsair Vengeance LPX DDR4 3600MHz 32GB',
brand: 'Corsair',
model: 'Vengeance LPX',
price: 699,
description: 'DDR4 3600MHz 32GB套装16GBx2',
stock: 60,
componentTypeId: memoryType!.id,
specifications: JSON.stringify({
type: 'DDR4',
speed: '3600MHz',
capacity: '32GB',
modules: 2
})
},
// 硬盘
{
name: 'Samsung 980 PRO 1TB NVMe SSD',
brand: 'Samsung',
model: '980 PRO',
price: 599,
description: 'PCIe 4.0 NVMe M.2 SSD读取速度7000MB/s',
stock: 80,
componentTypeId: storageType!.id,
specifications: JSON.stringify({
type: 'NVMe SSD',
capacity: '1TB',
interface: 'PCIe 4.0',
readSpeed: '7000MB/s'
})
},
{
name: 'Western Digital Blue 2TB SATA HDD',
brand: 'Western Digital',
model: 'Blue',
price: 399,
description: '2TB 7200RPM SATA 机械硬盘',
stock: 120,
componentTypeId: storageType!.id,
specifications: JSON.stringify({
type: 'HDD',
capacity: '2TB',
interface: 'SATA',
speed: '7200RPM'
})
},
// 主板
{
name: 'ASUS PRIME B660M-A',
brand: 'ASUS',
model: 'PRIME B660M-A',
price: 699,
description: 'Intel B660芯片组支持LGA1700接口',
stock: 40,
componentTypeId: motherboardType!.id,
specifications: JSON.stringify({
chipset: 'B660',
socket: 'LGA1700',
formFactor: 'mATX',
memorySlots: 4
})
},
{
name: 'MSI MAG B550M MORTAR',
brand: 'MSI',
model: 'MAG B550M MORTAR',
price: 799,
description: 'AMD B550芯片组支持AM4接口',
stock: 35,
componentTypeId: motherboardType!.id,
specifications: JSON.stringify({
chipset: 'B550',
socket: 'AM4',
formFactor: 'mATX',
memorySlots: 4
})
},
// 显卡
{
name: 'NVIDIA GeForce RTX 4060 Ti',
brand: 'NVIDIA',
model: 'RTX 4060 Ti',
price: 3299,
description: '16GB GDDR6显存支持光追和DLSS 3',
stock: 25,
componentTypeId: gpuType!.id,
specifications: JSON.stringify({
gpu: 'RTX 4060 Ti',
memory: '16GB GDDR6',
memoryBus: '128-bit',
baseClock: '2310MHz'
})
},
{
name: 'AMD Radeon RX 7600 XT',
brand: 'AMD',
model: 'RX 7600 XT',
price: 2799,
description: '16GB GDDR6显存RDNA 3架构',
stock: 20,
componentTypeId: gpuType!.id,
specifications: JSON.stringify({
gpu: 'RX 7600 XT',
memory: '16GB GDDR6',
memoryBus: '128-bit',
baseClock: '2300MHz'
})
},
// 机箱
{
name: 'Fractal Design Core 1000',
brand: 'Fractal Design',
model: 'Core 1000',
price: 299,
description: '紧凑型mATX机箱静音设计',
stock: 70,
componentTypeId: caseType!.id,
specifications: JSON.stringify({
formFactor: 'mATX',
dimensions: '390 x 175 x 350mm',
material: '钢制',
fans: '1x120mm'
})
},
{
name: 'NZXT H510',
brand: 'NZXT',
model: 'H510',
price: 599,
description: '中塔ATX机箱钢化玻璃侧板',
stock: 50,
componentTypeId: caseType!.id,
specifications: JSON.stringify({
formFactor: 'ATX',
dimensions: '435 x 210 x 460mm',
material: '钢制+钢化玻璃',
fans: '2x120mm'
})
},
]
for (const component of components) {
await prisma.component.upsert({
where: {
id: 'temp-id-' + component.name.replace(/\s+/g, '-').toLowerCase()
},
update: {},
create: component,
})
}
console.log('数据初始化完成!')
console.log('管理员账号admin@pcdiy.com')
console.log('管理员密码admin123')
}
main()
.catch((e) => {
console.error(e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})