Compare commits

...

2 Commits

Author SHA1 Message Date
swangnice
0e1b993e70 Finish database, API, Compare/Detail/Compare Views 2025-08-02 21:06:23 +08:00
swangnice
916e5386c5 Finish database, API, Compare/Detail/Compare Views 2025-08-02 21:05:35 +08:00
25 changed files with 2198 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
backend/mcu.db
backend/__pycache__/database.cpython-313.pyc
backend/__pycache__/main.cpython-313.pyc

View File

@@ -0,0 +1,54 @@
# A Personal MCU Benchmark
This is a personal project to benchmark various MCUs (Microcontroller Units) using FastAPI and SQLite for the backend and Vue.js for the frontend. The project allows you to add, list, and compare different MCUs.
```
mcubenchmark/
├── backend/
│ ├── main.py # FastAPI 主入口
│ ├── database.py # SQLite Base operations
│ ├── admin.py # SQLite CURD
│ └── mcu.db
├── frontend/
│ ├── public/
│ ├── src/
│ │ ├── views/
│ │ │ ├── ListView.vue
│ │ │ └── CompareView.vue
│ │ ├── App.vue
│ │ └── main.js
├── README.md
└── requirements.txt
```
## How to use?
Clone this repo, and install the requirements:
```bash
cd mcu-benchmark
conda create -n mcu-benchmark
conda activate mcu-benchmark
pip install -r requirements.txt
```
Then, start the backend server:
```bash
cd backend
uvicorn main:app --host 0.0.0.0 --port 3010 --reload
```
Use `admin.py` and follow the instructions to add MCUs to the database:
```
python admin.py
```
Now, you can access the backend API at `http://localhost:3010/api/mcus`.
Run the frontend (before this step, you need to install Node.js, npm and other dependencies):
```bash
cd backend
npm run dev
```
Now, you can access the frontend at `http://localhost:<port>`. The default port of Vite is `5173`.

Binary file not shown.

Binary file not shown.

140
backend/admin.py Normal file
View File

@@ -0,0 +1,140 @@
from database import init_db, get_all_mcus, get_mcu, add_mcu, update_mcu, delete_mcu
# 定义所有 MCU 字段(除了 id
MCU_FIELDS = [
"name", "core_num", "core_type", "instruction_set", "coremark", "coremark_per_mhz",
"frequency", "flash", "rom", "ram", "sram", "sram_in_rtc", "bus_width",
"cache_type", "l1_cache_size", "l2_cache_size", "l3_cache_size",
"pipeline_depth", "simd_support", "fpu", "package", "gpios", "uarts",
"i2cs", "i2ses", "spis", "spi_protocols", "systems", "manufacturer", "link"
]
FIELD_LABELS = {
"name": "Name",
"core_num": "# of Cores",
"core_type": "Core Architecture",
"instruction_set": "Instruction Set",
"coremark": "CoreMark Score",
"coremark_per_mhz": "CoreMark/MHz",
"frequency": "Frequency (MHz)",
"flash": "Flash (KB)",
"rom": "ROM (KB)",
"ram": "RAM (KB)",
"sram": "SRAM (KB)",
"sram_in_rtc": "RTC SRAM (KB)",
"bus_width": "Bus Width (bit)",
"cache_type": "Cache Type",
"l1_cache_size": "L1 Cache (KB)",
"l2_cache_size": "L2 Cache (KB)",
"l3_cache_size": "L3 Cache (KB)",
"pipeline_depth": "Pipeline Depth",
"simd_support": "SIMD support (y=1, n=0)",
"fpu": "FPU support (Single/Double Precision)",
"package": "Package",
"gpios": "Number of GPIO ",
"uarts": "Number of UARTs",
"i2cs": "Number of I2C",
"i2ses": "Number of I2S",
"spis": "Number of SPI",
"spi_protocols": "SPI Protocols",
"systems": "Systems",
"manufacturer": "Manufacturer",
"link": "Link"
}
def list_mcus():
mcus = get_all_mcus()
if not mcus:
print("⚠️ MCU 数据库为空。")
else:
print("\n=== MCU 列表 ===")
for m in mcus:
print(f"ID: {m['id']} | 名称: {m['name']} | 核心: {m['core_num']} x {m['core_type']} | "
f"频率: {m['frequency']} MHz | 厂商: {m.get('manufacturer','')}")
print("=================\n")
def input_fields(existing=None):
"""动态输入所有字段友好名称提示existing 用于编辑时提供默认值"""
data = {}
for field in MCU_FIELDS:
old_val = existing.get(field) if existing else ""
display_name = FIELD_LABELS.get(field, field) # ✅ 使用中文提示
prompt = f"{display_name} ({old_val if old_val is not None else ''}): "
val = input(prompt).strip()
if val == "" and existing: # 编辑时保留原值
continue
if val == "": # 新增时空值存 None
data[field] = None
else:
# ✅ 类型转换
if field in ["pipeline_depth"]:
try:
data[field] = int(val)
except ValueError:
data[field] = None
elif field in ["coremark_per_mhz"]:
try:
data[field] = float(val)
except ValueError:
data[field] = None
elif field == "simd_support":
data[field] = val.lower() in ["1", "true", "yes", "y"]
else:
data[field] = val
return data
def create_mcu():
print("\n=== 添加 MCU ===")
fields = input_fields()
add_mcu(**fields)
print("✅ MCU 添加成功!")
def edit_mcu():
mcu_id = int(input("输入要修改的 MCU ID: "))
existing = get_mcu(mcu_id)
if not existing:
print("❌ MCU ID 不存在")
return
print("\n=== 编辑 MCU直接回车保留原值 ===")
fields = input_fields(existing)
if fields:
update_mcu(mcu_id, **fields)
print("✅ MCU 更新成功!")
else:
print("⚠️ 未修改任何字段。")
def remove_mcu():
mcu_id = int(input("输入要删除的 MCU ID: "))
if not get_mcu(mcu_id):
print("❌ MCU ID 不存在")
return
delete_mcu(mcu_id)
print("✅ MCU 删除成功!")
def menu():
while True:
print("\n=== MCU 管理菜单 ===")
print("1. 查看所有 MCU")
print("2. 添加 MCU")
print("3. 修改 MCU")
print("4. 删除 MCU")
print("5. 退出")
choice = input("选择操作 (1-5): ")
if choice == "1":
list_mcus()
elif choice == "2":
create_mcu()
elif choice == "3":
edit_mcu()
elif choice == "4":
remove_mcu()
elif choice == "5":
print("👋 退出 MCU 管理工具")
break
else:
print("❌ 无效选择,请重新输入。")
if __name__ == "__main__":
init_db()
menu()

98
backend/database.py Normal file
View File

@@ -0,0 +1,98 @@
import sqlite3
from pathlib import Path
DB_PATH = Path(__file__).parent / "mcu.db"
def init_db():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS mcu (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- ID
name TEXT NOT NULL, -- 名称
core_num TEXT NOT NULL, -- 核心数量
core_type TEXT NOT NULL, -- 核心架构
instruction_set TEXT, -- 指令集架构
coremark TEXT, -- CoreMark 分数
coremark_per_mhz REAL, -- CoreMark/MHz
frequency TEXT NOT NULL, -- 主频 MHz
flash TEXT, -- Flash KB
rom TEXT, -- ROM KB
ram TEXT, -- RAM KB
sram TEXT, -- SRAM KB
sram_in_rtc TEXT, -- RTC SRAM KB
bus_width TEXT, -- 总线宽度 bit
cache_type TEXT, -- Cache 类型 L1/L2
l1_cache_size TEXT, -- L1 Cache 大小 KB
l2_cache_size TEXT, -- L2 Cache 大小 KB
l3_cache_size TEXT, -- L3 Cache 大小 KB
pipeline_depth TEXT, -- 流水线深度
simd_support BOOLEAN, -- SIMD 支持
fpu TEXT, -- FPU 支持 Single/Double Precision
package TEXT, -- 封装
gpios TEXT, -- GPIO 引脚数量
uarts TEXT, -- UART 数量
i2cs TEXT, -- I2C 数量
i2ses TEXT, -- I2S 数量
spis TEXT, -- SPI 数量
spi_protocols TEXT, -- SPI 协议
systems TEXT, -- 系统
manufacturer TEXT, -- 厂商
link TEXT -- 链接
)
""")
conn.commit()
conn.close()
# ==================== 查询所有 MCU关键字段 ====================
def get_all_mcus():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT id, name, core_num, core_type, rom, ram, sram, flash, frequency, manufacturer FROM mcu")
rows = cur.fetchall()
conn.close()
return [{"id": i, "name": n, "core_num": cn, "core_type": ct, "rom": ro, "ram": ra, "sram": sra, "flash":fl, "frequency": f, "manufacturer": m}
for i, n, cn, ct, ro, ra, sra, fl, f, m in rows]
# ==================== 查询单个 MCU完整字段 ====================
def get_mcu(mcu_id):
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row # 允许返回 dict
cur = conn.cursor()
cur.execute("SELECT * FROM mcu WHERE id=?", (mcu_id,))
row = cur.fetchone()
conn.close()
return dict(row) if row else None
# ==================== 插入 MCU支持所有字段 ====================
def add_mcu(**fields):
"""动态插入 MCU支持所有字段"""
keys = ",".join(fields.keys())
placeholders = ",".join("?" for _ in fields)
values = tuple(fields.values())
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute(f"INSERT INTO mcu ({keys}) VALUES ({placeholders})", values)
conn.commit()
conn.close()
# ==================== 更新 MCU支持所有字段 ====================
def update_mcu(mcu_id, **fields):
"""动态更新 MCU支持所有字段"""
set_clause = ",".join(f"{k}=?" for k in fields.keys())
values = tuple(fields.values()) + (mcu_id,)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute(f"UPDATE mcu SET {set_clause} WHERE id=?", values)
conn.commit()
conn.close()
# ==================== 删除 MCU ====================
def delete_mcu(mcu_id):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM mcu WHERE id=?", (mcu_id,))
conn.commit()
conn.close()

59
backend/main.py Normal file
View File

@@ -0,0 +1,59 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from database import init_db, get_all_mcus, get_mcu, add_mcu, update_mcu, delete_mcu
# Pydantic 模型(用于请求体验证)
class MCU(BaseModel):
name: str
core: str
frequency: int
# 初始化 FastAPI
app = FastAPI(title="MCU Compare API", version="1.0")
# 允许跨域
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# 初始化数据库
init_db()
# ✅ 1. 获取所有 MCU
@app.get("/api/mcus")
def read_mcus():
return get_all_mcus()
# ✅ 2. 获取单个 MCU
@app.get("/api/mcus/{mcu_id}")
def read_mcu(mcu_id: int):
mcu = get_mcu(mcu_id)
if not mcu:
raise HTTPException(status_code=404, detail="MCU not found")
return mcu
# ✅ 3. 创建 MCU
@app.post("/api/mcus")
def create_mcu(mcu: MCU):
add_mcu(mcu.name, mcu.core, mcu.frequency)
return {"message": "MCU added successfully"}
# ✅ 4. 更新 MCU
@app.put("/api/mcus/{mcu_id}")
def modify_mcu(mcu_id: int, mcu: MCU):
if not get_mcu(mcu_id):
raise HTTPException(status_code=404, detail="MCU not found")
update_mcu(mcu_id, mcu.name, mcu.core, mcu.frequency)
return {"message": "MCU updated successfully"}
# ✅ 5. 删除 MCU
@app.delete("/api/mcus/{mcu_id}")
def remove_mcu(mcu_id: int):
if not get_mcu(mcu_id):
raise HTTPException(status_code=404, detail="MCU not found")
delete_mcu(mcu_id)
return {"message": "MCU deleted successfully"}

24
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
frontend/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

5
frontend/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

13
frontend/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

1354
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
frontend/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@ag-grid-community/client-side-row-model": "^32.2.0",
"@ag-grid-community/core": "^32.2.0",
"ag-grid-community": "^34.1.0",
"ag-grid-vue3": "^32.2.0",
"vue": "^3.5.17",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.0",
"vite": "^7.0.4"
}
}

1
frontend/public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

3
frontend/src/App.vue Normal file
View File

@@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1,43 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

9
frontend/src/main.js Normal file
View File

@@ -0,0 +1,9 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index.js";
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
const app = createApp(App);
app.use(router);
app.mount("#app");

View File

@@ -0,0 +1,15 @@
import { createRouter, createWebHistory } from "vue-router";
import ListView from "../views/ListView.vue";
import DetailView from "../views/DetailView.vue";
import CompareView from "../views/CompareView.vue";
const routes = [
{ path: "/", component: ListView },
{ path: "/detail/:id", component: DetailView },
{ path: "/compare", component: CompareView }
];
export default createRouter({
history: createWebHistory(),
routes
});

79
frontend/src/style.css Normal file
View File

@@ -0,0 +1,79 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -0,0 +1,73 @@
<template>
<div style="padding:20px;">
<h2>MCU 对比</h2>
<table border="1" cellpadding="6">
<thead>
<tr>
<th>字段</th>
<th v-for="m in mcus" :key="m.id">{{ m.name }}</th>
</tr>
</thead>
<tbody>
<tr v-for="key in fieldKeys" :key="key">
<td><strong>{{ FIELD_LABELS[key] || key }}</strong></td>
<td v-for="m in mcus" :key="m.id">{{ m[key] }}</td>
</tr>
</tbody>
</table>
<button @click="$router.push('/')">返回</button>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const mcus = ref([]);
const fieldKeys = ref([]);
const FIELD_LABELS = {
name: "Name",
core_num: "Number of Cores",
core_type: "Core Architecture",
instruction_set: "Instruction Set",
coremark: "CoreMark Score",
coremark_per_mhz: "CoreMark per MHz",
frequency: "Frequency (MHz)",
flash: "Flash (KB)",
rom: "ROM (KB)",
ram: "RAM (KB)",
sram: "SRAM (KB)",
sram_in_rtc: "RTC SRAM (KB)",
bus_width: "Bus Width (bit)",
cache_type: "Cache Type",
l1_cache_size: "L1 Cache (KB)",
l2_cache_size: "L2 Cache (KB)",
l3_cache_size: "L3 Cache (KB)",
pipeline_depth: "Pipeline Depth",
simd_support: "SIMD Support (1 = Yes, 0 = No)",
fpu: "FPU Support (Single/Double Precision)",
package: "Package",
gpios: "Number of GPIOs",
uarts: "Number of UARTs",
i2cs: "Number of I2C Interfaces",
i2ses: "Number of I2S Interfaces",
spis: "Number of SPI Interfaces",
spi_protocols: "Supported SPI Protocols",
systems: "Supported Systems",
manufacturer: "Manufacturer",
link: "Product Link"
};
onMounted(async () => {
const ids = route.query.ids.split(",");
for (let id of ids) {
const res = await fetch(`/api/mcus/${id}`);
mcus.value.push(await res.json());
}
// Don't compare the "link" field
fieldKeys.value = Object.keys(FIELD_LABELS).filter(k => k !== "link");
});
</script>

View File

@@ -0,0 +1,58 @@
<template>
<div style="padding:20px;">
<h2>MCU 详情</h2>
<table border="1" cellpadding="6">
<tr v-for="(value, key) in mcu" :key="key">
<td><strong>{{ FIELD_LABELS[key] || key }}</strong></td>
<td>{{ value }}</td>
</tr>
</table>
<button @click="$router.push('/')">返回</button>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const mcu = ref({});
const FIELD_LABELS = {
name: "Name",
core_num: "Number of Cores",
core_type: "Core Architecture",
instruction_set: "Instruction Set",
coremark: "CoreMark Score",
coremark_per_mhz: "CoreMark per MHz",
frequency: "Frequency (MHz)",
flash: "Flash (KB)",
rom: "ROM (KB)",
ram: "RAM (KB)",
sram: "SRAM (KB)",
sram_in_rtc: "RTC SRAM (KB)",
bus_width: "Bus Width (bit)",
cache_type: "Cache Type",
l1_cache_size: "L1 Cache (KB)",
l2_cache_size: "L2 Cache (KB)",
l3_cache_size: "L3 Cache (KB)",
pipeline_depth: "Pipeline Depth",
simd_support: "SIMD Support (1 = Yes, 0 = No)",
fpu: "FPU Support (Single/Double Precision)",
package: "Package",
gpios: "Number of GPIOs",
uarts: "Number of UARTs",
i2cs: "Number of I2C Interfaces",
i2ses: "Number of I2S Interfaces",
spis: "Number of SPI Interfaces",
spi_protocols: "Supported SPI Protocols",
systems: "Supported Systems",
manufacturer: "Manufacturer",
link: "Product Link"
};
onMounted(async () => {
const res = await fetch(`/api/mcus/${route.params.id}`);
mcu.value = await res.json();
});
</script>

View File

@@ -0,0 +1,76 @@
<template>
<div style="padding:20px;">
<h1>MCU 对比表</h1>
<!-- 对比按钮 -->
<button @click="compareSelected" :disabled="selectedIds.length < 2" style="margin-bottom:10px;">
对比选中 ({{ selectedIds.length }})
</button>
<div class="ag-theme-alpine" style="height:600px; width:100%;">
<ag-grid-vue
class="ag-theme-alpine"
style="height:600px; width:100%;"
:rowData="rowData"
:columnDefs="columns"
rowSelection="multiple"
@selectionChanged="onSelectionChanged"
@rowClicked="onRowClicked"
:pagination="true"
:paginationPageSize="20"
:paginationPageSizeSelector="[10,20,50,100]">
</ag-grid-vue>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { AgGridVue } from "ag-grid-vue3";
import { ModuleRegistry } from "@ag-grid-community/core";
import { ClientSideRowModelModule } from "@ag-grid-community/client-side-row-model";
ModuleRegistry.registerModules([ClientSideRowModelModule]);
import { useRouter } from "vue-router";
const router = useRouter();
const rowData = ref([]);
const selectedIds = ref([]); // ✅ 存储选中的 ID
// ✅ 列配置:第一列加 checkbox
const columns = [
{ checkboxSelection: true, headerCheckboxSelection: true, width: 50 },
{ headerName: "ID", field: "id", width: 80 },
{ headerName: "Name", field: "name", sortable: true, filter: true, width: 300 },
{ headerName: "# of Cores", field: "core_num", sortable: true, width: 80 },
{ headerName: "Cores Architecture", field: "core_type", sortable: true, width: 160 },
{ headerName: "Frequency (MHz)", field: "frequency", sortable: true, width: 150 },
{ headerName: "Flash (KB)", field: "flash", sortable: true, width: 120 },
{ headerName: "ROM (KB)", field: "rom", sortable: true, width: 120 },
{ headerName: "RAM (KB)", field: "ram", sortable: true, width: 120 },
{ headerName: "SRAM (KB)", field: "sram", sortable: true, width: 120 },
{ headerName: "Manufacturer", field: "manufacturer", sortable: true }
];
onMounted(async () => {
const res = await fetch("/api/mcus");
rowData.value = await res.json();
console.log("✅ MCU 数据:", rowData.value);
});
// ✅ 监听选择变化
const onSelectionChanged = (event) => {
selectedIds.value = event.api.getSelectedRows().map(row => row.id);
};
// ✅ 点击行 → 进入详情页(保留原功能)
const onRowClicked = (event) => {
router.push(`/detail/${event.data.id}`);
};
// ✅ 点击“对比” → 跳转 CompareView
const compareSelected = () => {
router.push(`/compare?ids=${selectedIds.value.join(",")}`);
};
</script>

15
frontend/vite.config.js Normal file
View File

@@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
'/api': 'http://localhost:3010' // 代理后端 API
},
allowedHosts: [
'mcubenchmark.swangnice.cn'
],
host: true
}
})

49
requirements.txt Normal file
View File

@@ -0,0 +1,49 @@
altair==5.5.0
annotated-types==0.7.0
anyio==4.9.0
attrs==25.3.0
blinker==1.9.0
cachetools==6.1.0
certifi==2025.7.14
charset-normalizer==3.4.2
click==8.2.1
fastapi==0.116.1
gitdb==4.0.12
GitPython==3.1.45
h11==0.16.0
idna==3.10
Jinja2==3.1.6
jsonschema==4.25.0
jsonschema-specifications==2025.4.1
MarkupSafe==3.0.2
narwhals==1.48.1
numpy==2.3.2
packaging==25.0
pandas==2.3.1
pillow==11.3.0
protobuf==6.31.1
pyarrow==21.0.0
pydantic==2.11.7
pydantic_core==2.33.2
pydeck==0.9.1
python-dateutil==2.9.0.post0
pytz==2025.2
referencing==0.36.2
requests==2.32.4
rpds-py==0.26.0
setuptools==78.1.1
six==1.17.0
smmap==5.0.2
sniffio==1.3.1
starlette==0.47.2
streamlit==1.47.0
tenacity==9.1.2
toml==0.10.2
tornado==6.5.1
typing-inspection==0.4.1
typing_extensions==4.14.1
tzdata==2025.2
urllib3==2.5.0
uvicorn==0.35.0
watchdog==6.0.0
wheel==0.45.1