Compare commits
2 Commits
91a010662d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e1b993e70 | ||
|
|
916e5386c5 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
backend/mcu.db
|
||||
backend/__pycache__/database.cpython-313.pyc
|
||||
backend/__pycache__/main.cpython-313.pyc
|
||||
54
README.md
54
README.md
@@ -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`.
|
||||
BIN
backend/__pycache__/database.cpython-313.pyc
Normal file
BIN
backend/__pycache__/database.cpython-313.pyc
Normal file
Binary file not shown.
BIN
backend/__pycache__/main.cpython-313.pyc
Normal file
BIN
backend/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
140
backend/admin.py
Normal file
140
backend/admin.py
Normal 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
98
backend/database.py
Normal 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
59
backend/main.py
Normal 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
24
frontend/.gitignore
vendored
Normal 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
3
frontend/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
5
frontend/README.md
Normal file
5
frontend/README.md
Normal 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
13
frontend/index.html
Normal 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
1354
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
frontend/package.json
Normal file
23
frontend/package.json
Normal 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
1
frontend/public/vite.svg
Normal 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
3
frontend/src/App.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
1
frontend/src/assets/vue.svg
Normal file
1
frontend/src/assets/vue.svg
Normal 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 |
43
frontend/src/components/HelloWorld.vue
Normal file
43
frontend/src/components/HelloWorld.vue
Normal 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
9
frontend/src/main.js
Normal 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");
|
||||
15
frontend/src/router/index.js
Normal file
15
frontend/src/router/index.js
Normal 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
79
frontend/src/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
73
frontend/src/views/CompareView.vue
Normal file
73
frontend/src/views/CompareView.vue
Normal 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>
|
||||
58
frontend/src/views/DetailView.vue
Normal file
58
frontend/src/views/DetailView.vue
Normal 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>
|
||||
76
frontend/src/views/ListView.vue
Normal file
76
frontend/src/views/ListView.vue
Normal 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
15
frontend/vite.config.js
Normal 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
49
requirements.txt
Normal 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
|
||||
Reference in New Issue
Block a user