feat: switch to table layout and add image URL field
This commit is contained in:
+238
-310
@@ -15,7 +15,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 960px;
|
max-width: 1100px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
@@ -56,29 +56,13 @@ body {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary { background: #1677ff; color: #fff; }
|
||||||
background: #1677ff;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.btn-primary:hover { background: #4096ff; }
|
.btn-primary:hover { background: #4096ff; }
|
||||||
|
.btn-default { background: #fff; color: #333; border: 1px solid #d9d9d9; }
|
||||||
.btn-default {
|
|
||||||
background: #fff;
|
|
||||||
color: #333;
|
|
||||||
border: 1px solid #d9d9d9;
|
|
||||||
}
|
|
||||||
.btn-default:hover { border-color: #1677ff; color: #1677ff; }
|
.btn-default:hover { border-color: #1677ff; color: #1677ff; }
|
||||||
|
.btn-danger { background: #ff4d4f; color: #fff; }
|
||||||
.btn-danger {
|
|
||||||
background: #ff4d4f;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.btn-danger:hover { background: #ff7875; }
|
.btn-danger:hover { background: #ff7875; }
|
||||||
|
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
||||||
.btn-sm {
|
|
||||||
padding: 4px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mode-toggle {
|
.mode-toggle {
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
@@ -104,48 +88,140 @@ body {
|
|||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Component Cards */
|
/* Table */
|
||||||
.component-list {
|
.config-table-wrap {
|
||||||
display: flex;
|
background: #fff;
|
||||||
flex-direction: column;
|
border-radius: 12px;
|
||||||
gap: 12px;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
overflow: hidden;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-card {
|
.config-table {
|
||||||
background: #fff;
|
width: 100%;
|
||||||
border-radius: 12px;
|
border-collapse: collapse;
|
||||||
padding: 20px 24px;
|
font-size: 14px;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
||||||
transition: box-shadow 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-card:hover {
|
.config-table thead {
|
||||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.config-table th {
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
font-size: 13px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-table td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-table tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-table tbody tr:hover {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Image cell */
|
||||||
|
.cell-img {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-img-placeholder {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #eee;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
margin-bottom: 12px;
|
color: #ccc;
|
||||||
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-type {
|
/* Type badge */
|
||||||
|
.type-badge {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1677ff;
|
color: #1677ff;
|
||||||
background: #e6f4ff;
|
background: #e6f4ff;
|
||||||
padding: 4px 12px;
|
padding: 4px 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-actions {
|
/* Price cell */
|
||||||
|
.price-cell {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ff4d4f;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-cell .currency {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link cell */
|
||||||
|
.link-cell a {
|
||||||
|
color: #1677ff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.link-cell a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
|
/* Alt selector */
|
||||||
|
.alt-selector {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alt-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alt-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-actions button {
|
.alt-pill {
|
||||||
|
padding: 3px 8px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #fff;
|
||||||
|
color: #666;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alt-pill:hover { border-color: #1677ff; color: #1677ff; }
|
||||||
|
.alt-pill.active { border-color: #1677ff; background: #e6f4ff; color: #1677ff; font-weight: 500; }
|
||||||
|
|
||||||
|
/* Action cells */
|
||||||
|
.action-cell {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-cell button {
|
||||||
background: none;
|
background: none;
|
||||||
border: 1px solid #e8e8e8;
|
border: 1px solid #e8e8e8;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -154,107 +230,17 @@ body {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #666;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-actions button:hover {
|
.action-cell button:first-child { margin-left: 0; }
|
||||||
border-color: #1677ff;
|
.action-cell button:hover { border-color: #1677ff; color: #1677ff; }
|
||||||
color: #1677ff;
|
.action-cell button.delete-btn:hover { border-color: #ff4d4f; color: #ff4d4f; }
|
||||||
}
|
|
||||||
|
|
||||||
.card-actions button.delete-btn:hover {
|
/* Add button row */
|
||||||
border-color: #ff4d4f;
|
.add-row {
|
||||||
color: #ff4d4f;
|
padding: 16px;
|
||||||
}
|
text-align: center;
|
||||||
|
|
||||||
.card-body {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
gap: 8px 20px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
min-width: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value a {
|
|
||||||
color: #1677ff;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.info-value a:hover { text-decoration: underline; }
|
|
||||||
|
|
||||||
.card-price {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ff4d4f;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-price .currency {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Alternative Selector */
|
|
||||||
.alt-selector {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
margin-top: 8px;
|
|
||||||
padding-top: 12px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-options {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-pill {
|
|
||||||
padding: 5px 12px;
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: #fff;
|
|
||||||
color: #666;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-pill:hover {
|
|
||||||
border-color: #1677ff;
|
|
||||||
color: #1677ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-pill.active {
|
|
||||||
border-color: #1677ff;
|
|
||||||
background: #e6f4ff;
|
|
||||||
color: #1677ff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer / Total */
|
/* Footer / Total */
|
||||||
@@ -270,17 +256,14 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-inner {
|
.footer-inner {
|
||||||
max-width: 960px;
|
max-width: 1100px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-label {
|
.footer-label { font-size: 14px; color: #666; }
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-total {
|
.footer-total {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
@@ -288,9 +271,7 @@ body {
|
|||||||
color: #ff4d4f;
|
color: #ff4d4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-total .currency {
|
.footer-total .currency { font-size: 16px; }
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal */
|
/* Modal */
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
@@ -303,14 +284,12 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-overlay.active {
|
.modal-overlay.active { display: flex; }
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
width: 560px;
|
width: 600px;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
max-height: 85vh;
|
max-height: 85vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -324,10 +303,7 @@ body {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header h2 {
|
.modal-header h2 { font-size: 18px; font-weight: 600; }
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close {
|
.modal-close {
|
||||||
background: none;
|
background: none;
|
||||||
@@ -339,13 +315,9 @@ body {
|
|||||||
}
|
}
|
||||||
.modal-close:hover { color: #333; }
|
.modal-close:hover { color: #333; }
|
||||||
|
|
||||||
.modal-body {
|
.modal-body { padding: 20px 24px; }
|
||||||
padding: 20px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
.form-group { margin-bottom: 16px; }
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
.form-group label {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -356,8 +328,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-group input,
|
.form-group input,
|
||||||
.form-group select,
|
.form-group select {
|
||||||
.form-group textarea {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border: 1px solid #d9d9d9;
|
border: 1px solid #d9d9d9;
|
||||||
@@ -368,8 +339,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-group input:focus,
|
.form-group input:focus,
|
||||||
.form-group select:focus,
|
.form-group select:focus {
|
||||||
.form-group textarea:focus {
|
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #1677ff;
|
border-color: #1677ff;
|
||||||
box-shadow: 0 0 0 2px rgba(22,119,255,0.1);
|
box-shadow: 0 0 0 2px rgba(22,119,255,0.1);
|
||||||
@@ -395,17 +365,13 @@ body {
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alt-section-header h3 {
|
.alt-section-header h3 { font-size: 14px; font-weight: 600; }
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-item {
|
.alt-item {
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.alt-item-header {
|
.alt-item-header {
|
||||||
@@ -415,11 +381,7 @@ body {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alt-item-label {
|
.alt-item-label { font-size: 12px; font-weight: 600; color: #1677ff; }
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1677ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-item-remove {
|
.alt-item-remove {
|
||||||
background: none;
|
background: none;
|
||||||
@@ -444,10 +406,7 @@ body {
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state p {
|
.empty-state p { margin-bottom: 16px; font-size: 15px; }
|
||||||
margin-bottom: 16px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Import Overlay */
|
/* Import Overlay */
|
||||||
.import-overlay {
|
.import-overlay {
|
||||||
@@ -460,9 +419,7 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.import-overlay.active {
|
.import-overlay.active { display: flex; }
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.import-box {
|
.import-box {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -472,18 +429,14 @@ body {
|
|||||||
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.import-box p {
|
.import-box p { margin-bottom: 16px; color: #666; }
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 768px) {
|
||||||
.container { padding: 12px; }
|
.container { padding: 12px; }
|
||||||
.header { padding: 16px; }
|
.header { padding: 16px; }
|
||||||
.component-card { padding: 16px; }
|
.config-table-wrap { overflow-x: auto; }
|
||||||
.card-body { grid-template-columns: 1fr; }
|
.config-table { min-width: 700px; }
|
||||||
.card-price { justify-self: start; }
|
|
||||||
.form-row { grid-template-columns: 1fr; }
|
.form-row { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -491,7 +444,6 @@ body {
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- Header -->
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1 id="configName">PC装机配置表</h1>
|
<h1 id="configName">PC装机配置表</h1>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
@@ -504,12 +456,11 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Component List -->
|
<div id="contentArea"></div>
|
||||||
<div class="component-list" id="componentList"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer Total -->
|
<!-- Footer Total -->
|
||||||
<div class="footer" id="footer">
|
<div class="footer">
|
||||||
<div class="footer-inner">
|
<div class="footer-inner">
|
||||||
<span class="footer-label">总计</span>
|
<span class="footer-label">总计</span>
|
||||||
<span class="footer-total"><span class="currency">¥</span><span id="totalPrice">0</span></span>
|
<span class="footer-total"><span class="currency">¥</span><span id="totalPrice">0</span></span>
|
||||||
@@ -525,7 +476,6 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input type="hidden" id="editComponentIndex" value="-1">
|
<input type="hidden" id="editComponentIndex" value="-1">
|
||||||
<input type="hidden" id="editAltIndex" value="-1">
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>部件类型</label>
|
<label>部件类型</label>
|
||||||
@@ -545,6 +495,11 @@ body {
|
|||||||
<input type="text" id="formTypeCustom" style="display:none;margin-top:6px" placeholder="输入自定义类型名称">
|
<input type="text" id="formTypeCustom" style="display:none;margin-top:6px" placeholder="输入自定义类型名称">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>图片链接</label>
|
||||||
|
<input type="url" id="formImage" placeholder="https://example.com/image.jpg">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>品牌</label>
|
<label>品牌</label>
|
||||||
@@ -619,34 +574,21 @@ body {
|
|||||||
// ========== State ==========
|
// ========== State ==========
|
||||||
let config = { configName: 'PC装机配置表', components: [] };
|
let config = { configName: 'PC装机配置表', components: [] };
|
||||||
let currentMode = 'view';
|
let currentMode = 'view';
|
||||||
let tempAlts = []; // temporary alt items in modal
|
let tempAlts = [];
|
||||||
|
|
||||||
// ========== Preset Types ==========
|
|
||||||
const PRESET_TYPES = ['CPU', '主板', '显卡', '内存', '硬盘', '电源', '机箱', '散热器', '显示器'];
|
const PRESET_TYPES = ['CPU', '主板', '显卡', '内存', '硬盘', '电源', '机箱', '散热器', '显示器'];
|
||||||
|
|
||||||
// ========== Init ==========
|
// ========== Init ==========
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', loadFromURL);
|
||||||
loadFromURL();
|
|
||||||
});
|
|
||||||
|
|
||||||
function loadFromURL() {
|
function loadFromURL() {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const configURL = params.get('config');
|
const configURL = params.get('config');
|
||||||
if (configURL) {
|
if (configURL) {
|
||||||
fetch(configURL)
|
fetch(configURL)
|
||||||
.then(r => {
|
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
.then(data => { config = data; document.getElementById('configName').textContent = config.configName || 'PC装机配置表'; render(); })
|
||||||
return r.json();
|
.catch(err => { console.error('加载配置失败:', err); render(); });
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
config = data;
|
|
||||||
document.getElementById('configName').textContent = config.configName || 'PC装机配置表';
|
|
||||||
render();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error('加载配置失败:', err);
|
|
||||||
render();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
@@ -663,10 +605,10 @@ function setMode(mode) {
|
|||||||
|
|
||||||
// ========== Render ==========
|
// ========== Render ==========
|
||||||
function render() {
|
function render() {
|
||||||
const list = document.getElementById('componentList');
|
const area = document.getElementById('contentArea');
|
||||||
|
|
||||||
if (!config.components || config.components.length === 0) {
|
if (!config.components || config.components.length === 0) {
|
||||||
list.innerHTML = `
|
area.innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>暂无配置数据</p>
|
<p>暂无配置数据</p>
|
||||||
<button class="btn btn-primary" onclick="loadSample()">加载示例配置</button>
|
<button class="btn btn-primary" onclick="loadSample()">加载示例配置</button>
|
||||||
@@ -676,62 +618,79 @@ function render() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.innerHTML = config.components.map((comp, i) => renderCard(comp, i)).join('');
|
const editHeaders = currentMode === 'edit' ? '<th style="width:100px">操作</th>' : '';
|
||||||
updateTotal();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderCard(comp, index) {
|
let rows = config.components.map((comp, i) => {
|
||||||
const selectedIdx = comp.selected || 0;
|
const selectedIdx = comp.selected || 0;
|
||||||
const opt = comp.options[selectedIdx];
|
const opt = comp.options[selectedIdx];
|
||||||
if (!opt) return '';
|
if (!opt) return '';
|
||||||
|
|
||||||
const editActions = currentMode === 'edit' ? `
|
const imgCell = opt.image
|
||||||
<div class="card-actions">
|
? `<img class="cell-img" src="${opt.image}" alt="${opt.model}" onerror="this.outerHTML='<div class=\\'cell-img-placeholder\\'> </div>'">`
|
||||||
<button onclick="editComponent(${index})">编辑</button>
|
: '<div class="cell-img-placeholder"> </div>';
|
||||||
<button class="delete-btn" onclick="confirmDelete(${index})">删除</button>
|
|
||||||
</div>` : '';
|
|
||||||
|
|
||||||
const altSelector = comp.options.length > 1 ? `
|
const linkCell = opt.link
|
||||||
|
? `<a href="${opt.link}" target="_blank" rel="noopener">查看商品</a>`
|
||||||
|
: '-';
|
||||||
|
|
||||||
|
const altCell = comp.options.length > 1 ? `
|
||||||
<div class="alt-selector">
|
<div class="alt-selector">
|
||||||
<div class="alt-label">备选方案(${comp.options.length}个可选)</div>
|
<div class="alt-label">备选 (${comp.options.length})</div>
|
||||||
<div class="alt-options">
|
<div class="alt-options">
|
||||||
${comp.options.map((o, j) => `
|
${comp.options.map((o, j) => `
|
||||||
<button class="alt-pill ${j === selectedIdx ? 'active' : ''}" onclick="selectAlt(${index}, ${j})">
|
<button class="alt-pill ${j === selectedIdx ? 'active' : ''}" onclick="selectAlt(${i}, ${j})">
|
||||||
${o.brand} ${o.model} ¥${o.price}
|
${o.brand} ${o.model} ¥${o.price}
|
||||||
</button>
|
</button>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
</div>` : '';
|
</div>` : '';
|
||||||
|
|
||||||
|
const actionCell = currentMode === 'edit' ? `
|
||||||
|
<td class="action-cell">
|
||||||
|
<button onclick="editComponent(${i})">编辑</button>
|
||||||
|
<button class="delete-btn" onclick="confirmDelete(${i})">删除</button>
|
||||||
|
</td>` : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="component-card">
|
<tr>
|
||||||
<div class="card-header">
|
<td>${imgCell}</td>
|
||||||
<span class="component-type">${comp.type}</span>
|
<td><span class="type-badge">${comp.type}</span></td>
|
||||||
${editActions}
|
<td>${opt.brand || '-'}</td>
|
||||||
</div>
|
<td>${opt.model || '-'}</td>
|
||||||
<div class="card-body">
|
<td>${opt.specs || '-'}</td>
|
||||||
<div class="card-info">
|
<td class="price-cell"><span class="currency">¥</span>${(opt.price || 0).toLocaleString()}</td>
|
||||||
<div class="info-row">
|
<td class="link-cell">${linkCell}</td>
|
||||||
<span class="info-label">品牌</span>
|
<td>${altCell}</td>
|
||||||
<span class="info-value">${opt.brand || '-'}</span>
|
${actionCell}
|
||||||
</div>
|
</tr>`;
|
||||||
<div class="info-row">
|
}).join('');
|
||||||
<span class="info-label">型号</span>
|
|
||||||
<span class="info-value">${opt.model || '-'}</span>
|
const addRow = currentMode === 'edit'
|
||||||
</div>
|
? `<div class="add-row"><button class="btn btn-primary" onclick="addComponent()">+ 添加部件</button></div>`
|
||||||
<div class="info-row">
|
: '';
|
||||||
<span class="info-label">参数</span>
|
|
||||||
<span class="info-value">${opt.specs || '-'}</span>
|
area.innerHTML = `
|
||||||
</div>
|
<div class="config-table-wrap">
|
||||||
<div class="info-row">
|
<table class="config-table">
|
||||||
<span class="info-label">链接</span>
|
<thead>
|
||||||
<span class="info-value">${opt.link ? `<a href="${opt.link}" target="_blank" rel="noopener">查看商品</a>` : '-'}</span>
|
<tr>
|
||||||
</div>
|
<th style="width:80px">图片</th>
|
||||||
</div>
|
<th style="width:80px">类型</th>
|
||||||
<div class="card-price"><span class="currency">¥</span>${opt.price || 0}</div>
|
<th style="width:80px">品牌</th>
|
||||||
${altSelector}
|
<th style="width:120px">型号</th>
|
||||||
</div>
|
<th>参数</th>
|
||||||
|
<th style="width:100px">价格</th>
|
||||||
|
<th style="width:80px">链接</th>
|
||||||
|
<th style="width:200px">备选</th>
|
||||||
|
${editHeaders}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>${rows}</tbody>
|
||||||
|
</table>
|
||||||
|
${addRow}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
updateTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Total Price ==========
|
// ========== Total Price ==========
|
||||||
@@ -762,12 +721,10 @@ function closeImport() {
|
|||||||
function handleFileImport(event) {
|
function handleFileImport(event) {
|
||||||
const file = event.target.files[0];
|
const file = event.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(e.target.result);
|
config = JSON.parse(e.target.result);
|
||||||
config = data;
|
|
||||||
document.getElementById('configName').textContent = config.configName || 'PC装机配置表';
|
document.getElementById('configName').textContent = config.configName || 'PC装机配置表';
|
||||||
closeImport();
|
closeImport();
|
||||||
render();
|
render();
|
||||||
@@ -781,11 +738,7 @@ function handleFileImport(event) {
|
|||||||
function loadSample() {
|
function loadSample() {
|
||||||
fetch('sample_config.json')
|
fetch('sample_config.json')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => { config = data; document.getElementById('configName').textContent = config.configName || 'PC装机配置表'; render(); })
|
||||||
config = data;
|
|
||||||
document.getElementById('configName').textContent = config.configName || 'PC装机配置表';
|
|
||||||
render();
|
|
||||||
})
|
|
||||||
.catch(err => alert('加载示例失败: ' + err.message));
|
.catch(err => alert('加载示例失败: ' + err.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,7 +754,7 @@ function exportJSON() {
|
|||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Edit Modal ==========
|
// ========== Modal ==========
|
||||||
function openModal(title) {
|
function openModal(title) {
|
||||||
document.getElementById('modalTitle').textContent = title;
|
document.getElementById('modalTitle').textContent = title;
|
||||||
document.getElementById('editModal').classList.add('active');
|
document.getElementById('editModal').classList.add('active');
|
||||||
@@ -814,16 +767,12 @@ function closeModal() {
|
|||||||
|
|
||||||
function onTypeChange() {
|
function onTypeChange() {
|
||||||
const sel = document.getElementById('formType');
|
const sel = document.getElementById('formType');
|
||||||
const custom = document.getElementById('formTypeCustom');
|
document.getElementById('formTypeCustom').style.display = sel.value === '__custom__' ? '' : 'none';
|
||||||
custom.style.display = sel.value === '__custom__' ? '' : 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFormType() {
|
function getFormType() {
|
||||||
const sel = document.getElementById('formType');
|
const sel = document.getElementById('formType');
|
||||||
if (sel.value === '__custom__') {
|
return sel.value === '__custom__' ? document.getElementById('formTypeCustom').value.trim() : sel.value;
|
||||||
return document.getElementById('formTypeCustom').value.trim();
|
|
||||||
}
|
|
||||||
return sel.value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFormType(type) {
|
function setFormType(type) {
|
||||||
@@ -841,6 +790,7 @@ function setFormType(type) {
|
|||||||
|
|
||||||
function getFormData() {
|
function getFormData() {
|
||||||
return {
|
return {
|
||||||
|
image: document.getElementById('formImage').value.trim(),
|
||||||
brand: document.getElementById('formBrand').value.trim(),
|
brand: document.getElementById('formBrand').value.trim(),
|
||||||
model: document.getElementById('formModel').value.trim(),
|
model: document.getElementById('formModel').value.trim(),
|
||||||
specs: document.getElementById('formSpecs').value.trim(),
|
specs: document.getElementById('formSpecs').value.trim(),
|
||||||
@@ -850,6 +800,7 @@ function getFormData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setFormData(opt) {
|
function setFormData(opt) {
|
||||||
|
document.getElementById('formImage').value = opt.image || '';
|
||||||
document.getElementById('formBrand').value = opt.brand || '';
|
document.getElementById('formBrand').value = opt.brand || '';
|
||||||
document.getElementById('formModel').value = opt.model || '';
|
document.getElementById('formModel').value = opt.model || '';
|
||||||
document.getElementById('formSpecs').value = opt.specs || '';
|
document.getElementById('formSpecs').value = opt.specs || '';
|
||||||
@@ -861,16 +812,14 @@ function clearForm() {
|
|||||||
document.getElementById('formType').value = '';
|
document.getElementById('formType').value = '';
|
||||||
document.getElementById('formTypeCustom').value = '';
|
document.getElementById('formTypeCustom').value = '';
|
||||||
document.getElementById('formTypeCustom').style.display = 'none';
|
document.getElementById('formTypeCustom').style.display = 'none';
|
||||||
document.getElementById('formBrand').value = '';
|
['formImage', 'formBrand', 'formModel', 'formSpecs', 'formPrice', 'formLink'].forEach(id => {
|
||||||
document.getElementById('formModel').value = '';
|
document.getElementById(id).value = '';
|
||||||
document.getElementById('formSpecs').value = '';
|
});
|
||||||
document.getElementById('formPrice').value = '';
|
|
||||||
document.getElementById('formLink').value = '';
|
|
||||||
tempAlts = [];
|
tempAlts = [];
|
||||||
renderAltList();
|
renderAltList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Add Component ==========
|
// ========== Add / Edit Component ==========
|
||||||
function addComponent() {
|
function addComponent() {
|
||||||
clearForm();
|
clearForm();
|
||||||
document.getElementById('editComponentIndex').value = -1;
|
document.getElementById('editComponentIndex').value = -1;
|
||||||
@@ -878,28 +827,20 @@ function addComponent() {
|
|||||||
openModal('添加部件');
|
openModal('添加部件');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Edit Component ==========
|
|
||||||
function editComponent(index) {
|
function editComponent(index) {
|
||||||
const comp = config.components[index];
|
const comp = config.components[index];
|
||||||
document.getElementById('editComponentIndex').value = index;
|
document.getElementById('editComponentIndex').value = index;
|
||||||
|
|
||||||
setFormType(comp.type);
|
setFormType(comp.type);
|
||||||
|
setFormData(comp.options[0] || {});
|
||||||
// Load first option as main
|
|
||||||
const opt = comp.options[0] || {};
|
|
||||||
setFormData(opt);
|
|
||||||
|
|
||||||
// Load alternatives (skip first)
|
|
||||||
tempAlts = comp.options.slice(1).map(o => ({...o}));
|
tempAlts = comp.options.slice(1).map(o => ({...o}));
|
||||||
document.getElementById('altSection').style.display = tempAlts.length > 0 || comp.options.length > 0 ? '' : 'none';
|
document.getElementById('altSection').style.display = tempAlts.length > 0 || comp.options.length > 0 ? '' : 'none';
|
||||||
renderAltList();
|
renderAltList();
|
||||||
|
|
||||||
openModal('编辑部件');
|
openModal('编辑部件');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Alternatives in Modal ==========
|
// ========== Alternatives in Modal ==========
|
||||||
function addAltItem() {
|
function addAltItem() {
|
||||||
tempAlts.push({ brand: '', model: '', specs: '', price: 0, link: '' });
|
tempAlts.push({ image: '', brand: '', model: '', specs: '', price: 0, link: '' });
|
||||||
renderAltList();
|
renderAltList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,13 +855,16 @@ function renderAltList() {
|
|||||||
container.innerHTML = '<p style="font-size:12px;color:#999">暂无备选配置,点击上方按钮添加</p>';
|
container.innerHTML = '<p style="font-size:12px;color:#999">暂无备选配置,点击上方按钮添加</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.innerHTML = tempAlts.map((alt, i) => `
|
container.innerHTML = tempAlts.map((alt, i) => `
|
||||||
<div class="alt-item">
|
<div class="alt-item">
|
||||||
<div class="alt-item-header">
|
<div class="alt-item-header">
|
||||||
<span class="alt-item-label">备选 ${i + 1}</span>
|
<span class="alt-item-label">备选 ${i + 1}</span>
|
||||||
<button class="alt-item-remove" onclick="removeAltItem(${i})">删除</button>
|
<button class="alt-item-remove" onclick="removeAltItem(${i})">删除</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>图片链接</label>
|
||||||
|
<input type="url" value="${alt.image || ''}" onchange="tempAlts[${i}].image=this.value" placeholder="https://example.com/image.jpg">
|
||||||
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>品牌</label>
|
<label>品牌</label>
|
||||||
@@ -952,39 +896,27 @@ function renderAltList() {
|
|||||||
// ========== Save Component ==========
|
// ========== Save Component ==========
|
||||||
function saveComponent() {
|
function saveComponent() {
|
||||||
const type = getFormType();
|
const type = getFormType();
|
||||||
if (!type) {
|
if (!type) { alert('请选择或输入部件类型'); return; }
|
||||||
alert('请选择或输入部件类型');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainOpt = getFormData();
|
const mainOpt = getFormData();
|
||||||
const options = [mainOpt, ...tempAlts];
|
const options = [mainOpt, ...tempAlts];
|
||||||
const index = parseInt(document.getElementById('editComponentIndex').value);
|
const index = parseInt(document.getElementById('editComponentIndex').value);
|
||||||
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
// Edit existing
|
|
||||||
config.components[index].type = type;
|
config.components[index].type = type;
|
||||||
config.components[index].options = options;
|
config.components[index].options = options;
|
||||||
if (config.components[index].selected >= options.length) {
|
if (config.components[index].selected >= options.length) config.components[index].selected = 0;
|
||||||
config.components[index].selected = 0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Add new
|
config.components.push({ type, selected: 0, options });
|
||||||
config.components.push({
|
|
||||||
type: type,
|
|
||||||
selected: 0,
|
|
||||||
options: options
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Delete Component ==========
|
// ========== Delete ==========
|
||||||
function confirmDelete(index) {
|
function confirmDelete(index) {
|
||||||
document.getElementById('confirmMessage').textContent =
|
document.getElementById('confirmMessage').textContent = `确定要删除「${config.components[index].type}」吗?`;
|
||||||
`确定要删除「${config.components[index].type}」吗?`;
|
|
||||||
document.getElementById('confirmDeleteBtn').onclick = () => {
|
document.getElementById('confirmDeleteBtn').onclick = () => {
|
||||||
config.components.splice(index, 1);
|
config.components.splice(index, 1);
|
||||||
closeConfirm();
|
closeConfirm();
|
||||||
@@ -997,13 +929,9 @@ function closeConfirm() {
|
|||||||
document.getElementById('confirmModal').classList.remove('active');
|
document.getElementById('confirmModal').classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Keyboard Shortcuts ==========
|
// ========== Keyboard ==========
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') { closeModal(); closeImport(); closeConfirm(); }
|
||||||
closeModal();
|
|
||||||
closeImport();
|
|
||||||
closeConfirm();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -5,62 +5,62 @@
|
|||||||
"type": "CPU",
|
"type": "CPU",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "Intel", "model": "i7-14700K", "specs": "20核28线程 / 3.4GHz", "price": 2499, "link": "https://item.jd.com/100060726782.html" },
|
{ "image": "", "brand": "Intel", "model": "i7-14700K", "specs": "20核28线程 / 3.4GHz", "price": 2499, "link": "https://item.jd.com/100060726782.html" },
|
||||||
{ "brand": "AMD", "model": "R7 7800X3D", "specs": "8核16线程 / 4.2GHz", "price": 2199, "link": "https://item.jd.com/100057602578.html" }
|
{ "image": "", "brand": "AMD", "model": "R7 7800X3D", "specs": "8核16线程 / 4.2GHz", "price": 2199, "link": "https://item.jd.com/100057602578.html" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "主板",
|
"type": "主板",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "华硕", "model": "ROG STRIX Z790-E", "specs": "ATX / DDR5 / WiFi", "price": 2899, "link": "https://item.jd.com/100056938678.html" },
|
{ "image": "", "brand": "华硕", "model": "ROG STRIX Z790-E", "specs": "ATX / DDR5 / WiFi", "price": 2899, "link": "https://item.jd.com/100056938678.html" },
|
||||||
{ "brand": "微星", "model": "MPG Z790 EDGE", "specs": "ATX / DDR5 / WiFi", "price": 1999, "link": "https://item.jd.com/100058365432.html" }
|
{ "image": "", "brand": "微星", "model": "MPG Z790 EDGE", "specs": "ATX / DDR5 / WiFi", "price": 1999, "link": "https://item.jd.com/100058365432.html" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "显卡",
|
"type": "显卡",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "NVIDIA", "model": "RTX 4070 Ti Super", "specs": "16GB GDDR6X", "price": 6499, "link": "https://item.jd.com/100080746782.html" },
|
{ "image": "", "brand": "NVIDIA", "model": "RTX 4070 Ti Super", "specs": "16GB GDDR6X", "price": 6499, "link": "https://item.jd.com/100080746782.html" },
|
||||||
{ "brand": "AMD", "model": "RX 7900 XT", "specs": "20GB GDDR6", "price": 5299, "link": "https://item.jd.com/100060987654.html" }
|
{ "image": "", "brand": "AMD", "model": "RX 7900 XT", "specs": "20GB GDDR6", "price": 5299, "link": "https://item.jd.com/100060987654.html" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "内存",
|
"type": "内存",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "金士顿", "model": "FURY Beast DDR5", "specs": "32GB(16G×2) / 6000MHz", "price": 699, "link": "https://item.jd.com/100041234567.html" },
|
{ "image": "", "brand": "金士顿", "model": "FURY Beast DDR5", "specs": "32GB(16G×2) / 6000MHz", "price": 699, "link": "https://item.jd.com/100041234567.html" },
|
||||||
{ "brand": "芝奇", "model": "Trident Z5 DDR5", "specs": "32GB(16G×2) / 6400MHz", "price": 899, "link": "https://item.jd.com/100039876543.html" }
|
{ "image": "", "brand": "芝奇", "model": "Trident Z5 DDR5", "specs": "32GB(16G×2) / 6400MHz", "price": 899, "link": "https://item.jd.com/100039876543.html" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "硬盘",
|
"type": "硬盘",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "三星", "model": "990 PRO", "specs": "1TB / NVMe PCIe 4.0", "price": 699, "link": "https://item.jd.com/100038765432.html" },
|
{ "image": "", "brand": "三星", "model": "990 PRO", "specs": "1TB / NVMe PCIe 4.0", "price": 699, "link": "https://item.jd.com/100038765432.html" },
|
||||||
{ "brand": "西部数据", "model": "SN850X", "specs": "1TB / NVMe PCIe 4.0", "price": 599, "link": "https://item.jd.com/100037654321.html" }
|
{ "image": "", "brand": "西部数据", "model": "SN850X", "specs": "1TB / NVMe PCIe 4.0", "price": 599, "link": "https://item.jd.com/100037654321.html" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "电源",
|
"type": "电源",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "海韵", "model": "FOCUS GX-850", "specs": "850W / 80Plus金牌 / 全模组", "price": 899, "link": "https://item.jd.com/100036543210.html" }
|
{ "image": "", "brand": "海韵", "model": "FOCUS GX-850", "specs": "850W / 80Plus金牌 / 全模组", "price": 899, "link": "https://item.jd.com/100036543210.html" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "机箱",
|
"type": "机箱",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "联力", "model": "O11 Dynamic EVO", "specs": "中塔 / 玻璃侧透", "price": 999, "link": "https://item.jd.com/100035432109.html" }
|
{ "image": "", "brand": "联力", "model": "O11 Dynamic EVO", "specs": "中塔 / 玻璃侧透", "price": 999, "link": "https://item.jd.com/100035432109.html" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "散热器",
|
"type": "散热器",
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{ "brand": "利民", "model": "Frozen Magic 360", "specs": "360mm一体式水冷", "price": 499, "link": "https://item.jd.com/100034321098.html" },
|
{ "image": "", "brand": "利民", "model": "Frozen Magic 360", "specs": "360mm一体式水冷", "price": 499, "link": "https://item.jd.com/100034321098.html" },
|
||||||
{ "brand": "猫头鹰", "model": "NH-D15", "specs": "双塔风冷 / 150W+", "price": 699, "link": "https://item.jd.com/100033210987.html" }
|
{ "image": "", "brand": "猫头鹰", "model": "NH-D15", "specs": "双塔风冷 / 150W+", "price": 699, "link": "https://item.jd.com/100033210987.html" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user