feat: add version and updatedAt fields with export check and display
This commit is contained in:
@@ -0,0 +1,315 @@
|
|||||||
|
# PC装机配置表 Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Build a single-page HTML web application for PC build configuration management with JSON storage, edit/view modes, modal editing, and real-time total price calculation.
|
||||||
|
|
||||||
|
**Architecture:** Single HTML file with embedded CSS and JavaScript. Data loaded from JSON via URL parameter or file import. Two modes: view (read-only with alternative selection) and edit (add/delete/edit components via modal). Local dev via Python http.server.
|
||||||
|
|
||||||
|
**Tech Stack:** Vanilla HTML/CSS/JS (no frameworks), Python http.server
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Single HTML file: `index.html` — all CSS and JS embedded
|
||||||
|
- No external dependencies (no CDN, no frameworks)
|
||||||
|
- JSON config loaded via `?config=<url>` URL parameter
|
||||||
|
- Local dev server: Python `http.server` on port 8080
|
||||||
|
- Component types: preset dropdown (CPU, 主板, 显卡, 内存, 硬盘, 电源, 机箱, 散热器, 显示器) + custom input
|
||||||
|
- Chinese UI language
|
||||||
|
- Responsive design (desktop-first, mobile-friendly)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Create project structure and sample JSON
|
||||||
|
|
||||||
|
**Covers:** Data structure definition
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `sample_config.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create sample JSON config**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"configName": "我的装机方案",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"type": "CPU",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "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" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "主板",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "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" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "显卡",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "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" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "内存",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "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" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "硬盘",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "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" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "电源",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "brand": "海韵", "model": "FOCUS GX-850", "specs": "850W / 80Plus金牌 / 全模组", "price": 899, "link": "https://item.jd.com/100036543210.html" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "机箱",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "brand": "联力", "model": "O11 Dynamic EVO", "specs": "中塔 / 玻璃侧透", "price": 999, "link": "https://item.jd.com/100035432109.html" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "散热器",
|
||||||
|
"selected": 0,
|
||||||
|
"options": [
|
||||||
|
{ "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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add sample_config.json
|
||||||
|
git commit -m "feat: add sample PC configuration JSON"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Create the main HTML page with view mode
|
||||||
|
|
||||||
|
**Covers:** View mode, component display, alternative selection, total price
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `index.html`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: JSON data (from URL param or import)
|
||||||
|
- Produces: Rendered configuration list with selectable alternatives
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create HTML structure with CSS**
|
||||||
|
|
||||||
|
Create `index.html` with:
|
||||||
|
- Header section: config name, mode toggle button, import button
|
||||||
|
- Main content: component cards list
|
||||||
|
- Footer: total price display
|
||||||
|
- Modal template (hidden by default)
|
||||||
|
- Embedded CSS for styling
|
||||||
|
|
||||||
|
Key CSS:
|
||||||
|
- Card-based layout for components
|
||||||
|
- Each card shows: type label, selected option details (brand, model, specs, price), link button
|
||||||
|
- Alternative selector as a horizontal pill/dropdown within each card
|
||||||
|
- Responsive grid layout
|
||||||
|
- Modal overlay with form fields
|
||||||
|
|
||||||
|
- [ ] **Step 2: Implement JSON loading**
|
||||||
|
|
||||||
|
JavaScript functions:
|
||||||
|
- `loadConfig()`: parse URL param `?config=`, fetch JSON, store in memory
|
||||||
|
- `importConfig()`: file input handler, read JSON file
|
||||||
|
- `renderConfig()`: render all components to DOM
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement view mode rendering**
|
||||||
|
|
||||||
|
- Render each component as a card
|
||||||
|
- Show current selected option's brand, model, specs, price, link
|
||||||
|
- If multiple options exist, show selector (radio buttons or dropdown)
|
||||||
|
- On selector change, update displayed info and recalculate total
|
||||||
|
- Bottom bar shows sum of all selected options' prices
|
||||||
|
|
||||||
|
- [ ] **Step 4: Test locally**
|
||||||
|
|
||||||
|
Run: `python -m http.server 8080`
|
||||||
|
Open: `http://localhost:8080/?config=sample_config.json`
|
||||||
|
Verify: all components render, alternatives switch correctly, total price updates
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add index.html
|
||||||
|
git commit -m "feat: implement view mode with component display and total price"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Implement edit mode
|
||||||
|
|
||||||
|
**Covers:** Edit mode, add/delete/edit components, modal dialog
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `index.html`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: current config state from Task 2
|
||||||
|
- Produces: editable component list with modal for data entry
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add edit mode toggle and UI state**
|
||||||
|
|
||||||
|
- Add edit/view toggle button in header
|
||||||
|
- Edit mode shows: "添加部件" button, per-component edit/delete icons
|
||||||
|
- View mode hides edit controls
|
||||||
|
- CSS for edit mode visual indicators
|
||||||
|
|
||||||
|
- [ ] **Step 2: Implement modal dialog**
|
||||||
|
|
||||||
|
Modal form fields:
|
||||||
|
- 部件类型: dropdown (preset options) + text input for custom
|
||||||
|
- 品牌: text input
|
||||||
|
- 型号: text input
|
||||||
|
- 参数: text input
|
||||||
|
- 价格: number input
|
||||||
|
- 商品链接: URL input
|
||||||
|
- 备选配置列表: show existing alternatives, add/remove buttons
|
||||||
|
- Save/Cancel buttons
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement add component flow**
|
||||||
|
|
||||||
|
- Click "添加部件" → open empty modal
|
||||||
|
- Save → append new component to config → re-render
|
||||||
|
|
||||||
|
- [ ] **Step 4: Implement edit component flow**
|
||||||
|
|
||||||
|
- Click edit icon → open modal pre-filled with component data
|
||||||
|
- Modal shows all alternatives for that component
|
||||||
|
- Can add new alternative, edit existing, remove alternative
|
||||||
|
- Save → update component in config → re-render
|
||||||
|
|
||||||
|
- [ ] **Step 5: Implement delete component flow**
|
||||||
|
|
||||||
|
- Click delete icon → confirmation dialog
|
||||||
|
- Confirm → remove component from config → re-render
|
||||||
|
|
||||||
|
- [ ] **Step 6: Implement JSON export**
|
||||||
|
|
||||||
|
- Add "导出JSON" button (visible in edit mode)
|
||||||
|
- Click → download current config as JSON file
|
||||||
|
- Filename: `{configName}.json`
|
||||||
|
|
||||||
|
- [ ] **Step 7: Test edit mode**
|
||||||
|
|
||||||
|
- Add a new component (e.g., 显示器) with 2 alternatives
|
||||||
|
- Edit an existing component, change selected alternative
|
||||||
|
- Delete a component
|
||||||
|
- Export JSON and verify content
|
||||||
|
- Switch between edit/view modes
|
||||||
|
|
||||||
|
- [ ] **Step 8: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add index.html
|
||||||
|
git commit -m "feat: implement edit mode with modal editing and JSON export"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Create local development server
|
||||||
|
|
||||||
|
**Covers:** Local dev environment
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `start_server.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create Python server script**
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Local development server for PC Configuration Builder."""
|
||||||
|
import http.server
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PORT = 8080
|
||||||
|
|
||||||
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
|
def end_headers(self):
|
||||||
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
|
super().end_headers()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
with http.server.HTTPServer(('', PORT), Handler) as httpd:
|
||||||
|
print(f'Serving at http://localhost:{PORT}')
|
||||||
|
print(f'Open: http://localhost:{PORT}/?config=sample_config.json')
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\nServer stopped.')
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Test server**
|
||||||
|
|
||||||
|
Run: `python start_server.py`
|
||||||
|
Open: `http://localhost:8080/?config=sample_config.json`
|
||||||
|
Verify: page loads, config displays correctly
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add start_server.py
|
||||||
|
git commit -m "feat: add local Python development server"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: Final integration and polish
|
||||||
|
|
||||||
|
**Covers:** All sections — final verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `index.html` (if needed)
|
||||||
|
|
||||||
|
- [ ] **Step 1: End-to-end test**
|
||||||
|
|
||||||
|
- Load config via URL parameter
|
||||||
|
- Load config via import button
|
||||||
|
- View mode: switch alternatives, verify total price
|
||||||
|
- Edit mode: add component, edit component, delete component
|
||||||
|
- Export JSON, re-import, verify data integrity
|
||||||
|
- Test with empty config (no components)
|
||||||
|
|
||||||
|
- [ ] **Step 2: UI polish**
|
||||||
|
|
||||||
|
- Ensure consistent spacing and alignment
|
||||||
|
- Mobile responsiveness check
|
||||||
|
- Error handling for invalid JSON
|
||||||
|
- Empty state message when no config loaded
|
||||||
|
|
||||||
|
- [ ] **Step 3: Final commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "feat: complete PC configuration builder application"
|
||||||
|
```
|
||||||
+10
-1
@@ -431,6 +431,7 @@ body {
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<h1 id="configName">PC装机配置表 - 编辑器</h1>
|
<h1 id="configName">PC装机配置表 - 编辑器</h1>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<span class="version-info" style="font-size:13px;color:#999;margin-right:8px">版本: <input type="text" id="versionInput" value="1.0.0" style="width:80px;padding:2px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px" onchange="config.version=this.value"></span>
|
||||||
<button class="btn btn-default btn-sm" onclick="openLocalFile()">打开文件</button>
|
<button class="btn btn-default btn-sm" onclick="openLocalFile()">打开文件</button>
|
||||||
<button class="btn btn-primary btn-sm" onclick="exportJSON()">导出JSON</button>
|
<button class="btn btn-primary btn-sm" onclick="exportJSON()">导出JSON</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -551,7 +552,8 @@ body {
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ========== State ==========
|
// ========== State ==========
|
||||||
let config = { configName: 'PC装机配置表', components: [] };
|
let config = { configName: 'PC装机配置表', version: '1.0.0', updatedAt: '', components: [] };
|
||||||
|
let initialVersion = '1.0.0';
|
||||||
let tempAlts = [];
|
let tempAlts = [];
|
||||||
|
|
||||||
const PRESET_TYPES = ['CPU', '主板', '显卡', '内存', '硬盘', '电源', '机箱', '散热器', '显示器'];
|
const PRESET_TYPES = ['CPU', '主板', '显卡', '内存', '硬盘', '电源', '机箱', '散热器', '显示器'];
|
||||||
@@ -666,6 +668,8 @@ function handleFileImport(event) {
|
|||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
try {
|
try {
|
||||||
config = JSON.parse(e.target.result);
|
config = JSON.parse(e.target.result);
|
||||||
|
initialVersion = config.version || '1.0.0';
|
||||||
|
document.getElementById('versionInput').value = initialVersion;
|
||||||
document.getElementById('configName').textContent = (config.configName || 'PC装机配置表') + ' - 编辑器';
|
document.getElementById('configName').textContent = (config.configName || 'PC装机配置表') + ' - 编辑器';
|
||||||
render();
|
render();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -677,6 +681,11 @@ function handleFileImport(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportJSON() {
|
function exportJSON() {
|
||||||
|
config.version = document.getElementById('versionInput').value.trim() || '1.0.0';
|
||||||
|
if (config.version === initialVersion) {
|
||||||
|
if (!confirm(`版本号仍为 ${config.version},未修改。是否继续导出?`)) return;
|
||||||
|
}
|
||||||
|
config.updatedAt = new Date().toISOString();
|
||||||
const json = JSON.stringify(config, null, 2);
|
const json = JSON.stringify(config, null, 2);
|
||||||
const blob = new Blob([json], { type: 'application/json' });
|
const blob = new Blob([json], { type: 'application/json' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|||||||
+16
-1
@@ -253,7 +253,10 @@ body {
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
<div>
|
||||||
<h1 id="configName">PC装机配置表</h1>
|
<h1 id="configName">PC装机配置表</h1>
|
||||||
|
<div id="configMeta" style="font-size:12px;color:#999;margin-top:4px"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contentArea"></div>
|
<div id="contentArea"></div>
|
||||||
@@ -286,13 +289,25 @@ function loadFromURL() {
|
|||||||
if (configURL) {
|
if (configURL) {
|
||||||
fetch(configURL)
|
fetch(configURL)
|
||||||
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
|
||||||
.then(data => { config = data; document.getElementById('configName').textContent = config.configName || 'PC装机配置表'; render(); })
|
.then(data => { config = data; document.getElementById('configName').textContent = config.configName || 'PC装机配置表'; updateMeta(); render(); })
|
||||||
.catch(err => { console.error('加载配置失败:', err); render(); });
|
.catch(err => { console.error('加载配置失败:', err); render(); });
|
||||||
} else {
|
} else {
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Meta ==========
|
||||||
|
function updateMeta() {
|
||||||
|
const meta = document.getElementById('configMeta');
|
||||||
|
const parts = [];
|
||||||
|
if (config.version) parts.push('v' + config.version);
|
||||||
|
if (config.updatedAt) {
|
||||||
|
const d = new Date(config.updatedAt);
|
||||||
|
parts.push('更新于 ' + d.toLocaleDateString('zh-CN') + ' ' + d.toLocaleTimeString('zh-CN', {hour:'2-digit', minute:'2-digit'}));
|
||||||
|
}
|
||||||
|
meta.textContent = parts.join(' · ');
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Render ==========
|
// ========== Render ==========
|
||||||
function render() {
|
function render() {
|
||||||
const area = document.getElementById('contentArea');
|
const area = document.getElementById('contentArea');
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"configName": "我的装机方案",
|
"configName": "我的装机方案",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"updatedAt": "2026-07-18T20:00:00.000Z",
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"type": "CPU",
|
"type": "CPU",
|
||||||
|
|||||||
Reference in New Issue
Block a user