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
49 changed files with 3177 additions and 8587 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配置已生效。

View File

@ -1,11 +1,4 @@
---
description:
globs:
alwaysApply: true
---
# GitHub Copilot Instructions
本文件定义了项目的代码生成规范GitHub Copilot 和其他 AI 助手应遵循这些指令。
# Project Conventions and Technical Guidelines
This document outlines the core technical choices, coding conventions, and configuration details for this project. Adhering to these guidelines ensures consistency and leverages the project's setup effectively.

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@b4a2caa64aca72f8aeada59d0df3181a12df8268
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@b4a2caa64aca72f8aeada59d0df3181a12df8268
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@b4a2caa64aca72f8aeada59d0df3181a12df8268
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@b4a2caa64aca72f8aeada59d0df3181a12df8268
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@b4a2caa64aca72f8aeada59d0df3181a12df8268
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,123 +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.22",
"@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.12",
"@unhead/vue": "^2.0.5",
"@vant/use": "^1.6.0",
"@vueuse/core": "^13.5.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.131.0",
"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.2",
"satellite.js": "^6.0.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.9",
"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.9.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.16.3",
"@types/node": "^22.14.0",
"@types/nprogress": "^0.2.3",
"@types/plotly.js-dist-min": "^2.3.4",
"@types/three": "^0.178.1",
"@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.4",
"@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.6.0",
"@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.1",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-oxlint": "^1.6.0",
"eslint-plugin-perfectionist": "^4.15.0",
"eslint-plugin-unicorn": "^59.0.1",
"eslint-plugin-vue": "^10.3.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",
"naive-ui": "^2.42.0",
"npm-run-all2": "^8.0.4",
"oxlint": "^1.6.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.3",
"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",
"vfonts": "^0.0.3",
"vite": "^7.0.2",
"vite-plugin-checker": "^0.10.0",
"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-singlefile": "^2.3.0",
"vite-plugin-static-copy": "^3.1.0",
"vite-plugin-vue-devtools": "^7.7.7",
"vite-plugin-purgecss-updated-v5": "^1.2.4",
"vite-plugin-singlefile": "^2.2.0",
"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.1",
"vue-tsc": "^3.0.1"
"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"
}
}

6904
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -15,11 +15,9 @@ const themeConfig = computed(() => {
</script>
<template>
<n-config-provider preflight-style-disabled>
<a-config-provider :theme="themeConfig">
<RouterView />
</a-config-provider>
</n-config-provider>
<a-config-provider :theme="themeConfig">
<RouterView />
</a-config-provider>
<DynamicDialog /> <ConfirmDialog /> <Toast />
</template>

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,321 +0,0 @@
<script setup lang="ts">
import type { MenuOption } from 'naive-ui';
import { createGetRoutes } from '@/plugins/router';
const router = useRouter();
// 响应式断点检测
const isMobile = ref(false);
const isTablet = ref(false);
// 检测屏幕尺寸
const updateScreenSize = () => {
const width = window.innerWidth;
const wasMobile = isMobile.value;
isMobile.value = width < 768;
isTablet.value = width >= 768 && width < 1024;
// 当从移动端切换到桌面端时,关闭抽屉并重置折叠状态
if (wasMobile && !isMobile.value) {
drawerVisible.value = false;
collapsed.value = false;
}
// 当从桌面端切换到移动端时,关闭抽屉
else if (!wasMobile && isMobile.value) {
drawerVisible.value = false;
}
};
// 侧边栏状态管理
const collapsed = ref(false);
const drawerVisible = ref(false);
// 初始化屏幕尺寸检测
onMounted(() => {
updateScreenSize();
window.addEventListener('resize', updateScreenSize);
// 移动端默认收起侧边栏
if (isMobile.value) {
collapsed.value = true;
drawerVisible.value = false;
}
});
onUnmounted(() => {
window.removeEventListener('resize', updateScreenSize);
});
// 菜单项类型定义
type MenuItemWithRoute = MenuOption & {
routeName?: string;
parentId?: string;
originalPath?: string;
};
// 生成菜单项
const menuOptions = computed(() => {
let flatArray: MenuItemWithRoute[] = createGetRoutes(router)()
.filter((route) => !route.path.includes('/:'))
.filter((route) => !route.meta.hidden)
.map((route) => ({
key: route.path,
label: route.meta.title || `${(route.name as string) || route.path}`,
routeName: route.name as string,
}));
flatArray = flatArray.map((item) => {
const originalPath = item.key as string; // 保存原始路径
let id = item.key as string;
if (flatArray.some((item) => (item.key as string).startsWith(`${id}/`))) {
id = `${id}/index`;
}
// 去掉最前面的 /
id = id.replace(/^\//, '');
let parentId = id.replace(/\/[^/]+$/, '');
if (parentId === id) {
parentId = '_ROOT_';
}
return {
...item,
key: id,
parentId,
originalPath, // 保存原始路径用于后续映射
};
});
const groupItems: MenuItemWithRoute[] = [];
for (const flatArrayItem of flatArray) {
if (!groupItems.some((item) => item.key === flatArrayItem.parentId) && flatArrayItem.parentId !== '_ROOT_') {
let groupItemParentId = flatArrayItem.parentId!.replace(/\/[^/]+$/, '');
if (groupItemParentId === flatArrayItem.parentId) groupItemParentId = '_ROOT_';
groupItems.push({
key: flatArrayItem.parentId!,
label: `Group ${flatArrayItem.parentId}`,
parentId: groupItemParentId,
});
}
}
const tree = arrayToTree([...flatArray, ...groupItems], {
id: 'key',
parentId: 'parentId',
rootId: '_ROOT_',
});
// 递归转换树形结构为 naive-ui menu 格式
function convertToMenuOptions(tree: MenuItemWithRoute[]): MenuOption[] {
return tree.map((item) => {
const menuItem: MenuOption = {
key: item.key,
label: item.label,
};
if (item.children && item.children.length > 0) {
menuItem.children = convertToMenuOptions(item.children);
} else if (item.routeName) {
// 叶子节点,存储路由映射
menuRouteMap.set(item.key as string, item.routeName);
// 同时存储路径到 key 的映射(用于高亮显示)
if (item.originalPath) {
pathToKeyMap.set(item.originalPath, item.key as string);
}
(menuItem as MenuItemWithRoute).routeName = item.routeName;
}
return menuItem;
});
}
// 清空之前的映射
menuRouteMap.clear();
pathToKeyMap.clear();
const result = convertToMenuOptions(tree);
// 菜单生成后,重新设置当前选中的菜单项
nextTick(() => {
const currentPath = router.currentRoute.value.path;
const menuKey = pathToKeyMap.get(currentPath);
if (menuKey) {
selectedKey.value = menuKey;
} else {
const pathWithoutSlash = currentPath.replace(/^\//, '');
selectedKey.value = pathWithoutSlash;
}
});
return result;
});
// 当前选中的菜单项
const selectedKey = ref<string>();
// 存储菜单项与路由名称的映射
const menuRouteMap = new Map<string, string>();
// 存储路由路径与菜单 key 的映射(用于高亮显示)
const pathToKeyMap = new Map<string, string>();
// 处理菜单点击
const handleMenuSelect = (key: string, item: MenuOption) => {
const routeName = menuRouteMap.get(key) || (item as MenuItemWithRoute).routeName;
if (routeName) {
router.push({ name: routeName as never });
// 移动端点击菜单项后自动收起侧边栏
if (isMobile.value) {
drawerVisible.value = false;
}
}
};
// 监听路由变化,更新选中的菜单项
watch(
() => router.currentRoute.value.path,
(newPath) => {
// 使用路径到 key 的映射来找到对应的菜单项
const menuKey = pathToKeyMap.get(newPath);
if (menuKey) {
selectedKey.value = menuKey;
} else {
// 如果没有找到精确匹配,尝试去掉前面的 / 再匹配
const pathWithoutSlash = newPath.replace(/^\//, '');
selectedKey.value = pathWithoutSlash;
}
},
{ immediate: true },
);
// 切换侧边栏状态
const toggleSidebar = () => {
if (isMobile.value) {
// 移动端使用抽屉模式
drawerVisible.value = !drawerVisible.value;
} else {
// 桌面端使用折叠模式
collapsed.value = !collapsed.value;
}
};
</script>
<template>
<n-layout :has-sider="!isMobile">
<!-- 移动端抽屉 -->
<n-drawer
v-if="isMobile"
v-model:show="drawerVisible"
:width="280"
placement="left"
:trap-focus="false"
:block-scroll="false"
>
<n-drawer-content title="菜单" :native-scrollbar="false">
<n-menu :options="menuOptions" :value="selectedKey" @update:value="handleMenuSelect" />
</n-drawer-content>
</n-drawer>
<!-- 桌面端侧边栏 -->
<n-layout-sider
v-if="!isMobile"
:collapsed="collapsed"
:native-scrollbar="false"
bordered
collapse-mode="width"
:collapsed-width="64"
:width="240"
show-trigger
@collapse="collapsed = true"
@expand="collapsed = false"
>
<n-menu
:collapsed="collapsed"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="menuOptions"
:value="selectedKey"
@update:value="handleMenuSelect"
/>
</n-layout-sider>
<n-layout>
<n-layout-header
bordered
:style="{
height: '64px',
padding: isMobile ? '0 16px' : '0 24px',
display: 'flex',
alignItems: 'center',
}"
>
<n-button
quaternary
@click="toggleSidebar"
:style="{
marginRight: isMobile ? '8px' : '12px',
padding: isMobile ? '8px' : '6px',
}"
:size="isMobile ? 'medium' : 'small'"
>
<template #icon>
<n-icon :size="isMobile ? 20 : 18">
<svg viewBox="0 0 24 24">
<path
v-if="!isMobile && collapsed"
fill="currentColor"
d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"
/>
<path
v-else-if="!isMobile && !collapsed"
fill="currentColor"
d="M3 18h13v-2H3v2zm0-5h10v-2H3v2zm0-7v2h13V6H3zm18 9.59L17.42 12 21 8.41 19.59 7l-5 5 5 5L21 15.59z"
/>
<!-- 移动端始终显示菜单图标 -->
<path v-else fill="currentColor" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
</svg>
</n-icon>
</template>
</n-button>
<span
:style="{
fontSize: isMobile ? '16px' : '18px',
fontWeight: '500',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}"
>
Vue TS Example
</span>
</n-layout-header>
<n-layout-content
:content-style="{
padding: isMobile ? '16px' : '24px',
minHeight: 'calc(100vh - 64px - 72px)', // 减去头部和底部高度
}"
>
<router-view />
</n-layout-content>
<n-layout-footer
bordered
:style="{
padding: isMobile ? '16px' : '24px',
textAlign: 'center',
}"
>
<span
:style="{
color: 'var(--n-text-color-disabled)',
fontSize: isMobile ? '12px' : '14px',
}"
>
© 2025 Vue TS Example. All rights reserved.
</span>
</n-layout-footer>
</n-layout>
</n-layout>
</template>

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

@ -1,11 +0,0 @@
<script setup lang="ts"></script>
<template>
<n-card>
<n-divider title-placement="left">
<n-text depth="3"> Naive UI Components Divider </n-text>
</n-divider>
</n-card>
</template>
<style scoped></style>

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

@ -13,9 +13,4 @@ import 'primeicons/primeicons.css';
import './reset/reset-primevue.css';
import './reset/reset-antdv.less';
// 通用字体
import 'vfonts/Lato.css';
// 等宽字体
import 'vfonts/FiraCode.css';
//
import 'virtual:uno.css';

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 {};

4
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>>,
@ -47,7 +44,6 @@ declare module 'vue-router/auto-routes' {
'UIComponentsInfiniteLoading': RouteRecordInfo<'UIComponentsInfiniteLoading', '/UI-components/infinite-loading', Record<never, never>, Record<never, never>>,
'UIComponentsInfiniteLoadingDetail': RouteRecordInfo<'UIComponentsInfiniteLoadingDetail', '/UI-components/infinite-loading/detail', Record<never, never>, Record<never, never>>,
'UIComponentsInspiraUI': RouteRecordInfo<'UIComponentsInspiraUI', '/UI-components/InspiraUI', Record<never, never>, Record<never, never>>,
'UIComponentsNaiveUI': RouteRecordInfo<'UIComponentsNaiveUI', '/UI-components/NaiveUI', Record<never, never>, Record<never, never>>,
'UIComponentsPrimeVue': RouteRecordInfo<'UIComponentsPrimeVue', '/UI-components/PrimeVue', Record<never, never>, Record<never, never>>,
'UIComponentsShadcnVue': RouteRecordInfo<'UIComponentsShadcnVue', '/UI-components/ShadcnVue', Record<never, never>, Record<never, never>>,
'VueMacrosDefineRender': RouteRecordInfo<'VueMacrosDefineRender', '/VueMacros/DefineRender', Record<never, never>, Record<never, never>>,

View File

@ -11,11 +11,11 @@ import Vue from '@vitejs/plugin-vue';
import VueJsx from '@vitejs/plugin-vue-jsx';
import path from 'node:path';
import UnoCSS from 'unocss/vite';
import AutoImport from 'unplugin-auto-import/vite';
import UnpluginAutoImport from 'unplugin-auto-import/vite';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
import IconsResolver from 'unplugin-icons/resolver';
import Icons from 'unplugin-icons/vite';
import { AntDesignVueResolver, NaiveUiResolver, TDesignResolver } from 'unplugin-vue-components/resolvers';
import { AntDesignVueResolver, TDesignResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';
import VueMacros from 'unplugin-vue-macros/vite';
import Markdown from 'unplugin-vue-markdown/vite';
@ -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';
@ -66,8 +66,7 @@ export function Plugins() {
// https://github.com/dishait/vite-plugin-vue-meta-layouts
MetaLayouts({
// defaultLayout: 'sakai-vue/AppLayout',
defaultLayout: 'naive-ui/AppLayout',
defaultLayout: 'sakai-vue/AppLayout',
skipTopLevelRouteLayout: false, // 打开修复 https://github.com/JohnCampionJr/vite-plugin-vue-layouts/issues/134默认为 false 关闭
}),
@ -80,8 +79,11 @@ 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
AutoImport({
UnpluginAutoImport({
dirs: [
// 'src/composables',
'src/stores',
@ -99,7 +101,6 @@ export function Plugins() {
{
'consola/browser': ['consola'],
'vue-router/auto': ['useLink'],
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
},
],
resolvers: [TDesignResolver({ esm: true, library: 'mobile-vue' }), VantResolver({ importStyle: true })],
@ -126,7 +127,6 @@ export function Plugins() {
TDesignResolver({ esm: true, library: 'mobile-vue' }),
VantResolver({ importStyle: true }),
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
NaiveUiResolver(),
],
}),
@ -194,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',