Optimize DSP interface: stacked r/delta, fixed r-drift, locked phase, and linked axes
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Difference Equation Analyzer (DEA) - Architecture & Functionality
|
||||
|
||||
## Overview
|
||||
DEA is a specialized tool for designing, visualizing, and validating discrete-time filters. It bridges the gap between theoretical filter design (floating-point) and hardware implementation (fixed-point/MCU).
|
||||
|
||||
## 🏗 System Architecture
|
||||
|
||||
### 1. Backend (Python 3.12 + FastAPI)
|
||||
The backend is responsible for heavy mathematical computations and data processing.
|
||||
|
||||
- **API Entry (`dea_api.py`)**: Routes requests to appropriate modules and serves the Vue.js frontend from `static/`.
|
||||
- **Computation Engine (`dea/`)**:
|
||||
- `filter_design.py`: Uses `scipy.signal` to calculate coefficients for various filter topologies.
|
||||
- `bode.py`: Computes magnitude and phase response. Supports comparing "Ideal" (float) vs "Fixed" (quantized) responses.
|
||||
- `csv_processing.py`: Processes time-domain CSV data. It applies the current filter to the signal and downsamples the result for efficient visualization.
|
||||
- `validation.py`: Protects against unstable filter designs and invalid inputs.
|
||||
|
||||
### 2. Frontend (Vue 3 + Vite + Tailwind)
|
||||
A reactive "Single Page Application" (SPA) approach.
|
||||
|
||||
- **Reactive Logic (`src/app-options.js`)**:
|
||||
- Manages the state of all coefficients.
|
||||
- Implements "Fine-tuning" sliders that allow real-time adjustment of poles/zeros.
|
||||
- Handles Fixed-Point conversion logic (Q-format shifting).
|
||||
- **UI Components (`src/App.vue`)**:
|
||||
- **Control Sidebar**: Interactive inputs for all filter parameters.
|
||||
- **Visualization**: Dual-plot system using Plotly.js for Bode plots and Time-domain waveforms.
|
||||
- **Hardware Bridge**: Uses the **Web Serial API** to send commands directly to connected MCUs (requires HTTPS).
|
||||
|
||||
## 🚀 Execution Flow
|
||||
1. **Design**: User selects a filter type or enters coefficients manually.
|
||||
2. **Quantize**: User adjusts Q-format bits to see how quantization affects the frequency response.
|
||||
3. **Verify**: User uploads a CSV to see how the filter behaves with real-world signals.
|
||||
4. **Deploy**: User clicks "Write to MCU" to send the coefficients to their hardware.
|
||||
|
||||
## 🛠 Tech Stack
|
||||
- **Backend**: FastAPI, NumPy, SciPy, Pandas, Uvicorn.
|
||||
- **Frontend**: Vue 3, Vite, Tailwind CSS, Plotly.js.
|
||||
- **Security**: Restricted to LAN/Loopback; implemented security headers and HTTPS support.
|
||||
@@ -0,0 +1,43 @@
|
||||
# DSP Bode Plot 專案進度總結 (2026-05-15)
|
||||
|
||||
## 1. 核心功能優化 (DSP & 定點數運算)
|
||||
- **細部微調 (Fine-tuning)**:實施了基於 $\delta$ (Delta) 與 $r$ (Radius) 的參數化微調。
|
||||
- **定點數精確度修正**:採用「兩倍值整數運算」邏輯,解決了 $0.5$ 步階在浮點數圓整時產生的「按鍵沒反應」Bug。
|
||||
- **防止漂移 (Oscillation Logic)**:針對數位微調,實施了奇偶震盪平衡邏輯。當調整 $\delta$ 導致 $r$ 必須隨動時,採用震盪補償防止 $r$ 產生單向累積誤差。
|
||||
- **參數連動策略**:在 $0.5$ 步階時,採取「優先滿足目標變動」原則。若調整 $\delta$,則 $r$ 隨動;反之亦然。
|
||||
|
||||
## 2. UI/UX 介面調整
|
||||
- **側邊欄優化**:
|
||||
- 將 $r$ 與 $\delta$ 的調整區域改為**垂直堆疊**,適應 21rem 的窄邊欄。
|
||||
- 移除冗餘的 $a1, a2$ 預覽方塊,僅保留帶有數學公式標籤的 $r, \delta$ 預覽。
|
||||
- **步階序列**:數位微調步階改為自訂序列:`[0.5, 1, 2, 4, 8, 32, 256, 1024, 4096, 16384, 65536]`。
|
||||
- **微調量顯示**:統一使用 `±` 符號並帶空格(例如 `± 0.5`)。
|
||||
- **智能禁用**:當數位步階為 $0.5$ 時,自動禁用 $r$ 的控制項,確保以 $\delta$ 為主導。
|
||||
|
||||
## 3. 繪圖系統 (Bode Plot)
|
||||
- **幅值圖 (Magnitude)**:
|
||||
- 初始範圍:基於 $systemGain \times 3$,涵蓋 $70 \text{ dB}$。
|
||||
- 保留手動彈性:設定為 `fixedrange: false`,支援滑鼠縮放與 Auto Scale。
|
||||
- 狀態保持:使用 `uirevision`,確保微調參數時不會跳回初始縮放。
|
||||
- **相位圖 (Phase)**:
|
||||
- 絕對鎖死:固定範圍為 $[-180^\circ, 180^\circ]$。
|
||||
- 禁止縮放:設定為 `fixedrange: true`,提供穩定的絕對參考。
|
||||
- **座標軸連動**:幅值圖與相位圖的 **X 軸(頻率)已完全同步**。不論在哪一張圖進行橫向縮放,兩者都會連動。
|
||||
|
||||
## 4. 數學公式參考
|
||||
- **理想空間**:
|
||||
- $a1 = -1 - r + \delta$
|
||||
- $a2 = r + \delta$
|
||||
- **定點數空間 (Q14 為例)**:
|
||||
- $a0 = 2^{14} = 16384$
|
||||
- $a1 = -a0 - r + \delta$
|
||||
- $a2 = r + \delta$
|
||||
- $2\delta = a1 + a2 + a0$
|
||||
- $2r = a2 - a1 - a0$
|
||||
|
||||
## 5. 目前 Git 狀態
|
||||
- **當前分支**:`main` (已與 `origin/main` 同步)。
|
||||
- **建議後續操作**:若要儲存此穩定版本,可考慮建立分支 `dev-ui-fix-20260515`。
|
||||
|
||||
---
|
||||
*Last Updated: 2026-05-15 17:45*
|
||||
+19
-19
@@ -1,27 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# 產生自簽 SSL 憑證供 Uvicorn HTTPS 使用
|
||||
# 用法: bash certs/generate_cert.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CERT_FILE="${SCRIPT_DIR}/cert.pem"
|
||||
KEY_FILE="${SCRIPT_DIR}/key.pem"
|
||||
#!/usr/bin/env bash
|
||||
# 產生自簽 SSL 憑證供 Uvicorn HTTPS 使用
|
||||
# 用法: bash certs/generate_cert.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
CERT_FILE="${SCRIPT_DIR}/cert.pem"
|
||||
KEY_FILE="${SCRIPT_DIR}/key.pem"
|
||||
DAYS=3650
|
||||
|
||||
if [ -f "$CERT_FILE" ] && [ -f "$KEY_FILE" ]; then
|
||||
echo "憑證已存在: ${CERT_FILE}, ${KEY_FILE}"
|
||||
echo "如需重新產生,請先刪除後再執行。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -nodes \
|
||||
echo "憑證已存在: ${CERT_FILE}, ${KEY_FILE}"
|
||||
echo "如需重新產生,請先刪除後再執行。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -nodes \
|
||||
-keyout "$KEY_FILE" \
|
||||
-out "$CERT_FILE" \
|
||||
-days "$DAYS" \
|
||||
-subj "/CN=$(hostname)" \
|
||||
-addext "subjectAltName=DNS:$(hostname),IP:0.0.0.0"
|
||||
|
||||
echo "已產生自簽憑證(有效 ${DAYS} 天):"
|
||||
echo " cert: ${CERT_FILE}"
|
||||
echo " key: ${KEY_FILE}"
|
||||
|
||||
echo "已產生自簽憑證(有效 ${DAYS} 天):"
|
||||
echo " cert: ${CERT_FILE}"
|
||||
echo " key: ${KEY_FILE}"
|
||||
|
||||
Generated
+152
-185
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "diff-eq-analyzer",
|
||||
"name": "bodeplot",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@@ -175,17 +175,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.128.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz",
|
||||
"integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==",
|
||||
"version": "0.130.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz",
|
||||
"integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz",
|
||||
"integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -198,9 +198,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz",
|
||||
"integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -213,9 +213,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz",
|
||||
"integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -228,9 +228,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz",
|
||||
"integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -243,9 +243,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz",
|
||||
"integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -258,9 +258,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -273,9 +273,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz",
|
||||
"integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -288,9 +288,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -303,9 +303,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -318,9 +318,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz",
|
||||
"integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -333,9 +333,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz",
|
||||
"integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -348,9 +348,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz",
|
||||
"integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -363,9 +363,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz",
|
||||
"integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -380,9 +380,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz",
|
||||
"integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -395,9 +395,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz",
|
||||
"integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -410,9 +410,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.13",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz",
|
||||
"integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA=="
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
@@ -424,11 +424,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "6.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.6.tgz",
|
||||
"integrity": "sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==",
|
||||
"version": "6.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz",
|
||||
"integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==",
|
||||
"dependencies": {
|
||||
"@rolldown/pluginutils": "1.0.0-rc.13"
|
||||
"@rolldown/pluginutils": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
@@ -546,17 +546,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch/node_modules/picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
@@ -766,9 +755,9 @@
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.353",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz",
|
||||
"integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w=="
|
||||
"version": "1.5.356",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.356.tgz",
|
||||
"integrity": "sha512-9NgFd7m5t5MCJ5rUSjJITUXAH9mEGlrlofnMf4YEr+pz6JlP7cWmTAH+JFmbPnaSW8koVTkuW7pacORWAnA5Yw=="
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
@@ -836,22 +825,6 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -971,13 +944,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
@@ -1261,17 +1232,6 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch/node_modules/picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/mz": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
@@ -1300,9 +1260,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.38",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
|
||||
"integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="
|
||||
"version": "2.0.44",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz",
|
||||
"integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ=="
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
@@ -1339,11 +1299,11 @@
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
@@ -1433,9 +1393,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-load-config": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
||||
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1447,21 +1407,28 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"lilconfig": "^3.0.0",
|
||||
"yaml": "^2.3.4"
|
||||
"lilconfig": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jiti": ">=1.21.0",
|
||||
"postcss": ">=8.0.9",
|
||||
"ts-node": ">=9.0.0"
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"postcss": {
|
||||
"optional": true
|
||||
},
|
||||
"ts-node": {
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
@@ -1545,17 +1512,6 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp/node_modules/picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.12",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
|
||||
@@ -1586,12 +1542,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz",
|
||||
"integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.128.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.18"
|
||||
"@oxc-project/types": "=0.130.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
@@ -1600,28 +1556,23 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.18",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.18",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.18",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18"
|
||||
"@rolldown/binding-android-arm64": "1.0.1",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.1",
|
||||
"@rolldown/binding-darwin-x64": "1.0.1",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.1",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.1",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.1",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.1",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.1",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.1",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.1",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.1",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.18",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz",
|
||||
"integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw=="
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
@@ -1685,9 +1636,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||
"version": "3.4.19",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
||||
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@@ -1697,7 +1648,7 @@
|
||||
"fast-glob": "^3.3.2",
|
||||
"glob-parent": "^6.0.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"jiti": "^1.21.6",
|
||||
"jiti": "^1.21.7",
|
||||
"lilconfig": "^3.1.3",
|
||||
"micromatch": "^4.0.8",
|
||||
"normalize-path": "^3.0.0",
|
||||
@@ -1706,7 +1657,7 @@
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-js": "^4.0.1",
|
||||
"postcss-load-config": "^4.0.2",
|
||||
"postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
|
||||
"postcss-nested": "^6.2.0",
|
||||
"postcss-selector-parser": "^6.1.2",
|
||||
"resolve": "^1.22.8",
|
||||
@@ -1720,14 +1671,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/jiti": {
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
@@ -1762,6 +1705,33 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -1819,14 +1789,14 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz",
|
||||
"integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==",
|
||||
"version": "8.0.13",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz",
|
||||
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.14",
|
||||
"rolldown": "1.0.0-rc.18",
|
||||
"rolldown": "1.0.1",
|
||||
"tinyglobby": "^0.2.16"
|
||||
},
|
||||
"bin": {
|
||||
@@ -1894,6 +1864,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.34",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz",
|
||||
@@ -1913,20 +1894,6 @@
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
|
||||
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+59
-73
@@ -55,12 +55,12 @@
|
||||
<div class="flex flex-col lg:flex-row flex-1 overflow-hidden">
|
||||
<!-- 左側控制面板 (RWD: 手機版=設定頁籤全螢幕,桌面版=左側邊欄) -->
|
||||
<aside :class="mobileTab === 'settings' ? 'flex' : 'hidden'"
|
||||
class="lg:flex flex-col w-full lg:w-96 h-full bg-white dark:bg-dark border-b lg:border-b-0 lg:border-r border-slate-200 dark:border-gray-800 overflow-y-auto p-5 gap-5 custom-scrollbar transition-colors duration-300">
|
||||
class="lg:flex flex-col w-full lg:w-96 h-full bg-white dark:bg-dark border-b lg:border-b-0 lg:border-r border-slate-200 dark:border-gray-800 overflow-y-auto p-4 gap-3 custom-scrollbar transition-colors duration-300">
|
||||
|
||||
<!-- 濾波器設計工具 -->
|
||||
<section>
|
||||
<h2
|
||||
class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-3 flex items-center justify-between">
|
||||
class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-2 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
@@ -81,7 +81,7 @@
|
||||
class="text-[10px] font-bold role-bg-secondary-soft role-border-secondary-soft role-text-secondary role-hover-secondary-soft border px-2 py-1 rounded transition-colors uppercase">重設設計</button>
|
||||
</div>
|
||||
</h2>
|
||||
<div class="mb-3 space-y-2">
|
||||
<div class="mb-2 space-y-1.5">
|
||||
<div v-if="webSerialSupported" class="flex items-center gap-2">
|
||||
<button v-if="!mcuConnected" @click="connectMCUPort"
|
||||
class="flex-1 text-xs font-bold role-bg-primary role-hover-primary role-text-on-fill role-border-primary border px-3 py-2 rounded transition-colors uppercase">
|
||||
@@ -104,13 +104,13 @@
|
||||
<p v-if="mcuStatus" class="text-xs text-slate-500 dark:text-gray-400">{{ mcuStatus }}</p>
|
||||
</div>
|
||||
<select v-model="filterType" @change="onFilterTypeChange"
|
||||
class="w-full bg-slate-50 dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-base role-focus-primary focus:ring-1 role-focus-ring-primary outline-none transition-all mb-3 text-slate-900 dark:text-gray-100 truncate">
|
||||
class="w-full bg-slate-50 dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-base role-focus-primary focus:ring-1 role-focus-ring-primary outline-none transition-all mb-2 text-slate-900 dark:text-gray-100 truncate">
|
||||
<option v-for="opt in filterOptions" :key="opt" :value="opt">{{ opt }}</option>
|
||||
</select>
|
||||
|
||||
<!-- 動態參數區 -->
|
||||
<div v-if="filterType !== '(無) 手動自訂'"
|
||||
class="space-y-3 bg-slate-100 dark:bg-gray-900 p-4 rounded-lg border border-slate-200 dark:border-gray-800 shadow-inner">
|
||||
class="space-y-2 bg-slate-100 dark:bg-gray-900 p-3 rounded-lg border border-slate-200 dark:border-gray-800 shadow-inner">
|
||||
<!-- Lowpass / Highpass -->
|
||||
<div v-if="['Lowpass (低通)', 'Highpass (高通)'].includes(filterType)">
|
||||
<label class="text-sm text-slate-500 dark:text-gray-400 flex justify-between mb-1"><span>截止頻率
|
||||
@@ -119,12 +119,12 @@
|
||||
<input type="range" v-model.number="params.fc" @input="debouncedApply" :min="1" :max="fs/2"
|
||||
class="w-full role-range-primary mb-2">
|
||||
<input type="number" v-model.number="params.fc" @change="applyFilterDesign"
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-3 text-base role-focus-primary outline-none text-slate-900 dark:text-gray-100">
|
||||
<label class="text-sm text-slate-500 dark:text-gray-400 mt-3 mb-2 block">階數 Order</label>
|
||||
<div class="selector-button-group flex gap-2">
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-2 text-base role-focus-primary outline-none text-slate-900 dark:text-gray-100">
|
||||
<label class="text-sm text-slate-500 dark:text-gray-400 mt-1.5 mb-1 block">階數 Order</label>
|
||||
<div class="selector-button-group flex gap-1">
|
||||
<button v-for="o in [1,2,3]" :key="o" @click="params.order = o; applyFilterDesign()"
|
||||
:class="params.order === o ? 'primary-action role-bg-primary role-text-on-fill role-border-primary' : 'bg-slate-200 dark:bg-gray-800 text-slate-600 dark:text-gray-400 border-slate-300 dark:border-gray-700'"
|
||||
class="selector-button flex-1 rounded py-2.5 text-base font-medium border transition-colors">{{ o
|
||||
class="selector-button flex-1 rounded py-0.5 text-sm font-medium border transition-colors">{{ o
|
||||
}}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,25 +139,25 @@
|
||||
f_high (Hz)</span><span class="role-text-primary">{{
|
||||
formatK(params.bp_f_high) }}</span></label>
|
||||
<input type="number" v-model.number="params.bp_f_high" @change="applyFilterDesign"
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-3 text-base mb-2 text-slate-900 dark:text-gray-100">
|
||||
<label class="text-sm text-slate-500 dark:text-gray-400 mt-2 mb-2 block">階數 Order</label>
|
||||
<div class="selector-button-group flex gap-2">
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-2 text-base mb-1 text-slate-900 dark:text-gray-100">
|
||||
<label class="text-sm text-slate-500 dark:text-gray-400 mt-1.5 mb-1 block">階數 Order</label>
|
||||
<div class="selector-button-group flex gap-1">
|
||||
<button v-for="o in [1,2,3]" :key="o" @click="params.bp_order = o; applyFilterDesign()"
|
||||
:class="params.bp_order === o ? 'primary-action role-bg-primary role-text-on-fill role-border-primary' : 'bg-slate-200 dark:bg-gray-800 text-slate-600 dark:text-gray-400 border-slate-300 dark:border-gray-700'"
|
||||
class="selector-button flex-1 rounded py-1.5 text-sm border transition-colors">{{ o }}</button>
|
||||
class="selector-button flex-1 rounded py-0.5 text-sm border transition-colors">{{ o }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Notch -->
|
||||
<div v-if="filterType === 'Notch (陷波器)'" class="space-y-3">
|
||||
<div><label class="text-sm text-slate-500 dark:text-gray-400 mb-2 block">中心頻率 f_0
|
||||
<div v-if="filterType === 'Notch (陷波器)'" class="space-y-2">
|
||||
<div><label class="text-sm text-slate-500 dark:text-gray-400 mb-1 block">中心頻率 f_0
|
||||
(Hz)</label><input type="number" v-model.number="params.notch_f0"
|
||||
@change="applyFilterDesign"
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-3 text-base text-slate-900 dark:text-gray-100">
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-2 text-base text-slate-900 dark:text-gray-100">
|
||||
</div>
|
||||
<div><label class="text-sm text-slate-500 dark:text-gray-400 mb-2 block">品質因數
|
||||
<div><label class="text-sm text-slate-500 dark:text-gray-400 mb-1 block">品質因數
|
||||
Q</label><input type="number" v-model.number="params.notch_q"
|
||||
@change="applyFilterDesign" step="0.1"
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-3 text-base text-slate-900 dark:text-gray-100">
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded p-2 text-base text-slate-900 dark:text-gray-100">
|
||||
</div>
|
||||
</div>
|
||||
<!-- 1P1Z -->
|
||||
@@ -247,7 +247,7 @@
|
||||
|
||||
<!-- 系統參數 -->
|
||||
<section>
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<h2 class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-2 flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z">
|
||||
@@ -263,7 +263,7 @@
|
||||
(Hz)</span><span class="role-text-primary">{{ formatK(fs)
|
||||
}}</span></label>
|
||||
<input type="number" v-model.number="fs" @input="debouncedApply" @keydown="onlyNumberKey"
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-slate-500 dark:text-gray-400 flex justify-between mb-1"><span>系統增益
|
||||
@@ -271,7 +271,7 @@
|
||||
}}x</span></label>
|
||||
<input type="number" v-model.number="systemGain" @input="onGainChange" step="0.1"
|
||||
@keydown="onlyNumberKey"
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
class="w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -281,7 +281,7 @@
|
||||
<!-- 係數手動設定 -->
|
||||
<section class="coeff-section">
|
||||
<h2
|
||||
class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-3 flex justify-between items-center">
|
||||
class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-2 flex justify-between items-center">
|
||||
<span class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
@@ -306,7 +306,7 @@
|
||||
</div>
|
||||
<textarea v-model="b_str" @input="debouncedUpdateFromText('b')" @keydown="onlyNumberKey"
|
||||
rows="2"
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-primary"></textarea>
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-primary"></textarea>
|
||||
</div>
|
||||
<!-- b 係數倍率微調 -->
|
||||
<details :open="activeCoeffAdjustment === 'bScale'"
|
||||
@@ -315,11 +315,11 @@
|
||||
class="text-sm text-slate-500 dark:text-gray-400 px-3 py-3 cursor-pointer hover:text-slate-600 dark:hover:text-gray-300">
|
||||
b 係數倍率微調</summary>
|
||||
<div class="px-3 pb-3">
|
||||
<div class="selector-button-group flex items-center gap-2 mb-2 mt-1">
|
||||
<div class="selector-button-group flex items-center gap-1 mb-2 mt-1">
|
||||
<span class="text-xs text-slate-400 dark:text-gray-500">靈敏度:</span>
|
||||
<button v-for="s in ['1%','2x','10x']" :key="s" @click="sense_b = s"
|
||||
:class="sense_b === s ? 'primary-action role-bg-primary role-text-on-fill role-border-primary' : 'bg-slate-200 dark:bg-gray-800 text-slate-500 dark:text-gray-400'"
|
||||
class="selector-button btn-small px-3 py-1.5 rounded text-xs font-medium border border-slate-300 dark:border-gray-700">{{
|
||||
class="selector-button btn-small px-3 py-0.5 rounded text-[10px] font-medium border border-slate-300 dark:border-gray-700">{{
|
||||
s }}</button>
|
||||
</div>
|
||||
<template v-for="i in 7" :key="'bs'+i">
|
||||
@@ -350,7 +350,7 @@
|
||||
</div>
|
||||
<textarea v-model="a_str" @input="debouncedUpdateFromText('a')" @keydown="onlyNumberKey"
|
||||
rows="2"
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-secondary"></textarea>
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-secondary"></textarea>
|
||||
</div>
|
||||
<!-- a 係數倍率微調 -->
|
||||
<details :open="activeCoeffAdjustment === 'aScale'"
|
||||
@@ -363,7 +363,7 @@
|
||||
<span class="text-xs text-slate-400 dark:text-gray-500">靈敏度:</span>
|
||||
<button v-for="s in ['1%','2x','10x']" :key="s" @click="sense_a = s"
|
||||
:class="sense_a === s ? 'primary-action role-bg-primary role-text-on-fill role-border-primary' : 'bg-slate-200 dark:bg-gray-800 text-slate-500 dark:text-gray-400'"
|
||||
class="selector-button px-3 py-1.5 rounded text-xs font-medium border border-slate-300 dark:border-gray-700">{{
|
||||
class="selector-button px-3 py-0.5 rounded text-[10px] font-medium border border-slate-300 dark:border-gray-700">{{
|
||||
s }}</button>
|
||||
</div>
|
||||
<template v-for="i in 6" :key="'as'+i">
|
||||
@@ -391,24 +391,24 @@
|
||||
<div class="mb-4">
|
||||
<div class="grid grid-cols-[3.25rem_1fr_3.25rem] gap-2 mb-3">
|
||||
<button type="button" @click="adjustAFineStep(-1)" title="調粗"
|
||||
class="rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-slate-600 dark:text-gray-300 py-2 text-xs font-bold transition-colors hover:bg-slate-100 dark:hover:bg-gray-700">
|
||||
class="rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-slate-600 dark:text-gray-300 py-1 text-xs font-bold transition-colors hover:bg-slate-100 dark:hover:bg-gray-700">
|
||||
粗
|
||||
</button>
|
||||
<div class="min-w-0 rounded-lg border role-border-secondary-soft role-bg-secondary-soft px-3 py-2 text-center">
|
||||
<span class="block text-[9px] role-text-secondary-muted role-text-secondary font-bold">微調量</span>
|
||||
<span class="block truncate font-mono text-sm font-bold role-text-secondary" :title="formatAFineDelta()">
|
||||
+/-{{ formatAFineDelta() }}
|
||||
± {{ formatAFineDelta() }}
|
||||
</span>
|
||||
</div>
|
||||
<button type="button" @click="adjustAFineStep(1)" title="調細"
|
||||
class="rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-slate-600 dark:text-gray-300 py-2 text-xs font-bold transition-colors hover:bg-slate-100 dark:hover:bg-gray-700">
|
||||
class="rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-slate-600 dark:text-gray-300 py-1 text-xs font-bold transition-colors hover:bg-slate-100 dark:hover:bg-gray-700">
|
||||
細
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-[11px] font-semibold text-slate-400 dark:text-gray-500 mb-2">
|
||||
{{ isTouchInput ? '觸控拖曳微調' : '直接輸入 / shift + 滾輪微調' }}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex flex-col gap-3">
|
||||
<article v-if="!isTouchInput" class="block">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ</span>
|
||||
<div class="grid grid-cols-[2rem_1fr_2rem] gap-1">
|
||||
@@ -417,16 +417,17 @@
|
||||
<input type="number" :value="formatFineCoeff(currentDelta)" :step="aFineStep"
|
||||
@change="setAFineDirect('delta', $event.target.value)" @keydown="onlyNumberKey"
|
||||
@wheel="handleAFineWheel('delta', $event)"
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono text-center role-text-secondary">
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-xs font-mono text-center role-text-secondary">
|
||||
<button type="button" @pointerdown.prevent="startA1A2Repeat('delta', 1)" @pointerup="stopA1A2Repeat" @pointerleave="stopA1A2Repeat" @pointercancel="stopA1A2Repeat"
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">+</button>
|
||||
</div>
|
||||
</article>
|
||||
<button v-else type="button" @pointerdown.prevent="startAFineDrag('delta', $event)"
|
||||
class="touch-none select-none w-28 mx-auto bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center role-active-border-secondary active:ring-2 role-active-ring-secondary">
|
||||
class="touch-none select-none w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center role-active-border-secondary active:ring-2 role-active-ring-secondary">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ</span>
|
||||
<span class="block max-w-full truncate text-base font-mono font-bold role-text-secondary" :title="formatFineCoeff(currentDelta)">{{ formatFineCoeff(currentDelta) }}</span>
|
||||
</button>
|
||||
|
||||
<article v-if="!isTouchInput" class="block">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">r</span>
|
||||
<div class="grid grid-cols-[2rem_1fr_2rem] gap-1">
|
||||
@@ -435,13 +436,13 @@
|
||||
<input type="number" :value="formatFineCoeff(currentR)" :step="aFineStep"
|
||||
@change="setAFineDirect('r', $event.target.value)" @keydown="onlyNumberKey"
|
||||
@wheel="handleAFineWheel('r', $event)"
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono text-center text-slate-700 dark:text-gray-300">
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-xs font-mono text-center text-slate-700 dark:text-gray-300">
|
||||
<button type="button" @pointerdown.prevent="startA1A2Repeat('r', 1)" @pointerup="stopA1A2Repeat" @pointerleave="stopA1A2Repeat" @pointercancel="stopA1A2Repeat"
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">+</button>
|
||||
</div>
|
||||
</article>
|
||||
<button v-else type="button" @pointerdown.prevent="startAFineDrag('r', $event)"
|
||||
class="touch-none select-none w-28 mx-auto bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center active:border-slate-400 active:ring-2 active:ring-slate-400/20">
|
||||
class="touch-none select-none w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center active:border-slate-400 active:ring-2 active:ring-slate-400/20">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">r</span>
|
||||
<span class="block max-w-full truncate text-base font-mono font-bold text-slate-700 dark:text-gray-300" :title="formatFineCoeff(currentR)">{{ formatFineCoeff(currentR) }}</span>
|
||||
</button>
|
||||
@@ -450,27 +451,19 @@
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 mb-4">
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">a1 = -1 - r + δ</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary-muted" :title="formatFineCoeff(currentA1)">{{ formatFineCoeff(currentA1) }}</span>
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">r = (a2 - a1 - 1) / 2</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFineCoeff(currentR)">{{ formatFineCoeff(currentR) }}</span>
|
||||
</div>
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">a2 = r + δ</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary-muted" :title="formatFineCoeff(currentA2)">{{ formatFineCoeff(currentA2) }}</span>
|
||||
</div>
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">差值 a1-a2</span>
|
||||
<span class="block max-w-full truncate text-xs font-mono text-slate-600 dark:text-gray-300" :title="formatFineCoeff(currentA1A2Diff)">{{ formatFineCoeff(currentA1A2Diff) }}</span>
|
||||
</div>
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">總和 a1+a2</span>
|
||||
<span class="block max-w-full truncate text-xs font-mono text-slate-600 dark:text-gray-300" :title="formatFineCoeff(currentA1A2Sum)">{{ formatFineCoeff(currentA1A2Sum) }}</span>
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ = (a1 + a2 + 1) / 2</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFineCoeff(currentDelta)">{{ formatFineCoeff(currentDelta) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="role-bg-warning-soft border role-border-warning-soft rounded-lg p-3">
|
||||
<p class="text-[10px] role-text-warning leading-relaxed">
|
||||
<span class="font-bold">運算說明:</span><br>
|
||||
δ = (a1 + a2 + 1) / 2,r = (a2 - a1 - 1) / 2。更改數值會自動更新圖表;桌機可用 +/- 或 shift + 滾輪,觸控時按住數值面板上下拖曳。
|
||||
δ = (a1 + a2 + 1) / 2,r = (a2 - a1 - 1) / 2。更改數值會自動更新圖表;桌機可用 ± 或 shift + 滾輪,觸控時按住數值面板上下拖曳。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -482,7 +475,7 @@
|
||||
<!-- 定點數轉換 -->
|
||||
<section class="fixed-section">
|
||||
<h2
|
||||
class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-3 flex justify-between items-center">
|
||||
class="text-sm font-semibold text-slate-500 dark:text-gray-400 uppercase tracking-wider mb-2 flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
@@ -511,7 +504,7 @@
|
||||
<input type="number" :value="shiftBitsB" min="0"
|
||||
@change="setShiftBits('b', $event.target.value)" @keydown="onlyNumberKey"
|
||||
@wheel="handleShiftBitWheel('b', $event)"
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
<button type="button" @pointerdown.prevent="startShiftBitRepeat('b', 1)" @pointerup="stopShiftBitRepeat" @pointerleave="stopShiftBitRepeat" @pointercancel="stopShiftBitRepeat"
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">+</button>
|
||||
</div>
|
||||
@@ -530,7 +523,7 @@
|
||||
<input type="number" :value="shiftBitsA" min="0"
|
||||
@change="setShiftBits('a', $event.target.value)" @keydown="onlyNumberKey"
|
||||
@wheel="handleShiftBitWheel('a', $event)"
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-base role-focus-primary outline-none transition-all text-slate-900 dark:text-gray-100">
|
||||
<button type="button" @pointerdown.prevent="startShiftBitRepeat('a', 1)" @pointerup="stopShiftBitRepeat" @pointerleave="stopShiftBitRepeat" @pointercancel="stopShiftBitRepeat"
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">+</button>
|
||||
</div>
|
||||
@@ -557,7 +550,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<textarea v-model="b_int_str" @keydown="onlyNumberKey($event, false)" rows="2"
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-primary"></textarea>
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-primary"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-slate-500 dark:text-gray-400 mb-1 block">定點分母係數 a
|
||||
@@ -572,7 +565,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<textarea v-model="a_int_str" @keydown="onlyNumberKey($event, false)" rows="2"
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-secondary"></textarea>
|
||||
class="w-full bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-700 rounded-lg p-2 text-sm role-focus-primary outline-none font-mono resize-none leading-relaxed role-text-secondary"></textarea>
|
||||
</div>
|
||||
<details class="mt-3 bg-slate-50 dark:bg-gray-900 border border-slate-200 dark:border-gray-800 rounded-lg">
|
||||
<summary class="text-sm text-slate-500 dark:text-gray-400 px-3 py-3 cursor-pointer hover:text-slate-600 dark:hover:text-gray-300">
|
||||
@@ -584,7 +577,7 @@
|
||||
class="rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-slate-600 dark:text-gray-300 py-2 text-xs font-bold transition-colors hover:bg-slate-100 dark:hover:bg-gray-700">粗</button>
|
||||
<div class="min-w-0 rounded-lg border role-border-secondary-soft role-bg-secondary-soft px-3 py-2 text-center">
|
||||
<span class="block text-[9px] role-text-secondary-muted role-text-secondary font-bold">微調量</span>
|
||||
<span class="block truncate font-mono text-sm font-bold role-text-secondary" :title="formatFixedAFineDelta()">+/-{{ formatFixedAFineDelta() }}</span>
|
||||
<span class="block truncate font-mono text-sm font-bold role-text-secondary" :title="formatFixedAFineDelta()">± {{ formatFixedAFineDelta() }}</span>
|
||||
</div>
|
||||
<button type="button" @click="adjustFixedAFineStep(1)" title="調細"
|
||||
class="rounded-lg border border-slate-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-slate-600 dark:text-gray-300 py-2 text-xs font-bold transition-colors hover:bg-slate-100 dark:hover:bg-gray-700">細</button>
|
||||
@@ -592,7 +585,7 @@
|
||||
<div class="text-[11px] font-semibold text-slate-400 dark:text-gray-500 mb-2">
|
||||
{{ isTouchInput ? '觸控拖曳微調' : '直接輸入 / shift + 滾輪微調' }}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex flex-col gap-3">
|
||||
<article v-if="!isTouchInput" class="block">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ</span>
|
||||
<div class="grid grid-cols-[2rem_1fr_2rem] gap-1">
|
||||
@@ -607,25 +600,26 @@
|
||||
</div>
|
||||
</article>
|
||||
<button v-else type="button" @pointerdown.prevent="startFixedAFineDrag('delta', $event)"
|
||||
class="touch-none select-none w-28 mx-auto bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center role-active-border-secondary active:ring-2 role-active-ring-secondary">
|
||||
class="touch-none select-none w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center role-active-border-secondary active:ring-2 role-active-ring-secondary">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ</span>
|
||||
<span class="block max-w-full truncate text-base font-mono font-bold role-text-secondary" :title="formatFixedFineCoeff(fixedCurrentDelta)">{{ formatFixedFineCoeff(fixedCurrentDelta) }}</span>
|
||||
</button>
|
||||
<article v-if="!isTouchInput" class="block">
|
||||
|
||||
<article v-if="!isTouchInput" class="block" :class="{'opacity-40 pointer-events-none': fixedAFineStep === 0.5}">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">r</span>
|
||||
<div class="grid grid-cols-[2rem_1fr_2rem] gap-1">
|
||||
<button type="button" @pointerdown.prevent="startFixedA1A2Repeat('r', -1)" @pointerup="stopFixedA1A2Repeat" @pointerleave="stopFixedA1A2Repeat" @pointercancel="stopFixedA1A2Repeat"
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">-</button>
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm" :disabled="fixedAFineStep === 0.5">-</button>
|
||||
<input type="number" :value="formatFixedFineCoeff(fixedCurrentR)" :step="fixedAFineStep"
|
||||
@change="setFixedAFineDirect('r', $event.target.value)" @keydown="onlyNumberKey"
|
||||
@wheel="handleFixedAFineWheel('r', $event)"
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono text-center text-slate-700 dark:text-gray-300">
|
||||
class="min-w-0 w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-xs font-mono text-center text-slate-700 dark:text-gray-300" :disabled="fixedAFineStep === 0.5">
|
||||
<button type="button" @pointerdown.prevent="startFixedA1A2Repeat('r', 1)" @pointerup="stopFixedA1A2Repeat" @pointerleave="stopFixedA1A2Repeat" @pointercancel="stopFixedA1A2Repeat"
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm">+</button>
|
||||
class="stepper-button w-full h-[42px] flex items-center justify-center rounded-full role-bg-primary-soft role-hover-primary-soft role-text-primary transition-all duration-150 active:scale-90 text-lg font-bold shadow-sm" :disabled="fixedAFineStep === 0.5">+</button>
|
||||
</div>
|
||||
</article>
|
||||
<button v-else type="button" @pointerdown.prevent="startFixedAFineDrag('r', $event)"
|
||||
class="touch-none select-none w-28 mx-auto bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center active:border-slate-400 active:ring-2 active:ring-slate-400/20">
|
||||
class="touch-none select-none w-full bg-white dark:bg-gray-800 border border-slate-200 dark:border-gray-700 rounded-lg p-3 text-center active:border-slate-400 active:ring-2 active:ring-slate-400/20" :class="{'opacity-40 pointer-events-none': fixedAFineStep === 0.5}">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">r</span>
|
||||
<span class="block max-w-full truncate text-base font-mono font-bold text-slate-700 dark:text-gray-300" :title="formatFixedFineCoeff(fixedCurrentR)">{{ formatFixedFineCoeff(fixedCurrentR) }}</span>
|
||||
</button>
|
||||
@@ -633,20 +627,12 @@
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 mb-4">
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">a1 = -a0 - r + δ</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary-muted" :title="formatFixedFineCoeff(fixedCurrentA1)">{{ formatFixedFineCoeff(fixedCurrentA1) }}</span>
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">r = (a2 - a1 - a0) / 2</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFixedFineCoeff(fixedCurrentR)">{{ formatFixedFineCoeff(fixedCurrentR) }}</span>
|
||||
</div>
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">a2 = r + δ</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary-muted" :title="formatFixedFineCoeff(fixedCurrentA2)">{{ formatFixedFineCoeff(fixedCurrentA2) }}</span>
|
||||
</div>
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">差值 a1-a2</span>
|
||||
<span class="block max-w-full truncate text-xs font-mono text-slate-600 dark:text-gray-300" :title="formatFixedFineCoeff(fixedCurrentA1A2Diff)">{{ formatFixedFineCoeff(fixedCurrentA1A2Diff) }}</span>
|
||||
</div>
|
||||
<div class="min-w-0 bg-white dark:bg-gray-800 border border-slate-100 dark:border-gray-700 rounded-lg p-2 text-center shadow-sm">
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">總和 a1+a2</span>
|
||||
<span class="block max-w-full truncate text-xs font-mono text-slate-600 dark:text-gray-300" :title="formatFixedFineCoeff(fixedCurrentA1A2Sum)">{{ formatFixedFineCoeff(fixedCurrentA1A2Sum) }}</span>
|
||||
<span class="text-[9px] text-slate-400 block font-bold mb-1">δ = (a1 + a2 + a0) / 2</span>
|
||||
<span class="block max-w-full truncate text-sm font-mono font-bold role-text-secondary" :title="formatFixedFineCoeff(fixedCurrentDelta)">{{ formatFixedFineCoeff(fixedCurrentDelta) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-bg-warning-soft border role-border-warning-soft rounded-lg p-3">
|
||||
|
||||
+103
-15
@@ -58,7 +58,7 @@ export default {
|
||||
shiftBitRepeatDelayTimer: null,
|
||||
shiftBitRepeatTimer: null,
|
||||
shiftBitDrag: null,
|
||||
fixedAFineStep: 1,
|
||||
fixedAFineStep: 0.5,
|
||||
fixedAFineDrag: null,
|
||||
fixedAFineRepeatDelayTimer: null,
|
||||
fixedAFineRepeatTimer: null,
|
||||
@@ -69,6 +69,7 @@ export default {
|
||||
webSerialSupported: false,
|
||||
writingMCU: false,
|
||||
mcuStatus: '',
|
||||
bodeMagRange: null, // [min, max] for Y-axis
|
||||
}
|
||||
},
|
||||
|
||||
@@ -218,6 +219,7 @@ export default {
|
||||
if (!this.webSerialSupported) {
|
||||
this.mcuStatus = '此瀏覽器不支援 Web Serial(需使用 Chrome 或 Edge,且透過 HTTPS 連線)';
|
||||
}
|
||||
this.updateBodeMagRange();
|
||||
this.applyFilterDesign();
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -335,8 +337,22 @@ export default {
|
||||
this.aFineStep = Number(clampedStep.toPrecision(12));
|
||||
},
|
||||
adjustFixedAFineStep(direction) {
|
||||
const nextStep = this.fixedAFineStep * (direction > 0 ? 0.1 : 10);
|
||||
this.fixedAFineStep = Math.max(1, Math.min(1000000000, Math.round(nextStep)));
|
||||
const steps = [0.5, 1, 2, 4, 8, 32, 256, 1024, 4096, 16384, 65536];
|
||||
let currentIdx = steps.indexOf(this.fixedAFineStep);
|
||||
|
||||
// 如果當前值不在數列中,找最接近的一個
|
||||
if (currentIdx === -1) {
|
||||
currentIdx = steps.reduce((prevIdx, curr, idx) => {
|
||||
return Math.abs(curr - this.fixedAFineStep) < Math.abs(steps[prevIdx] - this.fixedAFineStep) ? idx : prevIdx;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (direction > 0) { // 細 -> 往小調
|
||||
currentIdx = Math.max(0, currentIdx - 1);
|
||||
} else { // 粗 -> 往大調
|
||||
currentIdx = Math.min(steps.length - 1, currentIdx + 1);
|
||||
}
|
||||
this.fixedAFineStep = steps[currentIdx];
|
||||
},
|
||||
setDeltaR(delta, r) {
|
||||
const nextDelta = Number(delta);
|
||||
@@ -397,11 +413,20 @@ export default {
|
||||
window.removeEventListener('pointercancel', this.stopAFineDrag);
|
||||
},
|
||||
setFixedDeltaR(delta, r) {
|
||||
const nextDelta = Number(delta);
|
||||
const nextR = Number(r);
|
||||
if (!Number.isFinite(nextDelta) || !Number.isFinite(nextR)) return;
|
||||
this.fixedOverrides.a[1] = Math.round(-this.fixedA0Int - nextR + nextDelta);
|
||||
this.fixedOverrides.a[2] = Math.round(nextR + nextDelta);
|
||||
// 使用「兩倍值」空間進行整數運算
|
||||
let S = Math.round(delta * 2);
|
||||
let D = Math.round(r * 2);
|
||||
const a0 = this.fixedA0Int;
|
||||
|
||||
// 核心邏輯:a1, a2 為整數的充要條件是 S 與 D 的奇偶性相同
|
||||
// 這裡採用「優先滿足目標值」的策略:如果奇偶不同,則微調其中一個
|
||||
// 由於此函數由 UI 直接調用,我們無法判斷使用者剛才改了誰
|
||||
// 所以我們預設採用 Math.round 來尋找最接近的整數組合
|
||||
const a2 = Math.round((S + D) / 2);
|
||||
const a1 = a2 - D - a0;
|
||||
|
||||
this.fixedOverrides.a[1] = a1;
|
||||
this.fixedOverrides.a[2] = a2;
|
||||
this.debouncedUpdateBode();
|
||||
},
|
||||
setFixedAFineDirect(target, rawValue) {
|
||||
@@ -414,12 +439,40 @@ export default {
|
||||
}
|
||||
},
|
||||
adjustFixedAFineValue(target, steps) {
|
||||
const delta = steps * this.fixedAFineStep;
|
||||
let S = Math.round(this.fixedCurrentDelta * 2);
|
||||
let D = Math.round(this.fixedCurrentR * 2);
|
||||
const dVal = Math.round(steps * this.fixedAFineStep * 2);
|
||||
const a0 = this.fixedA0Int;
|
||||
|
||||
if (target === 'delta') {
|
||||
this.setFixedDeltaR(this.fixedCurrentDelta + delta, this.fixedCurrentR);
|
||||
// 優先保證 delta 變動到位
|
||||
S += dVal;
|
||||
// 檢查奇偶性是否匹配
|
||||
if ((S % 2 + 2) % 2 !== (D % 2 + 2) % 2) {
|
||||
// 核心:震盪平衡邏輯。
|
||||
// 如果 S 變為偶數,D 應該變為最近的偶數;如果 S 變為奇數,D 應該變為最近的奇數。
|
||||
// 我們判斷目前 D 是在 .5 還是 .0,然後向反方向跳轉,防止單向漂移。
|
||||
if (D % 2 !== 0) {
|
||||
D -= 1; // 10.5 -> 10.0
|
||||
} else {
|
||||
D += 1; // 10.0 -> 10.5
|
||||
}
|
||||
}
|
||||
} else if (target === 'r') {
|
||||
this.setFixedDeltaR(this.fixedCurrentDelta, this.fixedCurrentR + delta);
|
||||
// 如果 r 沒被禁用,則優先保證 r 變動到位
|
||||
D += dVal;
|
||||
if ((S % 2 + 2) % 2 !== (D % 2 + 2) % 2) {
|
||||
if (S % 2 !== 0) S -= 1;
|
||||
else S += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const a2 = (S + D) / 2;
|
||||
const a1 = a2 - D - a0;
|
||||
|
||||
this.fixedOverrides.a[1] = a1;
|
||||
this.fixedOverrides.a[2] = a2;
|
||||
this.debouncedUpdateBode();
|
||||
},
|
||||
handleFixedAFineWheel(target, event) {
|
||||
if (!event.shiftKey) {
|
||||
@@ -664,8 +717,14 @@ export default {
|
||||
this.bodeTimeout = setTimeout(() => this.updateBodePlot({ switchToChart: false }), 150);
|
||||
},
|
||||
onGainChange() {
|
||||
this.updateBodeMagRange();
|
||||
this.updateFromControls();
|
||||
},
|
||||
updateBodeMagRange() {
|
||||
const K = parseFloat(this.systemGain) || 1.0;
|
||||
const yMax = 20 * Math.log10(Math.abs(K) * 3);
|
||||
this.bodeMagRange = [yMax - 70, yMax];
|
||||
},
|
||||
onFilterTypeChange() {
|
||||
this.systemGain = 1.0;
|
||||
// 這裡可以選擇是否重設所有設計參數 (fc, Q 等)
|
||||
@@ -679,6 +738,7 @@ export default {
|
||||
if (this.filterType !== MANUAL_FILTER_TYPE) {
|
||||
this.params = cloneDefaultParams();
|
||||
}
|
||||
this.updateBodeMagRange();
|
||||
this.applyFilterDesign();
|
||||
},
|
||||
resetFilterParams() {
|
||||
@@ -712,6 +772,7 @@ export default {
|
||||
this.stopShiftBitDrag();
|
||||
this.activeCoeffAdjustment = null;
|
||||
this.globalError = null;
|
||||
this.updateBodeMagRange();
|
||||
if (isManual) this.updateBodePlot({ switchToChart: false });
|
||||
else this.applyFilterDesign();
|
||||
},
|
||||
@@ -894,6 +955,7 @@ export default {
|
||||
|
||||
const layout = {
|
||||
paper_bgcolor: plotBgColor, plot_bgcolor: plotBgColor,
|
||||
uirevision: this.bodeMagRange ? this.bodeMagRange.join(',') : 'true', // 當範圍不變時,保留使用者縮放
|
||||
margin: isSmallScreen ? { t: 80, b: 120, l: 55, r: 15 } : { t: 70, b: 120, l: 70, r: 30 },
|
||||
showlegend: false,
|
||||
grid: { rows: 2, columns: 1, pattern: 'independent', roworder: 'top to bottom', ygap: isSmallScreen ? 0.35 : 0.3 },
|
||||
@@ -916,10 +978,36 @@ export default {
|
||||
|
||||
...annotations
|
||||
],
|
||||
xaxis: { ...xAxisCommon },
|
||||
yaxis: { title: { text: 'Mag (dB)', font: { size: isSmallScreen ? 12 : 14 } }, range: [-60, 0], gridcolor: gridColor, zerolinecolor: zeroLineColor, tickfont: { color: textColor, size: isSmallScreen ? 11 : 12 } },
|
||||
xaxis2: { ...xAxisCommon },
|
||||
yaxis2: { title: { text: 'Phase (°)', font: { size: isSmallScreen ? 12 : 14 } }, range: [-180, 180], tickvals: [-180, -90, 0, 90, 180], gridcolor: gridColor, zerolinecolor: zeroLineColor, tickfont: { color: textColor, size: isSmallScreen ? 11 : 12 } },
|
||||
xaxis: { ...xAxisCommon, anchor: 'y' },
|
||||
yaxis: {
|
||||
title: { text: 'Mag (dB)', font: { size: isSmallScreen ? 12 : 14 } },
|
||||
range: this.bodeMagRange || [-70, 0],
|
||||
gridcolor: gridColor,
|
||||
zerolinecolor: zeroLineColor,
|
||||
tickfont: { color: textColor, size: isSmallScreen ? 11 : 12 },
|
||||
fixedrange: false // 允許使用者手動縮放或點擊 Auto Scale
|
||||
},
|
||||
xaxis2: {
|
||||
type: 'log',
|
||||
title: { text: 'Freq (Hz)', font: { size: isSmallScreen ? 12 : 14 } },
|
||||
tickvals: xTicks,
|
||||
ticktext: xTexts,
|
||||
showgrid: true,
|
||||
gridcolor: gridColor,
|
||||
tickfont: { color: textColor, size: isSmallScreen ? 11 : 12 },
|
||||
nticks: isSmallScreen ? 6 : 10,
|
||||
matches: 'x',
|
||||
anchor: 'y2'
|
||||
},
|
||||
yaxis2: {
|
||||
title: { text: 'Phase (°)', font: { size: isSmallScreen ? 12 : 14 } },
|
||||
range: [-180, 180],
|
||||
tickvals: [-180, -90, 0, 90, 180],
|
||||
gridcolor: gridColor,
|
||||
zerolinecolor: zeroLineColor,
|
||||
tickfont: { color: textColor, size: isSmallScreen ? 11 : 12 },
|
||||
fixedrange: true // 相位縱軸鎖死,不可縮放
|
||||
},
|
||||
shapes: shapes
|
||||
};
|
||||
|
||||
|
||||
+25
-5
@@ -238,7 +238,7 @@ aside section {
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
aside.lg\:w-96 {
|
||||
width: 28rem !important;
|
||||
width: 21rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,7 +343,6 @@ select {
|
||||
background-size: 0.38rem 0.38rem, 0.38rem 0.38rem, 1px 55% !important;
|
||||
background-repeat: no-repeat !important;
|
||||
border-radius: 999px !important;
|
||||
min-height: 48px;
|
||||
padding-right: 3.25rem !important;
|
||||
}
|
||||
|
||||
@@ -383,7 +382,6 @@ button,
|
||||
[role="button"],
|
||||
label.cursor-pointer {
|
||||
border-color: transparent !important;
|
||||
border-radius: 999px !important;
|
||||
box-shadow: none !important;
|
||||
font-weight: 500;
|
||||
transition: background-color 140ms ease, color 140ms ease, box-shadow 140ms ease, transform 120ms ease;
|
||||
@@ -1640,9 +1638,31 @@ button.touch-none:hover, details button.touch-none:hover {
|
||||
background: var(--m3-background) !important;
|
||||
}
|
||||
|
||||
#app aside section,
|
||||
.dark #app aside section {
|
||||
#app aside section {
|
||||
background: transparent !important;
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important; /* 縮小區塊內縮 */
|
||||
}
|
||||
|
||||
/* 強制壓縮所有選擇按鈕 */
|
||||
#app .selector-button,
|
||||
#app button.touch-none,
|
||||
#app .stepper-button {
|
||||
padding-top: 2px !important;
|
||||
padding-bottom: 2px !important;
|
||||
min-height: 0 !important;
|
||||
height: auto !important;
|
||||
font-size: 12px !important;
|
||||
border-radius: 8px !important; /* 減少圓角讓視覺更緊湊 */
|
||||
}
|
||||
|
||||
/* 縮小所有輸入框 */
|
||||
#app input[type="number"],
|
||||
#app select,
|
||||
#app textarea {
|
||||
padding-top: 4px !important;
|
||||
padding-bottom: 4px !important;
|
||||
min-height: 0 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,15 @@ import vue from '@vitejs/plugin-vue'
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://127.0.0.1:8000',
|
||||
changeOrigin: true,
|
||||
secure: false, // 允許自簽憑證
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: 'static/build',
|
||||
emptyOutDir: true,
|
||||
|
||||
Reference in New Issue
Block a user