v1
This commit is contained in:
parent
142992a6c9
commit
7d139b22db
662
.dev-tools/data/Untitled-1.json
Normal file
662
.dev-tools/data/Untitled-1.json
Normal file
@ -0,0 +1,662 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "英特尔(INTEL)INTELE31225V3122612451246123112761275V312711285V3CPU二 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": "英特尔(Intel)i5-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代i7,192GB至高内存容量,旗舰超频,可兼容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": "酷睿Ultra9,36MB智能缓存,全新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": "英特尔(Intel)4代酷睿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": "英特尔(Intel)14代酷睿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": "英特尔(Intel)14代酷睿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": "英特尔(Intel)13代酷睿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": "英特尔(Intel)E5至强处理器服务器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": "英特尔(Intel)12代酷睿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": "英特尔(Intel)13代酷睿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": "英特尔(INTEL)i5-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.5GHz,192GB至高内存容量,全新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": "英特尔(Intel)4代酷睿 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": "英特尔(Intel)4代酷睿 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 V5(1151针)",
|
||||||
|
"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": "英特尔(INTEL)i5-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": "英特尔(Intel)i5-14490F 酷睿14代 处理器",
|
||||||
|
"brand": "英特尔(Intel)",
|
||||||
|
"model": "i5-14490F",
|
||||||
|
"price": 1096.8,
|
||||||
|
"description": "酷睿14代i5,192GB至高内存容量,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": "酷睿Ultra7,30MB智能缓存,全新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": "英特尔(Intel)7代酷睿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": "英特尔(Intel)PRO 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": "酷睿Ultra5,20MB智能缓存,全新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": "英特尔(Intel)4代酷睿 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": "英特尔(Intel)CPU处理器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系列)\"}"
|
||||||
|
}
|
||||||
|
]
|
||||||
332
.dev-tools/data/Untitled-2.json
Normal file
332
.dev-tools/data/Untitled-2.json
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "AMD 锐龙7 5700X处理器",
|
||||||
|
"brand": "AMD",
|
||||||
|
"model": "锐龙7 5700X",
|
||||||
|
"price": 797.4,
|
||||||
|
"description": "8核16线程,加速频率至高4.6GHz,65W 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": "7nm,6核12线程,加速频率至高4.2GHz,65W 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.2GHz,TDP 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": "7nm,6核12线程,加速频率至高4.4GHz,AM4盒装CPU,35MB游戏高速缓存。",
|
||||||
|
"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": "5nm,6核12线程,加速频率至高5GHz,AM5盒装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.3GHz,105W 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 锐龙 CPU,7nm 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\"}"
|
||||||
|
}
|
||||||
|
]
|
||||||
662
.dev-tools/data/Untitled-3.json
Normal file
662
.dev-tools/data/Untitled-3.json
Normal file
@ -0,0 +1,662 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "英伟达(NVIDIA)Tesla 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": "英伟达(NVIDIA)RTX4090系列 英伟达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": "盒装P400,2GB显存,适用于图形设计建模渲染,多屏炒股,工业包装",
|
||||||
|
"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": "英伟达(NVIDIA)Tesla 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": "英伟达(NVIDIA)RTX4090系列 英伟达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": "盈通(yeston)GeForce 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": "英伟达(NVIDIA)Tesla H100 80G",
|
||||||
|
"brand": "英伟达(NVIDIA)",
|
||||||
|
"model": "Tesla H100",
|
||||||
|
"price": 211999,
|
||||||
|
"description": "DeepSeeK部署显卡,人工智能深度学习高性能计算GPU,AI推理训练卡,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模型绘图服务器GPU,10G显存,不开发票",
|
||||||
|
"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": "英伟达(NVIDIA)Quadro 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": "英伟达(NVIDIA)RTX3080 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": "铭瑄(MAXSUN)GeForce 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": "英伟达(NVIDIA)RTX4060TI 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": "英伟达(NVIDIA)RTX 3080TI 12G 涡轮版",
|
||||||
|
"brand": "英伟达(NVIDIA)",
|
||||||
|
"model": "RTX 3080 Ti",
|
||||||
|
"price": 3709,
|
||||||
|
"description": "原厂公版双宽,适用于4卡/8卡服务器工作站GPU,12G显存,涡轮版,不开发票",
|
||||||
|
"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": "英伟达(NVIDIA)RTX 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": "英伟达(NVIDIA)RTX 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": "英伟达(NVIDIA)RTX 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": "盈通(yeston))GeForce 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": "英伟达(Nvidia)NVIDIA 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": "七彩虹(Colorful)RTX 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\":\"拆机精品,非矿卡\"}"
|
||||||
|
}
|
||||||
|
]
|
||||||
398
.dev-tools/data/Untitled-4.json
Normal file
398
.dev-tools/data/Untitled-4.json
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "雷克沙(Lexar) 雷神铠 DDR4台式机内存条 8G 3200 3600频率 马甲条 3200 皓月白 8GB 1条",
|
||||||
|
"brand": "雷克沙(Lexar)",
|
||||||
|
"model": "雷神铠",
|
||||||
|
"price": 199,
|
||||||
|
"description": "DDR4台式机内存条,8G,3200/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": "七彩虹(Colorful)DDR3L 8G 1600 笔记本内存条",
|
||||||
|
"brand": "七彩虹(Colorful)",
|
||||||
|
"model": "DDR3L",
|
||||||
|
"price": 44.91,
|
||||||
|
"description": "DDR3L笔记本内存条,8G,1600频率",
|
||||||
|
"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": "联想(Lenovo)16GB 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台式机电脑内存条,8GB,3200频率",
|
||||||
|
"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": "光威(Gloway)8GB 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": "七彩虹(Colorful)16GB 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": "金百达(KINGBANK)8GB 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": "金百达(KINGBANK)16GB 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": "金百达(KINGBANK)32GB(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": "金百达(KINGBANK)16GB(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": "威刚(ADATA)8GB 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": "联想(Lenovo)8GB 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": "金百达(KINGBANK)32GB(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": "阿斯加特(Asgard)32GB(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代英特尔cpu,1.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": "宏碁掠夺者(PREDATOR)48G(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": "七彩虹(Colorful)8GB 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 Wall)16GB 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": "光威(Gloway)8GB 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": "金百达(KINGBANK)32GB(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": "七彩虹(Colorful)16GB 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": "金百达(KINGBANK)32GB(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\":\"高效散热片,适配黑神话悟空\"}"
|
||||||
|
}
|
||||||
|
]
|
||||||
178
.dev-tools/data/Untitled-5.json
Normal file
178
.dev-tools/data/Untitled-5.json
Normal 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": "西部数据(WD)SSD固态硬盘 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": "西部数据(WD)SSD固态硬盘 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": "光威(Gloway)512GB 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英寸\"}"
|
||||||
|
}
|
||||||
|
]
|
||||||
519
.dev-tools/data/Untitled-6.json
Normal file
519
.dev-tools/data/Untitled-6.json
Normal 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": "航嘉(Huntkey)V320初恋 全景版海景房机箱",
|
||||||
|
"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": "瓦尔基里(VALKYRIE)VK03 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": "爱国者(aigo)A15 黑色 台式电脑主机箱",
|
||||||
|
"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": "爱国者(aigo)A15 黑色 台式电脑主机箱",
|
||||||
|
"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": "航嘉(Huntkey)V320初恋 全景版海景房机箱",
|
||||||
|
"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": "航嘉(Huntkey)S900沙尘暴全景版海景房机箱",
|
||||||
|
"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": "Thermaltake(Tt)钢影 透EX 海景房机箱",
|
||||||
|
"brand": "Thermaltake(Tt)",
|
||||||
|
"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": "爱国者(aigo)JV13黑色 电脑台式主机箱",
|
||||||
|
"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": "爱国者(aigo)A15 白色 台式电脑主机箱",
|
||||||
|
"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寸高清屏\"}"
|
||||||
|
}
|
||||||
|
]
|
||||||
475
.dev-tools/data/Untitled-7.json
Normal file
475
.dev-tools/data/Untitled-7.json
Normal 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/13400F(Intel 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": "华硕(ASUS)B760M-AYW WIFI D4 II 哎呦喂主板",
|
||||||
|
"brand": "华硕(ASUS)",
|
||||||
|
"model": "B760M-AYW WIFI D4 II 哎呦喂",
|
||||||
|
"price": 887.22,
|
||||||
|
"description": "支持 CPU 13600KF/13400F12600KF(Intel 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": "华硕(ASUS)TUF GAMING B760M-PLUS WIFI II重炮手二代 DDR5主板",
|
||||||
|
"brand": "华硕(ASUS)",
|
||||||
|
"model": "TUF GAMING B760M-PLUS WIFI II 重炮手二代",
|
||||||
|
"price": 1316.36,
|
||||||
|
"description": "支持 CPU 14600KF/14700KF(Intel 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": "华硕(ASUS)TX GAMING B760M WIFI 天选主板",
|
||||||
|
"brand": "华硕(ASUS)",
|
||||||
|
"model": "TX GAMING B760M WIFI 天选",
|
||||||
|
"price": 1296.4,
|
||||||
|
"description": "支持DDR5 CPU 13700K/13600KF/13400F(Intel 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": "微星(MSI)B760M 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": "微星(MSI)B650M 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": "微星(MSI)MAG 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": "七彩虹(Colorful)BATTLE-AX B760M-WHITE WIFI V20 DDR4 冷钢主板",
|
||||||
|
"brand": "七彩虹(Colorful)",
|
||||||
|
"model": "BATTLE-AX B760M-WHITE WIFI V20 冷钢",
|
||||||
|
"price": 727.54,
|
||||||
|
"description": "支持 12600KF/13490F/14600KF(Intel 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": "微星(MSI)MAG 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/13400F(Intel 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": "微星(MSI)A520M-A PRO DDR4电脑主板",
|
||||||
|
"brand": "微星(MSI)",
|
||||||
|
"model": "A520M-A PRO",
|
||||||
|
"price": 378.24,
|
||||||
|
"description": "支持CPU 5600/5600GT/5700X(AMD 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/14700K(Intel Z790/LGA 1700),扫码注册,一年换新,三年上门服务,白条6期免息,16+1+2供电模组,2.5G网卡&WIFI7,AI智能超频。",
|
||||||
|
"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套装英特尔i5,D4版本单主板无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": "七彩虹(Colorful)H610M-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套装英特尔i5,D4版本单主板无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": "七彩虹(Colorful)BATTLE-AX B760M-G WIFI V20A 小黑刃DDR4主板",
|
||||||
|
"brand": "七彩虹(Colorful)",
|
||||||
|
"model": "BATTLE-AX B760M-G WIFI V20A 小黑刃",
|
||||||
|
"price": 697.6,
|
||||||
|
"description": "支持14600K/14600KF(Intel 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": "七彩虹(Colorful)BATTLE-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": "技嘉(GIGABYTE)H610M 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/14600KF(Intel 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": "技嘉(GIGABYTE)B760M 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
34
.dev-tools/fetchDat.js
Normal 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
2
.gitignore
vendored
@ -39,3 +39,5 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
/app/generated/prisma
|
||||||
|
|||||||
727
app/admin/components/page.tsx
Normal file
727
app/admin/components/page.tsx
Normal 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
278
app/admin/orders/page.tsx
Normal 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
442
app/admin/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
102
app/api/admin/components/route.ts
Normal file
102
app/api/admin/components/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
47
app/api/admin/orders/[id]/route.ts
Normal file
47
app/api/admin/orders/[id]/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/api/admin/orders/route.ts
Normal file
38
app/api/admin/orders/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
123
app/api/admin/stats/route.ts
Normal file
123
app/api/admin/stats/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
59
app/api/auth/login/route.ts
Normal file
59
app/api/auth/login/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
70
app/api/auth/register/route.ts
Normal file
70
app/api/auth/register/route.ts
Normal 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
139
app/api/cart/[id]/route.ts
Normal 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
188
app/api/cart/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/api/component-types/route.ts
Normal file
27
app/api/component-types/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
90
app/api/components/[id]/route.ts
Normal file
90
app/api/components/[id]/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
215
app/api/components/batch/route.ts
Normal file
215
app/api/components/batch/route.ts
Normal 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
110
app/api/components/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
165
app/api/orders/[id]/route.ts
Normal file
165
app/api/orders/[id]/route.ts
Normal 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
155
app/api/orders/route.ts
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
61
app/api/user/change-password/route.ts
Normal file
61
app/api/user/change-password/route.ts
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
86
app/api/user/profile/route.ts
Normal file
86
app/api/user/profile/route.ts
Normal 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
170
app/api/user/stats/route.ts
Normal 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
456
app/build/page.tsx
Normal 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
393
app/cart/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
255
app/components/[id]/page.tsx
Normal file
255
app/components/[id]/page.tsx
Normal 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
294
app/components/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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
130
app/login/page.tsx
Normal 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.com,密码:admin123
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
398
app/orders/[id]/page.tsx
Normal file
398
app/orders/[id]/page.tsx
Normal 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
334
app/orders/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
217
app/page.tsx
217
app/page.tsx
@ -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
566
app/profile/page.tsx
Normal 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
215
app/register/page.tsx
Normal 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
179
bun.lock
@ -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=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
103
components/AddToCartButton.tsx
Normal file
103
components/AddToCartButton.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
150
components/ComponentCard.tsx
Normal file
150
components/ComponentCard.tsx
Normal 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
251
components/Navbar.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
55
components/admin/AdminAuth.tsx
Normal file
55
components/admin/AdminAuth.tsx
Normal 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
28
lib/admin-auth.ts
Normal 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
24
lib/auth.ts
Normal 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
9
lib/prisma.ts
Normal 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
|
||||||
@ -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;
|
||||||
|
|||||||
18
package.json
18
package.json
@ -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",
|
||||||
|
|||||||
93
prisma/migrations/20250621121352_init/migration.sql
Normal file
93
prisma/migrations/20250621121352_init/migration.sql
Normal 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;
|
||||||
@ -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;
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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
116
prisma/schema.prisma
Normal 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
261
seed.ts
Normal 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()
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user