1 Commits

Author SHA1 Message Date
9ada32765a vuetify
All checks were successful
/ lint-build-and-check (push) Successful in 5m44s
/ surge (push) Successful in 3m8s
/ playwright (push) Successful in 2m46s
2025-04-20 22:42:41 +08:00
44 changed files with 3157 additions and 7953 deletions

1
.gitattributes vendored
View File

@ -39,7 +39,6 @@ commit-msg text eol=lf
*.woff2 binary
*.eot binary
*.otf binary
*.spline binary
# Add more binary...

View File

@ -1,21 +0,0 @@
### .gitattributes
如果你先推送了一个.jpg文件然后再推送包含.gitattributes文件的更新Git不会自动重新处理之前的.jpg文件的属性。为使.gitattributes中的新规则生效你可以通过以下步骤重新应用设置
```bash
1. 删除本地缓存的.jpg文件git rm --cached <file>.jpg
2. 重新添加该文件git add <file>.jpg
3. 再次提交并推送git commit -m "Apply .gitattributes changes" && git push
```
这样Git将按照.gitattributes中的新规则处理该文件。
要验证.gitattributes文件中的二进制配置是否生效可以使用以下方法
```bash
1. 推送后验证:先按照之前步骤重新添加并推送.jpg文件。
2. 查看差异diff执行 git diff检查文件是否有文本形式的改动。如果是二进制文件Git不会显示具体内容的差异。
3. Git日志验证执行 git log -p <file>.jpg 查看提交的改动记录,确认文件未受行尾或编码处理的影响。
```
如果以上测试显示该文件未发生不必要的改动,说明.gitattributes配置已生效。

3
.github/copilot-instructions_MD vendored Normal file
View File

@ -0,0 +1,3 @@
```bash
ln -s .github/copilot-instructions.md .roorules
```

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 🛠️ 设置Node环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
- name: 🔍 静态代码分析
run: pnpm run lint
- name: 📦 构建项目

View File

@ -7,13 +7,6 @@ env:
on:
push:
workflow_dispatch:
inputs:
run_cleanup:
description: '是否运行 Surge 清理 Job'
required: false
type: boolean
default: true
jobs:
surge:
@ -22,7 +15,7 @@ jobs:
url: ${{ steps.surge_deploy.outputs.url }}
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
- name: 🔨 构建项目
run: pnpm run build-only
env:
@ -30,28 +23,22 @@ jobs:
- name: 🚀 部署到 Surge
id: surge_deploy
if: ${{ github.actor != 'nektos/act' }} # https://nektosact.com/usage/index.html#skipping-steps
uses: yanhao98/composite-actions/deploy-dist-to-surge@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/deploy-dist-to-surge@4470aa136359d05ae3b086d37cfaa33305448a5b
playwright:
needs: surge
runs-on: ubuntu-latest
container: mcr.microsoft.com/playwright:v1.53.2-noble
container: mcr.microsoft.com/playwright:v1.51.1-noble
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
# - name: 📥 安装 Playwright 浏览器
# run: pnpm exec playwright install --with-deps
- name: ▶️ 运行 Playwright 测试
run: npx playwright test
env:
BASE_URL: ${{ needs.surge.outputs.url }}
cleanup_surge:
runs-on: ubuntu-latest
needs: [surge, playwright]
if: github.event_name == 'push' || github.event.inputs.run_cleanup == true
steps:
- name: 🧹 清理 Surge 部署
run: npx surge teardown ${{ needs.surge.outputs.url }} --token ${{ env.SURGE_TOKEN }}
run: npx surge teardown ${{ needs.surge.outputs.url }} --token ${{ env.SURGE_TOKEN}}
env:
SURGE_TOKEN: d843de16b331c626f10771245c56ed93

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: ⚙️ 设置 Node 环境
uses: yanhao98/composite-actions/setup-node-environment@3bf07469124a5a7b9a06b6d07be36a116c5aa49e
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
- name: 📥 拉取 Vercel 环境信息
run: pnpm dlx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}

View File

@ -1,6 +1,5 @@
# 此钩子在 pre-commit 钩子成功完成后,用于检查提交消息。
echo "📝 [Commit-msg] 正在运行 commit-msg 钩子..."
echo "当前提交的文件是:$1"
# 在这里添加你的 commit message 验证逻辑,例如 commitlint
# npx --no -- commitlint --edit "$1"
pnpm exec commitlint --edit $1
echo "✅ [Commit-msg] commit-msg 钩子完成!"

2
.npmrc
View File

@ -5,7 +5,7 @@ registry=https://registry.npmjs.org/
# registry=https://nexus.oo1.dev/repository/npm/
# https://pnpm.io/zh/npmrc#node-mirrorltreleasedir
use-node-version=22.15.0
use-node-version=22.14.0
node-mirror:release=https://npmmirror.com/mirrors/node/ # pnpm config set node-mirror:release=https://npmmirror.com/mirrors/node/
node-mirror:rc=https://npmmirror.com/mirrors/node-rc/
node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/

View File

@ -27,7 +27,8 @@
"source.fixAll.eslint": "never",
"source.fixAll.stylelint": "never",
"source.fixAll.oxc": "never",
"source.organizeImports": "never"
"source.organizeImports": "never",
"source.fixAll": "never"
},
"editor.formatOnSave": false,
"oxc.enable": true,

View File

@ -15,9 +15,33 @@ pnpm install --registry=https://nexus.oo1.dev/repository/npm
pnpm run dev
```
## Tips
### .gitattributes
如果你先推送了一个.jpg文件然后再推送包含.gitattributes文件的更新Git不会自动重新处理之前的.jpg文件的属性。为使.gitattributes中的新规则生效你可以通过以下步骤重新应用设置
```bash
1. 删除本地缓存的.jpg文件git rm --cached <file>.jpg
2. 重新添加该文件git add <file>.jpg
3. 再次提交并推送git commit -m "Apply .gitattributes changes" && git push
```
这样Git将按照.gitattributes中的新规则处理该文件。
要验证.gitattributes文件中的二进制配置是否生效可以使用以下方法
```bash
1. 推送后验证:先按照之前步骤重新添加并推送.jpg文件。
2. 查看差异diff执行 git diff检查文件是否有文本形式的改动。如果是二进制文件Git不会显示具体内容的差异。
3. Git日志验证执行 git log -p <file>.jpg 查看提交的改动记录,确认文件未受行尾或编码处理的影响。
```
如果以上测试显示该文件未发生不必要的改动,说明.gitattributes配置已生效。
## Links
- https://github.com/hyoban-template/shadcn-vue-unocss-starter
- https://github.com/hyoban-template/shadcn-vue-unocss-starter$0
- [Performance API优化页面性能](https://juejin.cn/post/7238779568478552122)
- [vitepress-theme-demoblock](https://www.npmjs.com/package/vitepress-theme-demoblock)
- [Vite PWA](https://vite-pwa-org-zh.netlify.app/guide/)
@ -29,12 +53,10 @@ pnpm run dev
---
- [如何建立一个最小重现](https://antfu.me/posts/why-reproductions-are-required-zh#如何建立一个最小重现)
- https://biomejs.dev/zh-cn/internals/language-support/
- https://github.dev/antfu-collective/vitesse/
- [Vue3 入门指南与实战案例](https://vue3.chengpeiquan.com/)
- [如何建立一个最小重现](https://antfu.me/posts/why-reproductions-are-required-zh#如何建立一个最小重现)
---
- [primevue-scopedtokens](https://primevue.org/theming/styled/#scopedtokens)
+ https://www.npmjs.com/package/npkill

View File

@ -1,9 +0,0 @@
import type { UserConfig } from '@commitlint/types';
const Configuration: UserConfig = {
extends: ['@commitlint/config-conventional'],
formatter: '@commitlint/format',
};
export default Configuration;

View File

@ -84,8 +84,6 @@ export default defineConfigWithVueTs(
'import-x/newline-after-import': 'error',
'import-x/first': 'error',
'import-x/no-named-as-default': 'off',
'import-x/no-duplicates': 'off',
'import-x/named': 'off',
},
},
// endregion <<< eslint-plugin-import-x <<<
@ -101,8 +99,6 @@ export default defineConfigWithVueTs(
'perfectionist/sort-imports': ['error'],
'perfectionist/sort-modules': 'off',
'perfectionist/sort-object-types': 'off',
'perfectionist/sort-enums': 'off',
'perfectionist/sort-union-types': 'off',
},
},
// endregion <<< eslint-plugin-perfectionist <<<

View File

@ -1,5 +1,5 @@
{
"packageManager": "pnpm@10.12.4",
"packageManager": "pnpm@10.8.0",
"name": "vue-ts-example",
"version": "0.0.0",
"private": true,
@ -44,121 +44,121 @@
}
},
"dependencies": {
"@alova/adapter-axios": "^2.0.16",
"@alova/adapter-axios": "^2.0.13",
"@formkit/auto-animate": "^0.8.2",
"@intlify/unplugin-vue-i18n": "^6.0.8",
"@pinia/colada": "^0.17.1",
"@primeuix/themes": "^1.1.2",
"@splinetool/runtime": "^1.10.18",
"@intlify/unplugin-vue-i18n": "^6.0.6",
"@pinia/colada": "^0.14.2",
"@primeuix/themes": "^1.0.3",
"@splinetool/runtime": "^1.9.82",
"@types/p5": "^1.7.6",
"@types/sortablejs": "^1.15.8",
"@unhead/vue": "^2.0.11",
"@unhead/vue": "^2.0.5",
"@vant/use": "^1.6.0",
"@vueuse/core": "^13.4.0",
"alova": "^3.3.4",
"@vueuse/core": "^13.1.0",
"alova": "^3.2.10",
"ant-design-vue": "~4.2.6",
"axios": "^1.10.0",
"cesium": "^1.130.1",
"axios": "^1.8.4",
"cesium": "^1.128.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"consola": "^3.4.2",
"dayjs": "^1.11.13",
"deep-freeze-es6": "^4.0.1",
"deep-freeze-es6": "^4.0.0",
"jsencrypt": "^3.3.2",
"lucide-vue-next": "^0.525.0",
"lucide-vue-next": "^0.487.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"p5": "^2.0.3",
"p5": "^1.11.3",
"page-stack-vue3": "^2.5.6",
"pinia": "^3.0.3",
"pinia-plugin-persistedstate": "^4.4.1",
"pinia": "^3.0.1",
"pinia-plugin-persistedstate": "^4.2.0",
"plotly.js-dist-min": "^3.0.1",
"primeicons": "^7.0.0",
"primelocale": "^2.1.4",
"primevue": "^4.3.5",
"radash": "^12.1.1",
"primelocale": "^2.1.2",
"primevue": "^4.3.3",
"radash": "^12.1.0",
"radix-vue": "^1.9.17",
"reka-ui": "^2.3.1",
"reka-ui": "^2.2.0",
"satellite.js": "^6.0.0",
"sortablejs": "^1.15.6",
"tailwind-merge": "^3.3.1",
"tdesign-icons-vue-next": "^0.3.6",
"three": "^0.178.0",
"tailwind-merge": "^3.2.0",
"tdesign-icons-vue-next": "^0.3.5",
"three": "^0.175.0",
"ts-enum-util": "^4.1.0",
"utils4u": "^4.2.3",
"vant": "^4.9.20",
"vue": "^3.5.17",
"vue-data-ui": "^2.12.7",
"vant": "^4.9.18",
"vue": "^3.5.13",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "^11.1.7",
"vue-i18n": "^11.1.3",
"vue-page-stack": "^3.2.0",
"vue-router": "^4.5.1"
"vue-router": "^4.5.0",
"vuetify": "^3.8.2"
},
"devDependencies": {
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@eslint/compat": "^1.3.1",
"@faker-js/faker": "^9.8.0",
"@iconify-json/carbon": "^1.2.10",
"@eslint/compat": "^1.2.8",
"@faker-js/faker": "^9.6.0",
"@iconify-json/carbon": "^1.2.8",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/mdi": "^1.2.3",
"@iconify/utils": "^2.3.0",
"@playwright/test": "^1.53.2",
"@primevue/auto-import-resolver": "^4.3.5",
"@tsconfig/node22": "^22.0.2",
"@playwright/test": "^1.51.1",
"@primevue/auto-import-resolver": "^4.3.3",
"@tsconfig/node22": "^22.0.1",
"@types/archiver": "^6.0.3",
"@types/mockjs": "^1.0.10",
"@types/node": "^22.15.34",
"@types/node": "^22.14.0",
"@types/nprogress": "^0.2.3",
"@types/plotly.js-dist-min": "^2.3.4",
"@types/three": "^0.177.0",
"@types/three": "^0.175.0",
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vitejs/plugin-vue-jsx": "^5.0.1",
"@vitest/eslint-plugin": "^1.3.3",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"@vitest/eslint-plugin": "^1.1.40",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.1",
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.7.0",
"archiver": "^7.0.1",
"depcheck": "^1.4.7",
"eruda": "^3.4.3",
"eslint": "^9.30.0",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-oxlint": "^1.4.0",
"eslint-plugin-perfectionist": "^4.15.0",
"eslint-plugin-unicorn": "^59.0.1",
"eslint-plugin-vue": "^10.2.0",
"eruda": "^3.4.1",
"eslint": "^9.24.0",
"eslint-plugin-import-x": "^4.10.2",
"eslint-plugin-oxlint": "^0.16.6",
"eslint-plugin-perfectionist": "^4.11.0",
"eslint-plugin-unicorn": "^58.0.0",
"eslint-plugin-vue": "^10.0.0",
"husky": "^9.1.7",
"less": "^4.3.0",
"lint-staged": "^16.1.2",
"lint-staged": "^15.5.0",
"mockjs": "^1.1.0",
"npm-run-all2": "^8.0.4",
"oxlint": "^1.4.0",
"prettier": "3.6.2",
"npm-run-all2": "^7.0.2",
"oxlint": "^0.16.6",
"prettier": "3.5.3",
"sass-embedded": "^1.86.3",
"terser": "^5.39.0",
"typescript": "~5.8.3",
"unocss": "66.3.2",
"unocss-preset-animations": "^1.2.1",
"unocss": "66.1.0-beta.10",
"unocss-preset-animations": "^1.1.1",
"unocss-preset-chinese": "^0.3.3",
"unocss-preset-shadcn": "^0.5.0",
"unplugin-auto-import": "^19.3.0",
"unplugin-auto-import": "^19.1.2",
"unplugin-icons": "^22.1.0",
"unplugin-vue-components": "^28.8.0",
"unplugin-vue-components": "^28.5.0",
"unplugin-vue-macros": "^2.14.5",
"unplugin-vue-markdown": "^29.1.0",
"unplugin-vue-router": "^0.14.0",
"vite": "^7.0.0",
"vite-plugin-checker": "^0.9.3",
"unplugin-vue-markdown": "^28.3.1",
"unplugin-vue-router": "^0.12.0",
"vite": "^6.2.6",
"vite-plugin-checker": "^0.9.1",
"vite-plugin-fake-server": "^2.2.0",
"vite-plugin-image-tools": "^2.0.2",
"vite-plugin-purgecss-updated-v5": "^1.2.6",
"vite-plugin-purgecss-updated-v5": "^1.2.4",
"vite-plugin-singlefile": "^2.2.0",
"vite-plugin-static-copy": "^3.1.0",
"vite-plugin-vue-devtools": "^7.7.7",
"vite-plugin-static-copy": "^2.3.1",
"vite-plugin-vue-devtools": "^7.7.2",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-plugin-vue-meta-layouts": "^0.5.1",
"vite-plugin-webfont-dl": "^3.10.5",
"vue-component-type-helpers": "^3.0.0",
"vue-tsc": "^3.0.0"
"vite-plugin-vuetify": "^2.1.1",
"vite-plugin-webfont-dl": "^3.10.4",
"vue-component-type-helpers": "^2.2.8",
"vue-tsc": "^2.2.8"
}
}

6622
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
export enum E_选项卡菜单 {
= '幅度单位列表',
= '幅度菜单列表',
= '跨度菜单列表',
= '频率单位列表',
= '频率菜单列表',
}
export enum E_幅度菜单列表的项目 {
RefLevel = 'Ref Level',
// Attenuation = 'Attenuation', // 如果衰减也需要类似交互,则取消注释
}
export enum E_幅度单位 {
dBm = 'dBm',
mV = 'mV',
uA = 'µA', // 使用 uA 表示微安
uV = 'µV', // 使用 uV 表示微伏
}
export enum E_跨度菜单列表的项目 {
FullSpan = 'Full Span',
LastSpan = 'Last Span',
ZeroSpan = 'Zero Span',
}
/* 值是在屏幕上显示的值。 */
export enum E_选项卡菜单Freq菜单列表的项目 {
CenterFreq = 'Center',
StartFreq = 'Start',
StopFreq = 'Stop',
// CFStep = 'CFStep',
// FreqOffset = 'FreqOffset',
// SingnalTrack = 'SingnalTrack',
// ScaleType = 'ScaleType',
}
export enum E_Freq单位 {
GHz = 'GHz',
Hz = 'Hz',
kHz = 'kHz',
MHz = 'MHz',
}
export enum E_数字键盘按键 {
Dot = '.',
Num0 = 0,
Num1 = 1,
Num2 = 2,
Num3 = 3,
Num4 = 4,
Num5 = 5,
Num6 = 6,
Num7 = 7,
Num8 = 8,
Num9 = 9,
PlusMinus = '+/-',
}

View File

@ -1,13 +0,0 @@
@font-face {
font-family: 'Helvetica Custom';
font-style: normal;
font-weight: 400; /* 400 通常代表 'normal' 或 'regular' */
src: url('fonts/Helvetica-Light-05.eot');
src:
url('fonts/Helvetica-Light-05.eot?#iefix') format('embedded-opentype'),
url('fonts/Helvetica-Light-05.woff2') format('woff2'),
url('fonts/Helvetica-Light-05.woff') format('woff'),
url('fonts/Helvetica-Light-05.ttf') format('truetype'),
url('fonts/Helvetica-Light-05.svg#Helvetica') format('svg');
font-display: swap; /* 推荐 */
}

View File

@ -1,805 +0,0 @@
<!-- 频谱仪 -->
<script setup lang="ts">
import { $enum } from 'ts-enum-util';
import {
E_Freq单位,
E_幅度单位,
E_幅度菜单列表的项目,
E_数字键盘按键,
E_跨度菜单列表的项目,
E_选项卡菜单,
E_选项卡菜单Freq菜单列表的项目,
} from './CONST';
import 设备照片 from './pin-pu-yi-bg.png';
const state = reactive({
选项卡当前显示: E_选项卡菜单.频率菜单列表,
频谱仪状态: {
频率输入状态: {
选中的频率菜单列表的项目: null as E_选项卡菜单Freq菜单列表的项目 | null,
输入的值临时输入字符串: null as null | string, // 用于暂存用户输入
},
幅度输入状态: {
选中的幅度菜单列表的项目: null as E_幅度菜单列表的项目 | null,
输入的值临时输入字符串: null as null | string, // 用于暂存用户输入
refLevel: {
value: 0,
unit: E_幅度单位.dBm,
},
// attenuation: { value: 0, unit: E_幅度单位.dB }, // 如果需要衰减值
},
center: {
value: 0,
unit: E_Freq单位.Hz,
},
start: {
value: 0,
unit: E_Freq单位.Hz,
},
stop: {
value: 0,
unit: E_Freq单位.Hz,
},
span: {
currentSpan: {
start: 0,
stop: 0,
unit: E_Freq单位.Hz,
},
lastSpan: {
start: 0,
stop: 0,
unit: E_Freq单位.Hz,
},
fullSpan: {
start: 0,
stop: 3,
unit: E_Freq单位.GHz,
},
zeroSpan: false,
},
},
});
const 选项卡标题 = computed(() =>
$enum.mapValue(state.选项卡当前显示).with({
[E_选项卡菜单.频率菜单列表]: 'Freq/Channel',
[E_选项卡菜单.频率单位列表]: '',
[E_选项卡菜单.跨度菜单列表]: 'Span',
[E_选项卡菜单.幅度菜单列表]: 'Amplitude',
[E_选项卡菜单.幅度单位列表]: '',
}),
);
function 执行点击屏幕右边的按钮(按钮序号: number) {
console.group('🔘 点击屏幕右边的按钮', {
按钮序号,
选项卡当前显示: state.选项卡当前显示,
});
$enum.visitValue(state.选项卡当前显示).with({
[E_选项卡菜单.频率菜单列表]: () => {
switch (按钮序号) {
case 1: {
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = E_选项卡菜单Freq菜单列表的项目.CenterFreq;
console.debug('设置 Freq 输入状态为 CenterFreq');
break;
}
case 2: {
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = E_选项卡菜单Freq菜单列表的项目.StartFreq;
console.debug('设置 Freq 输入状态为 StartFreq');
break;
}
case 3: {
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = E_选项卡菜单Freq菜单列表的项目.StopFreq;
console.debug('设置 Freq 输入状态为 StopFreq');
break;
}
default: {
console.debug('频率菜单列表 - 这个按键还没有实现');
break;
}
}
},
[E_选项卡菜单.频率单位列表]: () => {
let selectedUnit: E_Freq单位 | null = null;
switch (按钮序号) {
case 1: {
selectedUnit = E_Freq单位.GHz;
break;
}
case 2: {
selectedUnit = E_Freq单位.MHz;
break;
}
case 3: {
selectedUnit = E_Freq单位.kHz;
break;
}
case 4: {
selectedUnit = E_Freq单位.Hz;
break;
}
default: {
console.warn('无效的频率单位按钮序号:', 按钮序号);
console.groupEnd();
return;
}
}
const tempInputString = state.频谱仪状态.频率输入状态.输入的值临时输入字符串;
const selectedFreqItem = state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目;
// 必须有临时输入值和选中的 Freq 项目才能确认
if (!tempInputString || !selectedFreqItem) {
console.warn('频率输入未完成或未选择项目,无法确认单位。');
console.groupEnd();
return;
}
const inputValue = Number.parseFloat(tempInputString);
// 校验解析结果
if (Number.isNaN(inputValue)) {
console.warn('无法将临时输入解析为有效数字 (频率):', tempInputString);
console.groupEnd();
return;
}
console.debug('确认频率单位:', selectedUnit, '值为:', inputValue);
switch (selectedFreqItem) {
case E_选项卡菜单Freq菜单列表的项目.CenterFreq: {
state.频谱仪状态.center.value = inputValue;
state.频谱仪状态.center.unit = selectedUnit;
console.debug('更新 CenterFreq:', state.频谱仪状态.center);
break;
}
case E_选项卡菜单Freq菜单列表的项目.StartFreq: {
state.频谱仪状态.start.value = inputValue;
state.频谱仪状态.start.unit = selectedUnit;
console.debug('更新 StartFreq:', state.频谱仪状态.start);
break;
}
case E_选项卡菜单Freq菜单列表的项目.StopFreq: {
state.频谱仪状态.stop.value = inputValue;
state.频谱仪状态.stop.unit = selectedUnit;
console.debug('更新 StopFreq:', state.频谱仪状态.stop);
break;
}
}
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 = null;
state.频谱仪状态.频率输入状态.输入的值临时输入字符串 = null;
state.选项卡当前显示 = E_选项卡菜单.频率菜单列表;
console.debug('频率输入状态已重置, 切换回频率菜单列表');
},
[E_选项卡菜单.跨度菜单列表]: () => {
switch (按钮序号) {
case 1: {
// Full Span
state.频谱仪状态.span.lastSpan.start = state.频谱仪状态.start.value;
state.频谱仪状态.span.lastSpan.stop = state.频谱仪状态.stop.value;
state.频谱仪状态.span.lastSpan.unit = state.频谱仪状态.stop.unit;
state.频谱仪状态.start.value = state.频谱仪状态.span.fullSpan.start;
state.频谱仪状态.start.unit = state.频谱仪状态.span.fullSpan.unit;
state.频谱仪状态.stop.value = state.频谱仪状态.span.fullSpan.stop;
state.频谱仪状态.stop.unit = state.频谱仪状态.span.fullSpan.unit;
state.频谱仪状态.center.value = (state.频谱仪状态.start.value + state.频谱仪状态.stop.value) / 2;
state.频谱仪状态.center.unit = state.频谱仪状态.span.fullSpan.unit;
state.频谱仪状态.span.zeroSpan = false;
console.debug('设置为 Full Span');
break;
}
case 2: {
// Zero Span
state.频谱仪状态.span.lastSpan.start = state.频谱仪状态.start.value;
state.频谱仪状态.span.lastSpan.stop = state.频谱仪状态.stop.value;
state.频谱仪状态.span.lastSpan.unit = state.频谱仪状态.stop.unit;
state.频谱仪状态.start.value = state.频谱仪状态.center.value;
state.频谱仪状态.start.unit = state.频谱仪状态.center.unit;
state.频谱仪状态.stop.value = state.频谱仪状态.center.value;
state.频谱仪状态.stop.unit = state.频谱仪状态.center.unit;
state.频谱仪状态.span.zeroSpan = true;
console.debug('设置为 Zero Span');
break;
}
case 3: {
// Last Span
state.频谱仪状态.start.value = state.频谱仪状态.span.lastSpan.start;
state.频谱仪状态.start.unit = state.频谱仪状态.span.lastSpan.unit;
state.频谱仪状态.stop.value = state.频谱仪状态.span.lastSpan.stop;
state.频谱仪状态.stop.unit = state.频谱仪状态.span.lastSpan.unit;
state.频谱仪状态.center.value = (state.频谱仪状态.start.value + state.频谱仪状态.stop.value) / 2;
state.频谱仪状态.center.unit = state.频谱仪状态.span.lastSpan.unit;
state.频谱仪状态.span.zeroSpan = false;
console.debug('恢复到 Last Span');
break;
}
default: {
console.debug('跨度菜单列表 - 这个按键还没有实现');
break;
}
}
},
[E_选项卡菜单.幅度菜单列表]: () => {
switch (按钮序号) {
case 1: {
// Ref Level
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 = E_幅度菜单列表的项目.RefLevel;
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 = null;
console.debug('设置 幅度输入状态为 RefLevel');
break;
}
case 2: {
// Attenuation
console.debug('点击了 幅度菜单列表 - Attenuation (功能待实现)');
break;
}
default: {
console.debug('幅度菜单列表 - 这个按键还没有实现');
break;
}
}
},
[E_选项卡菜单.幅度单位列表]: () => {
let selectedUnit: E_幅度单位 | null = null;
switch (按钮序号) {
case 1: {
selectedUnit = E_幅度单位.dBm;
break;
}
case 2: {
selectedUnit = E_幅度单位.mV;
break;
}
case 3: {
selectedUnit = E_幅度单位.uV;
break;
}
case 4: {
selectedUnit = E_幅度单位.uA;
break;
}
default: {
console.warn('无效的幅度单位按钮序号:', 按钮序号);
console.groupEnd();
return;
}
}
const tempInputString = state.频谱仪状态.幅度输入状态.输入的值临时输入字符串;
const selectedAmpItem = state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目;
if (!tempInputString || !selectedAmpItem) {
console.warn('幅度输入未完成或未选择项目,无法确认单位。');
console.groupEnd();
return;
}
const inputValue = Number.parseFloat(tempInputString);
if (Number.isNaN(inputValue)) {
console.warn('无法将临时输入解析为有效数字 (幅度):', tempInputString);
console.groupEnd();
return;
}
console.debug('确认幅度单位:', selectedUnit, '值为:', inputValue);
if (selectedAmpItem === E_幅度菜单列表的项目.RefLevel) {
state.频谱仪状态.幅度输入状态.refLevel.value = inputValue;
state.频谱仪状态.幅度输入状态.refLevel.unit = selectedUnit;
console.debug('更新 RefLevel:', state.频谱仪状态.幅度输入状态.refLevel);
}
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 = null;
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 = null;
state.选项卡当前显示 = E_选项卡菜单.幅度菜单列表;
console.debug('幅度输入状态已重置, 切换回幅度菜单列表');
},
});
console.groupEnd();
}
function 执行点击数字按钮(按钮值: E_数字键盘按键) {
console.group('🔢 点击数字按钮', { 按钮值 });
const isFreqInputActive = !!state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目;
const isAmpInputActive = !!state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目;
if (!isFreqInputActive && !isAmpInputActive) {
console.warn('请先选择一个频率或幅度菜单项');
console.groupEnd();
return;
}
let 当前临时输入: string;
let 更新临时输入回调: (newVal: null | string) => void;
let 后续操作菜单: E_选项卡菜单;
if (isFreqInputActive) {
当前临时输入 = state.频谱仪状态.频率输入状态.输入的值临时输入字符串 ?? '';
更新临时输入回调 = (newVal) => {
state.频谱仪状态.频率输入状态.输入的值临时输入字符串 = newVal;
};
后续操作菜单 = E_选项卡菜单.频率单位列表;
} else {
// isAmpInputActive
当前临时输入 = state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 ?? '';
更新临时输入回调 = (newVal) => {
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 = newVal;
};
后续操作菜单 = E_选项卡菜单.幅度单位列表;
}
switch (按钮值) {
case E_数字键盘按键.Dot: {
// 只有在当前临时输入不包含小数点时才允许添加
if (当前临时输入.includes('.')) {
console.debug('已存在小数点,忽略本次输入');
} else {
// 如果当前为空,则为 '0.',否则追加 '.'
更新临时输入回调(当前临时输入 ? `${当前临时输入}.` : '0.');
console.debug(
'输入小数点,临时字符串:',
isFreqInputActive
? state.频谱仪状态.频率输入状态.输入的值临时输入字符串
: state.频谱仪状态.幅度输入状态.输入的值临时输入字符串,
);
}
break;
}
case E_数字键盘按键.Num0:
case E_数字键盘按键.Num1:
case E_数字键盘按键.Num2:
case E_数字键盘按键.Num3:
case E_数字键盘按键.Num4:
case E_数字键盘按键.Num5:
case E_数字键盘按键.Num6:
case E_数字键盘按键.Num7:
case E_数字键盘按键.Num8:
case E_数字键盘按键.Num9: {
if (当前临时输入 === '0') {
更新临时输入回调(String(按钮值));
} else if (当前临时输入 === '-0') {
更新临时输入回调(`-${String(按钮值)}`);
} else {
更新临时输入回调(当前临时输入 + String(按钮值));
}
console.debug(
'输入数字,临时字符串:',
isFreqInputActive
? state.频谱仪状态.频率输入状态.输入的值临时输入字符串
: state.频谱仪状态.幅度输入状态.输入的值临时输入字符串,
);
break;
}
case E_数字键盘按键.PlusMinus: {
if (当前临时输入.startsWith('-')) {
更新临时输入回调(当前临时输入.slice(1));
} else if (当前临时输入 && 当前临时输入 !== '0' && 当前临时输入 !== '0.') {
更新临时输入回调(`-${当前临时输入}`);
} else if (当前临时输入 === '' || 当前临时输入 === '0' || 当前临时输入 === '0.') {
更新临时输入回调('-');
} else {
console.debug('无法切换正负号');
}
console.debug(
'切换正负号,临时字符串:',
isFreqInputActive
? state.频谱仪状态.频率输入状态.输入的值临时输入字符串
: state.频谱仪状态.幅度输入状态.输入的值临时输入字符串,
);
break;
}
default: {
console.warn('未知的数字键盘按键:', 按钮值);
}
}
state.选项卡当前显示 = 后续操作菜单;
console.groupEnd();
}
const img_modules = import.meta.glob<{ default: string }>('./imgs/*.png', {
eager: true,
});
const 屏幕照片 = ref(img_modules['./imgs/屏幕_0.png']!.default);
watch(
() => state.频谱仪状态.center.value,
() => {
屏幕照片.value = img_modules['./imgs/屏幕_1.png']!.default;
},
);
watch(
() => state.频谱仪状态.start.value,
() => {
屏幕照片.value = img_modules['./imgs/屏幕_2.png']!.default;
},
);
watch(
() => state.频谱仪状态.stop.value,
() => {
屏幕照片.value = img_modules['./imgs/屏幕_3.png']!.default;
},
);
watchEffect(() => {
console.debug(`屏幕照片.value :>> `, 屏幕照片.value);
});
</script>
<template>
<!-- <pre
v-if="$__DEV__"
class="fixed bottom-10 right-10 top-10 z-[9999] overflow-auto overflow-y-auto bg-white p-[10px] text-[12px] text-black"
>{{ { 选项卡菜单7个: $enum(E_选项卡菜单).getValues(), ...state } }}</pre
> -->
<div class="wrp relative">
<img :src="设备照片" alt="频谱仪设备" />
<div
class="absolute left-[640px] top-[174px] flex w-[55px] flex-col gap-y-[4px] overflow-visible text-black"
data-box="屏幕右边的按钮"
>
<!-- 上下还有一个 ESC / RETURN -->
<ul class="flex h-[300px] flex-col justify-around gap-y-[4px] overflow-visible" data-box="屏幕右边按钮列表">
<template v-for="n in 7" :key="`按钮${n}`">
<li
class="lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-visible rounded-[2px] text-[12px]"
@click="执行点击屏幕右边的按钮(n)"
>
<button class="ppy-btn !h-full !w-full"></button>
</li>
</template>
</ul>
</div>
<div class="absolute left-[195px] top-[140px] h-[350px] w-[430px] bg-black" data-box="屏幕">
<img :src="屏幕照片" alt="频谱仪屏幕" class="absolute left-0 top-0 !h-[330px] !w-[340px]" />
<!-- -->
<!-- 屏幕左上角 Ref Level -->
<div class="absolute left-[5px] top-[20px] text-left text-[10px] text-white" data-box="屏幕左上角RefLevel">
<span>
Ref {{ state.频谱仪状态.幅度输入状态.refLevel.value }}
{{ state.频谱仪状态.幅度输入状态.refLevel.unit }}
</span>
</div>
<div class="absolute bottom-0 left-0 right-[85px] flex justify-between text-white" data-box="下面左Start 右Stop">
<div class="flex-1 text-left">
<span>Start </span>
<span>{{ state.频谱仪状态.start.value }}</span>
<span>{{ state.频谱仪状态.start.unit }}</span>
</div>
<div class="flex-1 text-right">
<span>Stop </span>
<span>{{ state.频谱仪状态.stop.value }}</span>
<span>{{ state.频谱仪状态.stop.unit }}</span>
</div>
</div>
<div
v-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 ||
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目
"
class="lh-[1.2] absolute left-[40px] top-[140px] flex flex-col gap-y-[0px] bg-gray-500/100 text-white"
data-box="正在输入的值"
>
<!-- 标题:显示当前选中的输入项 -->
<div v-if="state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目">
{{ state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 }}
</div>
<div v-else-if="state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目">
{{ state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 }}
</div>
<!-- 值:优先显示临时输入字符串,如果为空,则显示已确认的值 -->
<div>
<template
v-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 &&
state.频谱仪状态.频率输入状态.输入的值临时输入字符串 !== null
"
>
{{ state.频谱仪状态.频率输入状态.输入的值临时输入字符串 }}
</template>
<template
v-else-if="
state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 &&
state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 !== null
"
>
{{ state.频谱仪状态.幅度输入状态.输入的值临时输入字符串 }}
</template>
<!-- 已确认的频率值 -->
<template
v-else-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 === E_选项卡菜单Freq菜单列表的项目.CenterFreq
"
>
{{ state.频谱仪状态.center.value }}
{{ state.频谱仪状态.center.unit }}
</template>
<template
v-else-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 === E_选项卡菜单Freq菜单列表的项目.StartFreq
"
>
{{ state.频谱仪状态.start.value }} {{ state.频谱仪状态.start.unit }}
</template>
<template
v-else-if="
state.频谱仪状态.频率输入状态.选中的频率菜单列表的项目 === E_选项卡菜单Freq菜单列表的项目.StopFreq
"
>
{{ state.频谱仪状态.stop.value }} {{ state.频谱仪状态.stop.unit }}
</template>
<!-- 已确认的幅度值 -->
<template
v-else-if="state.频谱仪状态.幅度输入状态.选中的幅度菜单列表的项目 === E_幅度菜单列表的项目.RefLevel"
>
{{ state.频谱仪状态.幅度输入状态.refLevel.value }}
{{ state.频谱仪状态.幅度输入状态.refLevel.unit }}
</template>
<template v-else>
0.000000000
<!-- 默认或回退显示 -->
</template>
</div>
</div>
<div
class="absolute bottom-[15px] right-0 top-0 flex w-[80px] flex-col gap-y-[4px] text-black"
data-box="屏幕右侧的选项卡"
>
<div class="menu-item lh-[30px] h-[30px] overflow-hidden bg-[#AFAFAF] text-center" data-box="菜单标题">
{{ 选项卡标题 }}
</div>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.频率菜单列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="频率菜单列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Center Freq</span>
{{ state.频谱仪状态.center.value }}
{{ state.频谱仪状态.center.unit }}
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Start Freq</span>
{{ state.频谱仪状态.start.value }}
{{ state.频谱仪状态.start.unit }}
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Stop Freq</span>
{{ state.频谱仪状态.stop.value }}
{{ state.频谱仪状态.stop.unit }}
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#CF Step</span>
<span>0.0000 hz</span>
<span>X</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Freq Offset</span>
<span>0.0000 Hz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Singnal Track</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Scale Type</span>
</li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.频率单位列表"
class="flex flex-1 flex-col justify-around gap-y-[4px]"
data-box="FreqChannel单位列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>GHz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>MHz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>KHz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Hz</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.跨度菜单列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="跨度菜单列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_跨度菜单列表的项目.FullSpan }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_跨度菜单列表的项目.ZeroSpan }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_跨度菜单列表的项目.LastSpan }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.幅度菜单列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="幅度菜单列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>Ref Level</span>
<span>
{{ state.频谱仪状态.幅度输入状态.refLevel.value }}
{{ state.频谱仪状态.幅度输入状态.refLevel.unit }}
</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>#Attenuation</span>
<!-- <span>TODO</span> -->
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
<ul
v-if="state.选项卡当前显示 === E_选项卡菜单.幅度单位列表"
class="flex h-[300px] flex-col justify-around gap-y-[4px]"
data-box="幅度单位列表"
>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.dBm }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.mV }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.uV }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]">
<span>{{ E_幅度单位.uA }}</span>
</li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
<li class="menu-item lh-[1.2] flex flex-1 flex-col gap-y-[0px] overflow-hidden bg-[#AFAFAF]"></li>
</ul>
</div>
</div>
<div class="ppy-btns absolute left-[768px] top-[120px] h-[415px] !overflow-visible" data-box="机器右边按键">
<!-- 左上角. 长的 -->
<div class="flex flex-col gap-y-[18px] !overflow-visible">
<button class="ppy-btn !w-[62px]" @click="state.选项卡当前显示 = E_选项卡菜单.频率菜单列表">
FREQ<br />Channel
</button>
<button class="ppy-btn !w-[62px]" @click="state.选项卡当前显示 = E_选项卡菜单.跨度菜单列表">
SPAN<br />X Scale
</button>
<button class="ppy-btn !w-[62px]" @click="state.选项卡当前显示 = E_选项卡菜单.幅度菜单列表">
AMPLITUDE<br />Y Scale
</button>
<button class="ppy-btn !w-[62px] opacity-0">#</button>
<button class="ppy-btn !w-[62px] opacity-0">#</button>
</div>
<div class="mt-[20px] grid grid-cols-3 gap-[16.5px] !overflow-visible" data-box="数字键盘区域">
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num7)">7</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num8)">8</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num9)">9</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num4)">4</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num5)">5</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num6)">6</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num1)">1</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num2)">2</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num3)">3</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Num0)">0</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.Dot)">.</button>
<button class="ppy-btn" @click="执行点击数字按钮(E_数字键盘按键.PlusMinus)">+/-</button>
</div>
</div>
</div>
</template>
<style scoped>
@import url('./Helvetica.css');
.wrp {
font-family: 'Helvetica Custom';
}
/* .wrp * {
overflow: hidden;
} */
img[alt='频谱仪设备'] {
width: calc(2554px * 0.5);
max-width: fit-content;
height: calc(1626px * 0.5);
max-height: fit-content;
}
.ppy-btn {
display: flex; /* 使用 flex 居中文本 */
align-items: center;
justify-content: center;
width: 36px;
height: 30px;
padding-left: 2px;
overflow: hidden;
font-size: 10px;
line-height: 1.2;
color: #333; /* 深灰色文字 */
text-align: left;
text-decoration: none;
background-color: #e0e0e0; /* 按钮浅灰色背景 */
border: 1px solid green;
border: 1px solid #b0b0b0; /* 添加边框 */
border-radius: 4px;
box-shadow:
1px 1px 2px rgb(0 0 0 / 20%),
/* 轻微外阴影 */ -1px -1px 2px rgb(255 255 255 / 80%); /* 轻微外高光 */
transition: all 0.1s ease-in-out; /* 平滑过渡效果 */
/* width: 100%; */
/* 宽度占满 grid 单元格 */
/* height: 100%; */
/* 高度占满 grid 单元格 */
/* font-size: 20px; */
/* 调整字体大小 */
/* font-weight: bold; */
/* 字体加粗 */
/* border-radius: 4px; */
/* 按钮圆角 */
}
.ppy-btn:active {
background-color: #d5d5d5;
border-color: #a0a0a0;
box-shadow:
inset 1px 1px 3px rgb(0 0 0 / 30%),
inset -1px -1px 3px rgb(255 255 255 / 60%);
transform: scale(0.98);
}
.menu-item {
/* @apply bg-gray-200/50 p-[2px]; */
font-size: 12px;
/* border: 1px solid white; */
}
[alt='频谱仪设备'] {
opacity: 1;
transition: opacity 0.3s;
}
/* li,
.ppy-btns,
[data-box='屏幕'],
[data-box='菜单标题'] {
box-shadow: 0 0 0 1px rgba(255, 0, 0, 0.5);
} */
</style>

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 705 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 KiB

View File

@ -18,32 +18,32 @@ const readyPromise = new Promise<void>((resolve) => {
});
const 频谱瀑布图Layout = {
title: { text: '频谱瀑布图' },
title: '频谱瀑布图',
xaxis: {
title: { text: '频率 (Hz)' },
title: '频率 (Hz)',
// range: [0, 22_050],
showgrid: false,
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
},
yaxis: {
title: { text: '时间步' },
title: '时间步',
showgrid: false,
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
},
margin: { l: 60, r: 40, b: 40, t: 60 },
} satisfies Partial<import('plotly.js-dist-min').Layout>;
};
const 频谱图Layout = {
title: { text: '频谱图' },
title: '频谱图',
xaxis: {
title: { text: '频率 (Hz)' },
title: '频率 (Hz)',
// range: [0, 22_050],
showgrid: true,
gridcolor: '#eee',
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
},
yaxis: {
title: { text: '幅度 (dB)' },
title: '幅度 (dB)',
showgrid: true, // 显示网格线
gridcolor: '#eee',
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)

View File

@ -1,12 +1,7 @@
import './styles';
import { LogLevels } from 'consola';
import App from './App.vue';
import { setupPlugins } from './plugins';
const autoInstallModules = import.meta.glob('./plugins/*.ts', { eager: true });
setupPlugins(createApp(App), autoInstallModules).mount('#app');
consola.level = LogLevels.verbose;

View File

@ -1,3 +0,0 @@
# 测试
这个文件是被 import 的

View File

@ -1,11 +0,0 @@
<script setup lang="ts">
import MD from './MDPageImportMD.md';
</script>
<template>
<div>
<MD />
</div>
</template>
<style scoped></style>

View File

@ -1,13 +0,0 @@
<!--
http://localhost:4730/Page/PinPuYi
-->
<script setup lang="ts">
definePage({ meta: { title: '频谱仪', hidden: !$__DEV__ } });
</script>
<template>
<template v-if="true">
<PinPuYi />
</template>
<template v-else> 🤡 </template>
</template>

View File

@ -1,352 +0,0 @@
<script setup lang="ts">
import { VueUiXy, type VueUiXyConfig, type VueUiXyDatasetItem } from 'vue-data-ui';
import 'vue-data-ui/style.css'; // If you are using multiple components, place styles import in your main
const config = computed<VueUiXyConfig>(() => {
return {
theme: undefined,
responsive: false,
customPalette: [],
useCssAnimation: true,
downsample: {
threshold: 500,
},
chart: {
fontFamily: 'inherit',
backgroundColor: '#FFFFFFff',
color: '#1A1A1Aff',
height: 600,
width: 1000,
zoom: {
show: true,
color: '#CCCCCCff',
highlightColor: '#4A4A4A',
fontSize: 14,
useResetSlot: false,
startIndex: null,
endIndex: null,
enableRangeHandles: true,
enableSelectionDrag: true,
minimap: {
show: true,
smooth: false,
selectedColor: '#1F77B4',
selectedColorOpacity: 0.2,
lineColor: '#1A1A1A',
selectionRadius: 2,
indicatorColor: '#1A1A1A',
verticalHandles: false,
},
},
padding: {
top: 36,
right: 24,
bottom: 48,
left: 48,
},
highlighter: {
color: '#1A1A1Aff',
opacity: 5,
useLine: false,
lineDasharray: 2,
lineWidth: 1,
},
highlightArea: {
show: false,
from: 0,
to: 0,
color: '#CCCCCCff',
opacity: 20,
caption: {
text: 'Caption',
fontSize: 20,
color: '#1A1A1Aff',
bold: false,
offsetY: 0,
width: 'auto',
padding: 3,
textAlign: 'center',
},
},
timeTag: {
show: false,
backgroundColor: '#e1e5e8ff',
color: '#1A1A1Aff',
fontSize: 12,
circleMarker: {
radius: 3,
color: '#1A1A1Aff',
},
},
grid: {
stroke: '#e1e5e8ff',
showVerticalLines: false,
showHorizontalLines: false,
position: 'middle',
frame: {
show: false,
stroke: '#E1E5E8ff',
strokeWidth: 2,
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeDasharray: 0,
},
labels: {
show: true,
color: '#1A1A1Aff',
fontSize: 16,
axis: {
yLabel: '',
yLabelOffsetX: 0,
xLabel: '',
xLabelOffsetY: 14,
fontSize: 12,
},
zeroLine: {
show: true,
},
xAxis: {
showBaseline: true,
},
yAxis: {
showBaseline: true,
commonScaleSteps: 10,
useIndividualScale: false,
stacked: false,
gap: 12,
labelWidth: 40,
formatter: null,
scaleMin: null,
scaleMax: null,
groupColor: '#1A1A1A',
scaleLabelOffsetX: 0,
scaleValueOffsetX: 0,
},
xAxisLabels: {
color: '#1A1A1Aff',
show: true,
values: [],
fontSize: 18,
showOnlyFirstAndLast: false,
showOnlyAtModulo: false,
modulo: 12,
yOffset: 8,
rotation: 0,
},
},
},
comments: {
show: true,
showInTooltip: true,
width: 200,
offsetX: 0,
offsetY: 0,
},
labels: {
fontSize: 16,
prefix: '',
suffix: '',
},
legend: {
color: '#1A1A1Aff',
show: true,
fontSize: 16,
},
title: {
text: 'Title',
color: '#1A1A1Aff',
fontSize: 20,
bold: true,
textAlign: 'center',
paddingLeft: 0,
paddingRight: 0,
subtitle: {
color: '#CCCCCCff',
text: '',
fontSize: 16,
bold: false,
},
show: true,
},
tooltip: {
show: true,
color: '#1A1A1Aff',
backgroundColor: '#FFFFFFff',
fontSize: 14,
customFormat: null,
borderRadius: 4,
borderColor: '#e1e5e8',
borderWidth: 1,
backgroundOpacity: 30,
position: 'center',
offsetY: 24,
showTimeLabel: true,
showValue: true,
showPercentage: false,
roundingValue: 0,
roundingPercentage: 0,
},
userOptions: {
show: true,
showOnChartHover: false,
keepStateOnChartLeave: true,
position: 'right',
buttons: {
tooltip: true,
pdf: true,
csv: true,
img: true,
table: true,
labels: true,
fullscreen: true,
sort: false,
stack: true,
animation: false,
annotator: true,
},
buttonTitles: {
open: 'Open options',
close: 'Close options',
tooltip: 'Toggle tooltip',
pdf: 'Download PDF',
csv: 'Download CSV',
img: 'Download PNG',
table: 'Toggle table',
labels: 'Toggle labels',
fullscreen: 'Toggle fullscreen',
stack: 'Toggle stack mode',
annotator: 'Toggle annotator',
},
print: {
allowTaint: false,
backgroundColor: '#FFFFFFff',
useCORS: false,
onclone: null,
scale: 2,
logging: false,
},
},
},
bar: {
borderRadius: 2,
useGradient: true,
periodGap: 0.1,
border: {
useSerieColor: false,
strokeWidth: 1,
stroke: '#FFFFFFff',
},
labels: {
show: true,
offsetY: -8,
rounding: 0,
color: '#1A1A1Aff',
formatter: null,
},
serieName: {
show: false,
offsetY: -6,
useAbbreviation: true,
abbreviationSize: 3,
useSerieColor: true,
color: '#1A1A1Aff',
bold: false,
},
},
line: {
radius: 6,
useGradient: false,
strokeWidth: 2,
cutNullValues: false,
dot: {
hideAboveMaxSerieLength: 62,
useSerieColor: false,
fill: '#FFFFFF',
strokeWidth: 2,
},
labels: {
show: true,
offsetY: -16,
rounding: 0,
color: '#1A1A1Aff',
formatter: null,
},
area: {
useGradient: true,
opacity: 20,
},
tag: {
followValue: true,
formatter: null,
fontSize: 14,
},
},
plot: {
radius: 6,
useGradient: true,
dot: {
useSerieColor: true,
fill: '#FFFFFF',
strokeWidth: 0.5,
},
labels: {
show: true,
offsetY: -8,
rounding: 0,
color: '#1A1A1Aff',
formatter: null,
},
tag: {
followValue: true,
formatter: null,
fontSize: 14,
},
},
table: {
responsiveBreakpoint: 400,
rounding: 0,
sparkline: true,
showSum: true,
columnNames: {
period: 'Period',
total: 'Total',
},
th: {
backgroundColor: '#FAFAFAff',
color: '#1A1A1Aff',
outline: '',
},
td: {
backgroundColor: '#FAFAFAff',
color: '#1A1A1Aff',
outline: '',
},
},
showTable: false,
};
});
const dataset = computed<VueUiXyDatasetItem[]>(() => {
return [
{
name: 'Serie name',
series: [1, 9, 7, 2, 12, 16, 17, 30, 16, 23],
color: '#1f77b4',
type: 'line',
shape: 'circle',
useArea: false,
useProgression: false,
dataLabels: true,
smooth: true,
dashed: false,
useTag: 'none',
},
];
});
</script>
<template>
<!-- Using a wrapper is optional -->
<div :style="{ width: '600px' }">
<VueUiXy :config="config" :dataset="dataset" />
</div>
</template>

View File

@ -27,7 +27,15 @@ import { Input as ShaInput } from '@/shadcn/components/ui/input';
<ShadcnButton variant="ghost">Ghost</ShadcnButton>
<ShadcnButton variant="link">Link</ShadcnButton>
</div>
<div class="demo-block">
<VBtn>VBtn</VBtn>
<v-btn variant="outlined"> Button </v-btn>
<v-btn variant="tonal"> Button </v-btn>
<v-btn variant="text"> Button </v-btn>
<v-btn variant="plain"> Button </v-btn>
</div>
</div>
<div class="demo-block">
<a-input placeholder="Ant Design" />
<InputText placeholder="Primevue" />

View File

@ -28,7 +28,7 @@ defineOptions({
},
});
const refresh = () => {
state.list.splice(0);
state.list.splice(0, state.list.length);
state.page = 0;
state.complete = false;
loadMore();

View File

@ -1,7 +1,7 @@
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
import { setupLayouts } from 'virtual:meta-layouts';
// import { createGetRoutes, setupLayouts } from 'virtual:generated-layouts';
import { createRouter, createWebHistory } from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router/auto';
import { handleHotUpdate, routes } from 'vue-router/auto-routes';
const setupLayoutsResult = setupLayouts(routes);

11
src/plugins/vuetify.ts Normal file
View File

@ -0,0 +1,11 @@
import { createVuetify } from 'vuetify';
import 'vuetify/styles';
export function install({ app }: { app: import('vue').App<Element> }) {
const vuetify = createVuetify({
// components,
// directives,
});
app.use(vuetify);
}

View File

@ -1,11 +0,0 @@
declare global {
const $__DEV__: boolean;
}
declare module 'vue' {
export interface ComponentCustomProperties {
$__DEV__: boolean;
}
}
export {};

View File

@ -1,4 +1,12 @@
declare module '*.vue' {
declare global {
const $__DEV__: boolean;
}
declare module 'vue' {
export interface ComponentCustomProperties {
$__DEV__: boolean;
}
import type { ComponentOptions } from 'vue';
const Component: ComponentOptions;
@ -11,3 +19,5 @@ declare module '*.md' {
const Component: ComponentOptions;
export default Component;
}
export {};

3
typed-router.d.ts vendored
View File

@ -34,12 +34,9 @@ declare module 'vue-router/auto-routes' {
'PageIframePageSpectrogram': RouteRecordInfo<'PageIframePageSpectrogram', '/Page/iframe-page/Spectrogram', Record<never, never>, Record<never, never>>,
'PageJSPage': RouteRecordInfo<'PageJSPage', '/Page/JSPage', Record<never, never>, Record<never, never>>,
'PageMDPage': RouteRecordInfo<'PageMDPage', '/Page/MDPage', Record<never, never>, Record<never, never>>,
'PageMDPageImportMD': RouteRecordInfo<'PageMDPageImportMD', '/Page/MDPageImportMD', Record<never, never>, Record<never, never>>,
'PageP5Js': RouteRecordInfo<'PageP5Js', '/Page/p5_js', Record<never, never>, Record<never, never>>,
'PagePinPuYi': RouteRecordInfo<'PagePinPuYi', '/Page/PinPuYi', Record<never, never>, Record<never, never>>,
'PageStyle': RouteRecordInfo<'PageStyle', '/Page/Style', Record<never, never>, Record<never, never>>,
'PageViteAssets': RouteRecordInfo<'PageViteAssets', '/Page/vite-assets', Record<never, never>, Record<never, never>>,
'PageVueDataUi': RouteRecordInfo<'PageVueDataUi', '/Page/vue-data-ui', Record<never, never>, Record<never, never>>,
'PkgsUsageI18n': RouteRecordInfo<'PkgsUsageI18n', '/PkgsUsage/I18n', Record<never, never>, Record<never, never>>,
'PkgsUsageTsEnumUtil': RouteRecordInfo<'PkgsUsageTsEnumUtil', '/PkgsUsage/ts-enum-util', Record<never, never>, Record<never, never>>,
'UIComponentsAntdV': RouteRecordInfo<'UIComponentsAntdV', '/UI-components/AntdV', Record<never, never>, Record<never, never>>,

View File

@ -25,12 +25,12 @@ import { createUtils4uAutoImports } from 'utils4u/auto-imports';
import { type PluginOption } from 'vite';
import { checker } from 'vite-plugin-checker';
import { vitePluginFakeServer } from 'vite-plugin-fake-server';
import VitePluginImageTools from 'vite-plugin-image-tools';
import pluginPurgeCss from 'vite-plugin-purgecss-updated-v5';
import { viteSingleFile } from 'vite-plugin-singlefile';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import VueDevTools from 'vite-plugin-vue-devtools';
import MetaLayouts from 'vite-plugin-vue-meta-layouts';
import vuetify from 'vite-plugin-vuetify';
import { ViteWebfontDownload } from 'vite-plugin-webfont-dl';
import { viteArchiverPlugin } from './vite.config.plugin.archiver';
@ -79,6 +79,9 @@ export function Plugins() {
headEnabled: true,
}),
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#automatic-imports
vuetify({ autoImport: true /* styles: 'none' */ }), // Enabled by default
// https://github.com/antfu/unplugin-auto-import
UnpluginAutoImport({
dirs: [
@ -191,15 +194,6 @@ export function Plugins() {
{ dest: cesiumBaseUrl, src: `${cesiumSource}/Widgets` },
],
}),
VitePluginImageTools({
quality: 80,
enableWebp: true,
enableDev: false,
enableDevWebp: false,
// 排除字体 SVG 文件和可能有问题的 SVG 文件
excludes: /fonts\/.*\.svg$|Helvetica.*\.svg$/,
}),
);
// 检查是否在VS Code终端中运行

View File

@ -36,9 +36,6 @@ export default defineConfig(({ command, mode }) => {
'satellite.js',
'ts-enum-util',
'unplugin-vue-router',
'unplugin-vue-router/runtime',
'unplugin-vue-router/data-loaders/basic',
'unplugin-vue-router/data-loaders/pinia-colada',
],
exclude: ['quill', 'chart.js/auto'],
},
@ -56,7 +53,7 @@ export default defineConfig(({ command, mode }) => {
// https://cn.rollupjs.org/configuration-options/#output-assetfilenames
// output: env.VITE_SPLIT_CHUNKS === 'true' ? (await import('utils4u/rollup')).createSplitChunkOutput() : undefined,
output: {
// minifyInternalExports: false,
minifyInternalExports: false,
// manualChunks: {
// 'vendor/utils4u': ['utils4u', 'utils4u/vue-use', 'utils4u/primevue'],
// 'vendor/vue': ['vue'],
@ -70,14 +67,6 @@ export default defineConfig(({ command, mode }) => {
manualChunks: {
'vendor/Cesium': ['cesium'],
},
// advancedChunks: {
// groups: [
// {
// name: 'vendor/cesium',
// test: 'cesium',
// },
// ],
// },
},
},
sourcemap: mode !== 'production' || env.VITE_SOURCE_MAP === 'true',