Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9ada32765a |
@@ -1,8 +1,6 @@
|
|||||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
end_of_line = lf
|
|
||||||
max_line_length = 120
|
|
||||||
|
1
.gitattributes
vendored
@@ -39,7 +39,6 @@ commit-msg text eol=lf
|
|||||||
*.woff2 binary
|
*.woff2 binary
|
||||||
*.eot binary
|
*.eot binary
|
||||||
*.otf binary
|
*.otf binary
|
||||||
*.spline binary
|
|
||||||
# Add more binary...
|
# Add more binary...
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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配置已生效。
|
|
9
.github/copilot-instructions.md
vendored
@@ -1,11 +1,4 @@
|
|||||||
---
|
# Project Conventions and Technical Guidelines
|
||||||
description:
|
|
||||||
globs:
|
|
||||||
alwaysApply: true
|
|
||||||
---
|
|
||||||
# GitHub Copilot Instructions
|
|
||||||
|
|
||||||
本文件定义了项目的代码生成规范,GitHub Copilot 和其他 AI 助手应遵循这些指令。
|
|
||||||
|
|
||||||
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.
|
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
@@ -0,0 +1,3 @@
|
|||||||
|
```bash
|
||||||
|
ln -s .github/copilot-instructions.md .roorules
|
||||||
|
```
|
2
.github/workflows/lint.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 🛠️ 设置Node环境
|
- name: 🛠️ 设置Node环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
|
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
|
||||||
- name: 🔍 静态代码分析
|
- name: 🔍 静态代码分析
|
||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
- name: 📦 构建项目
|
- name: 📦 构建项目
|
||||||
|
28
.github/workflows/playwright.yaml
vendored
@@ -7,13 +7,6 @@ env:
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
run_cleanup:
|
|
||||||
description: '是否运行 Surge 清理 Job'
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
surge:
|
surge:
|
||||||
@@ -22,7 +15,7 @@ jobs:
|
|||||||
url: ${{ steps.surge_deploy.outputs.url }}
|
url: ${{ steps.surge_deploy.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
- name: ⚙️ 设置 Node 环境
|
- name: ⚙️ 设置 Node 环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
|
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
|
||||||
- name: 🔨 构建项目
|
- name: 🔨 构建项目
|
||||||
run: pnpm run build-only
|
run: pnpm run build-only
|
||||||
env:
|
env:
|
||||||
@@ -30,31 +23,22 @@ jobs:
|
|||||||
- name: 🚀 部署到 Surge
|
- name: 🚀 部署到 Surge
|
||||||
id: surge_deploy
|
id: surge_deploy
|
||||||
if: ${{ github.actor != 'nektos/act' }} # https://nektosact.com/usage/index.html#skipping-steps
|
if: ${{ github.actor != 'nektos/act' }} # https://nektosact.com/usage/index.html#skipping-steps
|
||||||
uses: yanhao98/composite-actions/deploy-dist-to-surge@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
|
uses: yanhao98/composite-actions/deploy-dist-to-surge@4470aa136359d05ae3b086d37cfaa33305448a5b
|
||||||
|
|
||||||
playwright:
|
playwright:
|
||||||
needs: surge
|
needs: surge
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: mcr.microsoft.com/playwright:v1.55.0-noble
|
container: mcr.microsoft.com/playwright:v1.51.1-noble
|
||||||
steps:
|
steps:
|
||||||
- name: ⚙️ 设置 Node 环境
|
- name: ⚙️ 设置 Node 环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
|
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
|
||||||
# - name: 📥 安装 Playwright 浏览器
|
# - name: 📥 安装 Playwright 浏览器
|
||||||
# run: pnpm exec playwright install --with-deps
|
# run: pnpm exec playwright install --with-deps
|
||||||
- name: ▶️ 运行 Playwright 测试
|
- name: ▶️ 运行 Playwright 测试
|
||||||
run: |
|
run: npx playwright test
|
||||||
echo "BASE_URL: ${{ needs.surge.outputs.url }}"
|
|
||||||
echo "───────────────────────────────"
|
|
||||||
npx playwright test
|
|
||||||
env:
|
env:
|
||||||
BASE_URL: ${{ needs.surge.outputs.url }}
|
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 部署
|
- 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:
|
env:
|
||||||
SURGE_TOKEN: d843de16b331c626f10771245c56ed93
|
SURGE_TOKEN: d843de16b331c626f10771245c56ed93
|
||||||
|
2
.github/workflows/vercel.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: ⚙️ 设置 Node 环境
|
- name: ⚙️ 设置 Node 环境
|
||||||
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
|
uses: yanhao98/composite-actions/setup-node-environment@4470aa136359d05ae3b086d37cfaa33305448a5b
|
||||||
|
|
||||||
- name: 📥 拉取 Vercel 环境信息
|
- name: 📥 拉取 Vercel 环境信息
|
||||||
run: pnpm dlx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
run: pnpm dlx vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
# 此钩子在 pre-commit 钩子成功完成后,用于检查提交消息。
|
# 此钩子在 pre-commit 钩子成功完成后,用于检查提交消息。
|
||||||
echo "📝 [Commit-msg] 正在运行 commit-msg 钩子..."
|
echo "📝 [Commit-msg] 正在运行 commit-msg 钩子..."
|
||||||
echo "当前提交的文件是:$1"
|
# 在这里添加你的 commit message 验证逻辑,例如 commitlint
|
||||||
# npx --no -- commitlint --edit "$1"
|
# npx --no -- commitlint --edit "$1"
|
||||||
pnpm exec commitlint --edit $1
|
|
||||||
echo "✅ [Commit-msg] commit-msg 钩子完成!"
|
echo "✅ [Commit-msg] commit-msg 钩子完成!"
|
||||||
|
4
.npmrc
@@ -5,9 +5,9 @@ registry=https://registry.npmjs.org/
|
|||||||
# registry=https://nexus.oo1.dev/repository/npm/
|
# registry=https://nexus.oo1.dev/repository/npm/
|
||||||
|
|
||||||
# https://pnpm.io/zh/npmrc#node-mirrorltreleasedir
|
# https://pnpm.io/zh/npmrc#node-mirrorltreleasedir
|
||||||
use-node-version=22.18.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: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:rc=https://npmmirror.com/mirrors/node-rc/
|
||||||
node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/
|
node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/
|
||||||
|
|
||||||
# shamefully-hoist=true
|
shamefully-hoist=true
|
||||||
|
1
.vscode/extensions.json
vendored
@@ -4,6 +4,7 @@
|
|||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"simonhe.common-intellisense",
|
"simonhe.common-intellisense",
|
||||||
|
"antfu.file-nesting",
|
||||||
"oxc.oxc-vscode"
|
"oxc.oxc-vscode"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
3
.vscode/settings.json
vendored
@@ -27,7 +27,8 @@
|
|||||||
"source.fixAll.eslint": "never",
|
"source.fixAll.eslint": "never",
|
||||||
"source.fixAll.stylelint": "never",
|
"source.fixAll.stylelint": "never",
|
||||||
"source.fixAll.oxc": "never",
|
"source.fixAll.oxc": "never",
|
||||||
"source.organizeImports": "never"
|
"source.organizeImports": "never",
|
||||||
|
"source.fixAll": "never"
|
||||||
},
|
},
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"oxc.enable": true,
|
"oxc.enable": true,
|
||||||
|
33
.vscode/tasks.json
vendored
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "🚀 dev",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "pnpm run dev",
|
|
||||||
"isBackground": true,
|
|
||||||
"problemMatcher": {
|
|
||||||
"owner": "vite",
|
|
||||||
"pattern": {
|
|
||||||
"regexp": "."
|
|
||||||
},
|
|
||||||
"background": {
|
|
||||||
"activeOnStart": true,
|
|
||||||
"beginsPattern": ".*VITE.*",
|
|
||||||
"endsPattern": ".*ready in.*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"group": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
},
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "always",
|
|
||||||
"panel": "new"
|
|
||||||
},
|
|
||||||
"runOptions": {
|
|
||||||
"instanceLimit": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
30
README.md
@@ -15,9 +15,33 @@ pnpm install --registry=https://nexus.oo1.dev/repository/npm
|
|||||||
pnpm run dev
|
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
|
## 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)
|
- [Performance API优化页面性能](https://juejin.cn/post/7238779568478552122)
|
||||||
- [vitepress-theme-demoblock](https://www.npmjs.com/package/vitepress-theme-demoblock)
|
- [vitepress-theme-demoblock](https://www.npmjs.com/package/vitepress-theme-demoblock)
|
||||||
- [Vite PWA](https://vite-pwa-org-zh.netlify.app/guide/)
|
- [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://biomejs.dev/zh-cn/internals/language-support/
|
||||||
- https://github.dev/antfu-collective/vitesse/
|
- https://github.dev/antfu-collective/vitesse/
|
||||||
- [Vue3 入门指南与实战案例](https://vue3.chengpeiquan.com/)
|
- [Vue3 入门指南与实战案例](https://vue3.chengpeiquan.com/)
|
||||||
|
- [如何建立一个最小重现](https://antfu.me/posts/why-reproductions-are-required-zh#如何建立一个最小重现)
|
||||||
|
|
||||||
---
|
---
|
||||||
- [primevue-scopedtokens](https://primevue.org/theming/styled/#scopedtokens)
|
- [primevue-scopedtokens](https://primevue.org/theming/styled/#scopedtokens)
|
||||||
|
|
||||||
+ https://www.npmjs.com/package/npkill
|
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
import type { UserConfig } from '@commitlint/types';
|
|
||||||
|
|
||||||
|
|
||||||
const Configuration: UserConfig = {
|
|
||||||
extends: ['@commitlint/config-conventional'],
|
|
||||||
formatter: '@commitlint/format',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Configuration;
|
|
9
env.d.ts
vendored
@@ -1,9 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
/// <reference types="vitest" />
|
|
||||||
/// <reference types="vite-plugin-vue-layouts/client" />
|
|
||||||
/// <reference types="vite-plugin-vue-meta-layouts/client" />
|
|
||||||
/* /// <reference types="vite-plugin-pwa/client" /> */
|
|
||||||
/// <reference types="unplugin-vue-macros/macros-global" />
|
|
||||||
/// <reference types="unplugin-vue-router/client" />
|
|
||||||
/// <reference types="unplugin-icons/types/vue" />
|
|
||||||
/// <reference types="@intlify/unplugin-vue-i18n/messages" />
|
|
@@ -6,9 +6,8 @@ import pluginVitest from '@vitest/eslint-plugin';
|
|||||||
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
|
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
|
||||||
import { configureVueProject, defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
|
import { configureVueProject, defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
|
||||||
import { flatConfigs as eslintPluginImportX_flatConfigs } from 'eslint-plugin-import-x';
|
import { flatConfigs as eslintPluginImportX_flatConfigs } from 'eslint-plugin-import-x';
|
||||||
import pluginOxlint from 'eslint-plugin-oxlint';
|
import oxlint from 'eslint-plugin-oxlint';
|
||||||
import perfectionist from 'eslint-plugin-perfectionist';
|
import perfectionist from 'eslint-plugin-perfectionist';
|
||||||
import pluginPlaywright from 'eslint-plugin-playwright';
|
|
||||||
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
|
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
|
||||||
import pluginVue from 'eslint-plugin-vue';
|
import pluginVue from 'eslint-plugin-vue';
|
||||||
|
|
||||||
@@ -17,13 +16,15 @@ configureVueProject({ scriptLangs: ['ts', 'tsx', 'js', 'jsx'] });
|
|||||||
|
|
||||||
const _ignores = [
|
const _ignores = [
|
||||||
// >>>
|
// >>>
|
||||||
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
(await import('@eslint/compat')).includeIgnoreFile(
|
(await import('@eslint/compat')).includeIgnoreFile(
|
||||||
// eslint-disable-next-line unicorn/import-style
|
// eslint-disable-next-line unicorn/import-style, unicorn/no-await-expression-member
|
||||||
(await import('node:path')).default.resolve(import.meta.dirname, '.gitignore'),
|
(await import('node:path')).default.resolve(import.meta.dirname, '.gitignore'),
|
||||||
),
|
),
|
||||||
// <<<
|
// <<<
|
||||||
|
|
||||||
// >>>
|
// >>>
|
||||||
|
// eslint-disable-next-line unicorn/no-await-expression-member
|
||||||
(await import('eslint/config')).globalIgnores([
|
(await import('eslint/config')).globalIgnores([
|
||||||
'**/dist/**',
|
'**/dist/**',
|
||||||
'**/dist-ssr/**',
|
'**/dist-ssr/**',
|
||||||
@@ -43,7 +44,6 @@ const _ignores = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default defineConfigWithVueTs(
|
export default defineConfigWithVueTs(
|
||||||
// >>> create vue >>>
|
|
||||||
{
|
{
|
||||||
name: 'app/files-to-lint',
|
name: 'app/files-to-lint',
|
||||||
files: ['**/*.{ts,mts,tsx,vue}'],
|
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||||
@@ -59,13 +59,8 @@ export default defineConfigWithVueTs(
|
|||||||
files: ['src/**/__tests__/*'],
|
files: ['src/**/__tests__/*'],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
...oxlint.configs['flat/recommended'],
|
||||||
...pluginPlaywright.configs['flat/recommended'],
|
|
||||||
files: ['e2e/playwright/**/*.{test,spec}.{js,ts,jsx,tsx}'],
|
|
||||||
},
|
|
||||||
...pluginOxlint.configs['flat/recommended'],
|
|
||||||
skipFormatting,
|
skipFormatting,
|
||||||
// <<< create vue <<<
|
|
||||||
|
|
||||||
// region >> eslint-plugin-unicorn >>
|
// region >> eslint-plugin-unicorn >>
|
||||||
eslintPluginUnicorn.configs.recommended,
|
eslintPluginUnicorn.configs.recommended,
|
||||||
@@ -77,7 +72,6 @@ export default defineConfigWithVueTs(
|
|||||||
'unicorn/no-useless-spread': 'off',
|
'unicorn/no-useless-spread': 'off',
|
||||||
'unicorn/prevent-abbreviations': 'off',
|
'unicorn/prevent-abbreviations': 'off',
|
||||||
'unicorn/relative-url-style': 'off', // [plugin:vite:import-glob] Invalid glob: "imgs/*.png" (resolved: "imgs/*.png"). It must start with '/' or './'
|
'unicorn/relative-url-style': 'off', // [plugin:vite:import-glob] Invalid glob: "imgs/*.png" (resolved: "imgs/*.png"). It must start with '/' or './'
|
||||||
'unicorn/no-await-expression-member': 'off',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// endregion <<< eslint-plugin-unicorn <<<
|
// endregion <<< eslint-plugin-unicorn <<<
|
||||||
@@ -90,8 +84,6 @@ export default defineConfigWithVueTs(
|
|||||||
'import-x/newline-after-import': 'error',
|
'import-x/newline-after-import': 'error',
|
||||||
'import-x/first': 'error',
|
'import-x/first': 'error',
|
||||||
'import-x/no-named-as-default': 'off',
|
'import-x/no-named-as-default': 'off',
|
||||||
'import-x/no-duplicates': 'off',
|
|
||||||
'import-x/named': 'off',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// endregion <<< eslint-plugin-import-x <<<
|
// endregion <<< eslint-plugin-import-x <<<
|
||||||
@@ -107,8 +99,6 @@ export default defineConfigWithVueTs(
|
|||||||
'perfectionist/sort-imports': ['error'],
|
'perfectionist/sort-imports': ['error'],
|
||||||
'perfectionist/sort-modules': 'off',
|
'perfectionist/sort-modules': 'off',
|
||||||
'perfectionist/sort-object-types': 'off',
|
'perfectionist/sort-object-types': 'off',
|
||||||
'perfectionist/sort-enums': 'off',
|
|
||||||
'perfectionist/sort-union-types': 'off',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// endregion <<< eslint-plugin-perfectionist <<<
|
// endregion <<< eslint-plugin-perfectionist <<<
|
||||||
|
120
fake/xml.fake.ts
@@ -1,120 +0,0 @@
|
|||||||
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
|
|
||||||
|
|
||||||
// 简单的实体转义,避免在 XML 中出现非法字符;先替换 & 再替换其他符号,防止二次转义
|
|
||||||
function esc(s: string) {
|
|
||||||
return s
|
|
||||||
.replaceAll('&', '&')
|
|
||||||
.replaceAll('<', '<')
|
|
||||||
.replaceAll('>', '>')
|
|
||||||
.replaceAll('"', '"')
|
|
||||||
.replaceAll("'", ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从原始 XML 文本中提取某个标签的文本内容(弱解析,仅用于演示)
|
|
||||||
function pickTag(xml: string, tag: string) {
|
|
||||||
// 说明:使用非贪婪匹配并允许标签上带属性;N.B. 未处理命名空间与嵌套同名标签的复杂情形
|
|
||||||
const re = new RegExp(`<${tag}(?:\n|\r|s|>|/)[^>]*>([sS]*?)</${tag}>`, 'i');
|
|
||||||
const m = xml.match(re);
|
|
||||||
return m?.[1]?.trim() ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过 rawResponse 返回 XML 文本,便于在浏览器端演示 XML 解析
|
|
||||||
export default defineFakeRoute([
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
url: '/xml/sample',
|
|
||||||
rawResponse(req, res) {
|
|
||||||
// 通过查询参数自定义返回的 XML 字段
|
|
||||||
// 支持的参数:to, from, heading, body, id, createdAt
|
|
||||||
const url = new URL(req.url!, 'http://localhost');
|
|
||||||
const q = url.searchParams;
|
|
||||||
const to = q.get('to') ?? 'George';
|
|
||||||
const from = q.get('from') ?? 'John';
|
|
||||||
const heading = q.get('heading') ?? 'Reminder';
|
|
||||||
const body = q.get('body') ?? "Don't forget the meeting at 3 PM today.";
|
|
||||||
const id = q.get('id') ?? '42';
|
|
||||||
const createdAt = q.get('createdAt') ?? '2025-09-02T10:00:00Z';
|
|
||||||
|
|
||||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<note>
|
|
||||||
<to>${esc(to)}</to>
|
|
||||||
<from>${esc(from)}</from>
|
|
||||||
<heading>${esc(heading)}</heading>
|
|
||||||
<body>${esc(body)}</body>
|
|
||||||
<meta>
|
|
||||||
<id>${esc(id)}</id>
|
|
||||||
<createdAt>${esc(createdAt)}</createdAt>
|
|
||||||
</meta>
|
|
||||||
</note>`;
|
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/xml; charset=UTF-8' });
|
|
||||||
res.end(xml);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
url: '/xml/submit',
|
|
||||||
rawResponse(req, res) {
|
|
||||||
const chunks: Buffer[] = [];
|
|
||||||
req.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
||||||
req.on('end', () => {
|
|
||||||
const contentType = String(req.headers['content-type'] || '').toLowerCase();
|
|
||||||
const raw = Buffer.concat(chunks).toString('utf8');
|
|
||||||
|
|
||||||
// 默认值,与 GET 一致
|
|
||||||
let to = 'George';
|
|
||||||
let from = 'John';
|
|
||||||
let heading = 'Reminder';
|
|
||||||
let body = "Don't forget the meeting at 3 PM today.";
|
|
||||||
let id = '42';
|
|
||||||
let createdAt = new Date().toISOString();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (contentType.includes('application/json')) {
|
|
||||||
const data = JSON.parse(raw || '{}');
|
|
||||||
to = data.to ?? to;
|
|
||||||
from = data.from ?? from;
|
|
||||||
heading = data.heading ?? heading;
|
|
||||||
body = data.body ?? body;
|
|
||||||
id = data.id ?? id;
|
|
||||||
createdAt = data.createdAt ?? createdAt;
|
|
||||||
} else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
|
|
||||||
// 解析 XML 请求体中常用字段(演示用,未处理命名空间/CDATA 等复杂场景)
|
|
||||||
to = pickTag(raw, 'to') || to;
|
|
||||||
from = pickTag(raw, 'from') || from;
|
|
||||||
heading = pickTag(raw, 'heading') || heading;
|
|
||||||
body = pickTag(raw, 'body') || body;
|
|
||||||
id = pickTag(raw, 'id') || id;
|
|
||||||
createdAt = pickTag(raw, 'createdAt') || createdAt;
|
|
||||||
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
||||||
// 简单解析表单:key=value&…;此处演示用途,未处理复杂编码场景
|
|
||||||
const params = new URLSearchParams(raw);
|
|
||||||
to = params.get('to') ?? to;
|
|
||||||
from = params.get('from') ?? from;
|
|
||||||
heading = params.get('heading') ?? heading;
|
|
||||||
body = params.get('body') ?? body;
|
|
||||||
id = params.get('id') ?? id;
|
|
||||||
createdAt = params.get('createdAt') ?? createdAt;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// 忽略解析错误,保持默认值
|
|
||||||
}
|
|
||||||
|
|
||||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<note>
|
|
||||||
<to>${esc(to)}</to>
|
|
||||||
<from>${esc(from)}</from>
|
|
||||||
<heading>${esc(heading)}</heading>
|
|
||||||
<body>${esc(body)}</body>
|
|
||||||
<meta>
|
|
||||||
<id>${esc(id)}</id>
|
|
||||||
<createdAt>${esc(createdAt)}</createdAt>
|
|
||||||
</meta>
|
|
||||||
</note>`;
|
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/xml; charset=UTF-8' });
|
|
||||||
res.end(xml);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
185
package.json
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"packageManager": "pnpm@10.15.1",
|
"packageManager": "pnpm@10.8.0",
|
||||||
"name": "vue-ts-example",
|
"name": "vue-ts-example",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -9,17 +9,15 @@
|
|||||||
"all": "run-p build-only format type-check lint",
|
"all": "run-p build-only format type-check lint",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"preview": "vite preview --port 4173",
|
|
||||||
"lint-format": "run-p lint:oxlint lint:eslint format",
|
"lint-format": "run-p lint:oxlint lint:eslint format",
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write src/",
|
||||||
"type-check": "vue-tsc --build",
|
"type-check": "vue-tsc --build",
|
||||||
"lint": "run-s lint:*",
|
"lint": "run-s lint:*",
|
||||||
"_oxlint_cfg": "oxlint . --fix --ignore-path=.gitignore --print-config",
|
"_oxlint_cfg": "oxlint . --fix --ignore-path=.gitignore --print-config",
|
||||||
"__oxlint_-D": "oxlint . --fix --deny=correctness",
|
"__oxlint_-D": "oxlint . --fix --deny=correctness",
|
||||||
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
"lint:oxlint": "oxlint --fix",
|
||||||
"lint:eslint": "eslint . --fix",
|
"lint:eslint": "eslint . --fix",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"test:unit": "vitest",
|
|
||||||
"playwright": "playwright test",
|
"playwright": "playwright test",
|
||||||
"playwright:headless": "HEADLESS=true playwright test",
|
"playwright:headless": "HEADLESS=true playwright test",
|
||||||
"playwright:ui": "playwright test --ui",
|
"playwright:ui": "playwright test --ui",
|
||||||
@@ -32,7 +30,7 @@
|
|||||||
"knip": "pnpm dlx knip"
|
"knip": "pnpm dlx knip"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"{src,e2e}/**/*.{js,jsx,ts,tsx,vue}": [
|
"src/**/*.{js,jsx,ts,tsx,vue}": [
|
||||||
"prettier --write",
|
"prettier --write",
|
||||||
"eslint --fix",
|
"eslint --fix",
|
||||||
"oxlint --fix"
|
"oxlint --fix"
|
||||||
@@ -41,141 +39,126 @@
|
|||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"vite": "$vite",
|
"vite": "$vite",
|
||||||
|
"vue-tsc": "$vue-tsc",
|
||||||
"@primevue/auto-import-resolver": "$primevue"
|
"@primevue/auto-import-resolver": "$primevue"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alova/adapter-axios": "^2.0.16",
|
"@alova/adapter-axios": "^2.0.13",
|
||||||
"@ant-design/icons-vue": "^7.0.1",
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@formkit/auto-animate": "^0.8.4",
|
"@intlify/unplugin-vue-i18n": "^6.0.6",
|
||||||
"@intlify/unplugin-vue-i18n": "^11.0.0",
|
"@pinia/colada": "^0.14.2",
|
||||||
"@pinia/colada": "^0.17.3",
|
"@primeuix/themes": "^1.0.3",
|
||||||
"@primeuix/themes": "^1.2.3",
|
"@splinetool/runtime": "^1.9.82",
|
||||||
"@primevue/icons": "^4.3.8",
|
|
||||||
"@splinetool/runtime": "^1.10.53",
|
|
||||||
"@types/p5": "^1.7.6",
|
"@types/p5": "^1.7.6",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
"@unhead/vue": "^2.0.14",
|
"@unhead/vue": "^2.0.5",
|
||||||
"@vant/use": "^1.6.0",
|
"@vant/use": "^1.6.0",
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
"alova": "^3.3.4",
|
"alova": "^3.2.10",
|
||||||
"ant-design-vue": "~4.2.6",
|
"ant-design-vue": "~4.2.6",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.8.4",
|
||||||
"cesium": "^1.132.0",
|
"cesium": "^1.128.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"consola": "^3.4.2",
|
"consola": "^3.4.2",
|
||||||
"dayjs": "^1.11.18",
|
"dayjs": "^1.11.13",
|
||||||
"deep-freeze-es6": "^4.0.1",
|
"deep-freeze-es6": "^4.0.0",
|
||||||
"jsencrypt": "^3.5.4",
|
"jsencrypt": "^3.3.2",
|
||||||
"lucide-vue-next": "^0.542.0",
|
"lucide-vue-next": "^0.487.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"p5": "^2.0.5",
|
"p5": "^1.11.3",
|
||||||
"page-stack-vue3": "^2.5.6",
|
"page-stack-vue3": "^2.5.6",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.1",
|
||||||
"pinia-plugin-persistedstate": "^4.5.0",
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
"plotly.js-dist-min": "^3.1.0",
|
"plotly.js-dist-min": "^3.0.1",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primelocale": "^2.1.7",
|
"primelocale": "^2.1.2",
|
||||||
"primevue": "^4.3.8",
|
"primevue": "^4.3.3",
|
||||||
"radash": "^12.1.1",
|
"radash": "^12.1.0",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
"reka-ui": "^2.5.0",
|
"reka-ui": "^2.2.0",
|
||||||
"satellite.js": "^6.0.1",
|
"satellite.js": "^6.0.0",
|
||||||
"sortablejs": "^1.15.6",
|
"sortablejs": "^1.15.6",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tdesign-icons-vue-next": "^0.3.7",
|
"tdesign-icons-vue-next": "^0.3.5",
|
||||||
"three": "^0.179.1",
|
"three": "^0.175.0",
|
||||||
"ts-enum-util": "^4.1.0",
|
"ts-enum-util": "^4.1.0",
|
||||||
"utils4u": "^4.2.3",
|
"utils4u": "^4.2.3",
|
||||||
"vant": "^4.9.21",
|
"vant": "^4.9.18",
|
||||||
"vite-plugin-image-optimizer": "^2.0.2",
|
"vue": "^3.5.13",
|
||||||
"vue": "^3.5.20",
|
|
||||||
"vue-data-ui": "^3.0.15",
|
|
||||||
"vue-draggable-plus": "^0.6.0",
|
"vue-draggable-plus": "^0.6.0",
|
||||||
"vue-i18n": "^11.1.11",
|
"vue-i18n": "^11.1.3",
|
||||||
"vue-page-stack": "^3.2.0",
|
"vue-page-stack": "^3.2.0",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.0",
|
||||||
|
"vuetify": "^3.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^19.8.1",
|
"@eslint/compat": "^1.2.8",
|
||||||
"@commitlint/config-conventional": "^19.8.1",
|
"@faker-js/faker": "^9.6.0",
|
||||||
"@eslint/compat": "^1.3.2",
|
"@iconify-json/carbon": "^1.2.8",
|
||||||
"@faker-js/faker": "^10.0.0",
|
"@iconify-json/logos": "^1.2.4",
|
||||||
"@iconify-json/carbon": "^1.2.13",
|
|
||||||
"@iconify-json/logos": "^1.2.9",
|
|
||||||
"@iconify-json/mdi": "^1.2.3",
|
"@iconify-json/mdi": "^1.2.3",
|
||||||
"@iconify/utils": "^3.0.1",
|
"@iconify/utils": "^2.3.0",
|
||||||
"@playwright/test": "^1.55.0",
|
"@playwright/test": "^1.51.1",
|
||||||
"@prettier/plugin-oxc": "^0.0.4",
|
"@primevue/auto-import-resolver": "^4.3.3",
|
||||||
"@primevue/auto-import-resolver": "^4.3.8",
|
"@tsconfig/node22": "^22.0.1",
|
||||||
"@primevue/metadata": "^4.3.8",
|
|
||||||
"@tsconfig/node22": "^22.0.2",
|
|
||||||
"@types/archiver": "^6.0.3",
|
"@types/archiver": "^6.0.3",
|
||||||
"@types/jsdom": "^21.1.7",
|
|
||||||
"@types/mockjs": "^1.0.10",
|
"@types/mockjs": "^1.0.10",
|
||||||
"@types/node": "^24.3.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@types/plotly.js-dist-min": "^2.3.4",
|
"@types/plotly.js-dist-min": "^2.3.4",
|
||||||
"@types/three": "^0.180.0",
|
"@types/three": "^0.175.0",
|
||||||
"@unocss/preset-attributify": "^66.5.0",
|
|
||||||
"@unocss/reset": "^66.5.0",
|
|
||||||
"@vant/auto-import-resolver": "^1.3.0",
|
"@vant/auto-import-resolver": "^1.3.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
"@vitejs/plugin-vue-jsx": "^4.1.2",
|
||||||
"@vitest/eslint-plugin": "^1.3.6",
|
"@vitest/eslint-plugin": "^1.1.40",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@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/test-utils": "^2.4.6",
|
||||||
"@vue/tsconfig": "^0.8.1",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"create-vue": "3.18.0",
|
|
||||||
"depcheck": "^1.4.7",
|
"depcheck": "^1.4.7",
|
||||||
"eruda": "^3.4.3",
|
"eruda": "^3.4.1",
|
||||||
"eslint": "^9.34.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-plugin-import-x": "^4.16.1",
|
"eslint-plugin-import-x": "^4.10.2",
|
||||||
"eslint-plugin-oxlint": "^1.14.0",
|
"eslint-plugin-oxlint": "^0.16.6",
|
||||||
"eslint-plugin-perfectionist": "^4.15.0",
|
"eslint-plugin-perfectionist": "^4.11.0",
|
||||||
"eslint-plugin-playwright": "^2.2.2",
|
"eslint-plugin-unicorn": "^58.0.0",
|
||||||
"eslint-plugin-unicorn": "^61.0.2",
|
"eslint-plugin-vue": "^10.0.0",
|
||||||
"eslint-plugin-vue": "^10.4.0",
|
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jiti": "^2.5.1",
|
"less": "^4.3.0",
|
||||||
"jsdom": "^26.1.0",
|
"lint-staged": "^15.5.0",
|
||||||
"less": "^4.4.1",
|
|
||||||
"lint-staged": "^16.1.6",
|
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"naive-ui": "^2.42.0",
|
"npm-run-all2": "^7.0.2",
|
||||||
"npm-run-all2": "^8.0.4",
|
"oxlint": "^0.16.6",
|
||||||
"oxlint": "~1.14.0",
|
"prettier": "3.5.3",
|
||||||
"prettier": "3.6.2",
|
"sass-embedded": "^1.86.3",
|
||||||
"spritesmith": "^3.5.1",
|
"terser": "^5.39.0",
|
||||||
"typescript": "~5.9.2",
|
"typescript": "~5.8.3",
|
||||||
"unocss": "66.5.0",
|
"unocss": "66.1.0-beta.10",
|
||||||
"unocss-preset-animations": "^1.2.1",
|
"unocss-preset-animations": "^1.1.1",
|
||||||
"unocss-preset-chinese": "^0.3.3",
|
"unocss-preset-chinese": "^0.3.3",
|
||||||
"unocss-preset-shadcn": "^0.5.0",
|
"unocss-preset-shadcn": "^0.5.0",
|
||||||
"unplugin-auto-import": "^20.1.0",
|
"unplugin-auto-import": "^19.1.2",
|
||||||
"unplugin-icons": "^22.2.0",
|
"unplugin-icons": "^22.1.0",
|
||||||
"unplugin-vue-components": "^29.0.0",
|
"unplugin-vue-components": "^28.5.0",
|
||||||
"unplugin-vue-macros": "^2.14.5",
|
"unplugin-vue-macros": "^2.14.5",
|
||||||
"unplugin-vue-markdown": "^29.1.0",
|
"unplugin-vue-markdown": "^28.3.1",
|
||||||
"unplugin-vue-router": "^0.15.0",
|
"unplugin-vue-router": "^0.12.0",
|
||||||
"vfonts": "^0.0.3",
|
"vite": "^6.2.6",
|
||||||
"vite": "npm:rolldown-vite@^7.1.5",
|
"vite-plugin-checker": "^0.9.1",
|
||||||
"vite-plugin-checker": "^0.10.3",
|
|
||||||
"vite-plugin-fake-server": "^2.2.0",
|
"vite-plugin-fake-server": "^2.2.0",
|
||||||
"vite-plugin-image-tools": "^3.0.0",
|
"vite-plugin-purgecss-updated-v5": "^1.2.4",
|
||||||
"vite-plugin-purgecss-updated-v5": "^1.2.6",
|
"vite-plugin-singlefile": "^2.2.0",
|
||||||
"vite-plugin-singlefile": "^2.3.0",
|
"vite-plugin-static-copy": "^2.3.1",
|
||||||
"vite-plugin-static-copy": "^3.1.2",
|
"vite-plugin-vue-devtools": "^7.7.2",
|
||||||
"vite-plugin-vue-devtools": "^8.0.1",
|
|
||||||
"vite-plugin-vue-layouts": "^0.11.0",
|
"vite-plugin-vue-layouts": "^0.11.0",
|
||||||
"vite-plugin-vue-meta-layouts": "^0.6.0",
|
"vite-plugin-vue-meta-layouts": "^0.5.1",
|
||||||
"vite-plugin-webfont-dl": "^3.11.1",
|
"vite-plugin-vuetify": "^2.1.1",
|
||||||
"vitest": "^3.2.4",
|
"vite-plugin-webfont-dl": "^3.10.4",
|
||||||
"vue-component-type-helpers": "^3.0.6",
|
"vue-component-type-helpers": "^2.2.8",
|
||||||
"vue-tsc": "^3.0.6"
|
"vue-tsc": "^2.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
|
||||||
const runningInVSCode = process.env.TERM_PROGRAM === 'vscode';
|
|
||||||
const baseURL = runningInVSCode ? 'http://localhost:4173' : process.env.BASE_URL || 'https://vue-ts-example.oo1.dev';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read environment variables from file.
|
* Read environment variables from file.
|
||||||
* https://github.com/motdotla/dotenv
|
* https://github.com/motdotla/dotenv
|
||||||
@@ -61,7 +58,7 @@ export default defineConfig({
|
|||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
testDir: './e2e/playwright',
|
testDir: './tests/playwright',
|
||||||
/* Maximum time one test can run for. */
|
/* Maximum time one test can run for. */
|
||||||
timeout: 30 * 1000,
|
timeout: 30 * 1000,
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
@@ -69,7 +66,7 @@ export default defineConfig({
|
|||||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||||
actionTimeout: 0,
|
actionTimeout: 0,
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
baseURL,
|
baseURL: process.env.BASE_URL || 'https://vue-ts-example.oo1.dev',
|
||||||
|
|
||||||
/* Only on CI systems run the tests headless */
|
/* Only on CI systems run the tests headless */
|
||||||
headless: !!process.env.CI || process.env.HEADLESS === 'true',
|
headless: !!process.env.CI || process.env.HEADLESS === 'true',
|
||||||
@@ -79,13 +76,16 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// /* Run your local dev server before starting the tests */
|
// /* Run your local dev server before starting the tests */
|
||||||
webServer: runningInVSCode
|
// webServer: {
|
||||||
? {
|
// /**
|
||||||
command: 'pnpm run build-only; pnpm run preview',
|
// * Use the dev server by default for faster feedback loop.
|
||||||
port: 4173,
|
// * Use the preview server on CI for more realistic testing.
|
||||||
reuseExistingServer: true,
|
// * Playwright will re-use the local server if there is already a dev-server running.
|
||||||
}
|
// */
|
||||||
: undefined,
|
// command: process.env.CI ? 'npm run preview' : 'npm run dev',
|
||||||
|
// port: process.env.CI ? 4173 : 5173,
|
||||||
|
// reuseExistingServer: !process.env.CI,
|
||||||
|
// },
|
||||||
|
|
||||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||||
// outputDir: 'test-results/',
|
// outputDir: 'test-results/',
|
||||||
|
9162
pnpm-lock.yaml
generated
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["https://git.1-h.cc/examples/renovate-example/raw/branch/main/default.json5", ":automergeMinor"],
|
"extends": ["https://git.1-h.cc/examples/renovate-example/raw/branch/main/default.json5", ":automergeMinor"]
|
||||||
"postUpdateOptions": ["pnpmDedupe"]
|
|
||||||
}
|
}
|
||||||
|
@@ -15,11 +15,9 @@ const themeConfig = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<n-config-provider preflight-style-disabled>
|
<a-config-provider :theme="themeConfig">
|
||||||
<a-config-provider :theme="themeConfig">
|
<RouterView />
|
||||||
<RouterView />
|
</a-config-provider>
|
||||||
</a-config-provider>
|
|
||||||
</n-config-provider>
|
|
||||||
|
|
||||||
<DynamicDialog /> <ConfirmDialog /> <Toast />
|
<DynamicDialog /> <ConfirmDialog /> <Toast />
|
||||||
</template>
|
</template>
|
||||||
|
@@ -72,9 +72,9 @@ const orbitRadii = new Array(SPHERE_COUNT)
|
|||||||
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
||||||
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
||||||
const positions: [number, number, number][] = orbitRadii.map((rad, i) => [
|
const positions: [number, number, number][] = orbitRadii.map((rad, i) => [
|
||||||
rad * cos(thetas[i]!) * sin(phis[i]!),
|
rad * cos(thetas[i]) * sin(phis[i]),
|
||||||
rad * sin(thetas[i]!) * sin(phis[i]!),
|
rad * sin(thetas[i]) * sin(phis[i]),
|
||||||
rad * cos(phis[i]!),
|
rad * cos(phis[i]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const sphereGeometry = new SphereGeometry(SPHERE_SCALE_COEFF);
|
const sphereGeometry = new SphereGeometry(SPHERE_SCALE_COEFF);
|
||||||
@@ -93,7 +93,7 @@ const bgMaterial = getGradientMaterial(
|
|||||||
BG_COLOR_BOTTOM_ORANGISH,
|
BG_COLOR_BOTTOM_ORANGISH,
|
||||||
BG_COLOR_TOP_ORANGISH,
|
BG_COLOR_TOP_ORANGISH,
|
||||||
);
|
);
|
||||||
bgMaterial.uniforms.uTemperatureVariancePeriod!.value = new Vector3(0, 0, 0.1);
|
bgMaterial.uniforms.uTemperatureVariancePeriod.value = new Vector3(0, 0, 0.1);
|
||||||
|
|
||||||
function seededRandom(a: number) {
|
function seededRandom(a: number) {
|
||||||
return function () {
|
return function () {
|
||||||
@@ -234,17 +234,17 @@ function createScene() {
|
|||||||
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
const thetas = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
||||||
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
const phis = new Array(SPHERE_COUNT).fill(0).map(() => randRange(PI2));
|
||||||
const positions = orbitRadii.map((rad, i) => [
|
const positions = orbitRadii.map((rad, i) => [
|
||||||
rad * cos(thetas[i]!) * sin(phis[i]!),
|
rad * cos(thetas[i]) * sin(phis[i]),
|
||||||
rad * sin(thetas[i]!) * sin(phis[i]!),
|
rad * sin(thetas[i]) * sin(phis[i]),
|
||||||
rad * cos(phis[i]!),
|
rad * cos(phis[i]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (let i = 0; i < SPHERE_COUNT; i++) {
|
for (let i = 0; i < SPHERE_COUNT; i++) {
|
||||||
const sphere = new Mesh(sphereGeometry, sphereMaterial);
|
const sphere = new Mesh(sphereGeometry, sphereMaterial);
|
||||||
const [x, y, z] = positions[i]!;
|
const [x, y, z] = positions[i];
|
||||||
const scaleVector = sizes[i];
|
const scaleVector = sizes[i];
|
||||||
sphere.scale.set(scaleVector!, scaleVector!, scaleVector!);
|
sphere.scale.set(scaleVector, scaleVector, scaleVector);
|
||||||
sphere.position.set(x!, y!, z!);
|
sphere.position.set(x, y, z);
|
||||||
spheres.push(sphere);
|
spheres.push(sphere);
|
||||||
scene.add(sphere);
|
scene.add(sphere);
|
||||||
}
|
}
|
||||||
@@ -258,11 +258,11 @@ function animate() {
|
|||||||
const elapsed = clock.getElapsedTime();
|
const elapsed = clock.getElapsedTime();
|
||||||
const temperature = sin(elapsed * 0.5) * 0.5 + 0.5;
|
const temperature = sin(elapsed * 0.5) * 0.5 + 0.5;
|
||||||
|
|
||||||
bgMaterial.uniforms.uTemperature!.value = temperature;
|
bgMaterial.uniforms.uTemperature.value = temperature;
|
||||||
bgMaterial.uniforms.uElapsedTime!.value = elapsed;
|
bgMaterial.uniforms.uElapsedTime.value = elapsed;
|
||||||
|
|
||||||
sphereMaterial.uniforms.uTemperature!.value = temperature;
|
sphereMaterial.uniforms.uTemperature.value = temperature;
|
||||||
sphereMaterial.uniforms.uElapsedTime!.value = elapsed;
|
sphereMaterial.uniforms.uElapsedTime.value = elapsed;
|
||||||
|
|
||||||
// Floating effect for spheres
|
// Floating effect for spheres
|
||||||
spheres.forEach((sphere, index) => {
|
spheres.forEach((sphere, index) => {
|
||||||
@@ -270,7 +270,7 @@ function animate() {
|
|||||||
const floatFactor = 2; // Adjust this value to control float intensity
|
const floatFactor = 2; // Adjust this value to control float intensity
|
||||||
const speed = 0.3; // Adjust this value to control float speed
|
const speed = 0.3; // Adjust this value to control float speed
|
||||||
const floatY = sin(elapsed * speed + index) * floatFactor;
|
const floatY = sin(elapsed * speed + index) * floatFactor;
|
||||||
sphere.position.y = basePosition![1] + floatY;
|
sphere.position.y = basePosition[1] + floatY;
|
||||||
});
|
});
|
||||||
|
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
|
@@ -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 = '+/-',
|
|
||||||
}
|
|
@@ -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; /* 推荐 */
|
|
||||||
}
|
|
@@ -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>
|
|
Before Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 705 KiB |
Before Width: | Height: | Size: 810 KiB |
@@ -25,7 +25,7 @@ export class HCesiumManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.viewer = new Cesium.Viewer(container, VIEWER_OPTIONS_FN());
|
this.viewer = new Cesium.Viewer(container, VIEWER_OPTIONS_FN());
|
||||||
if (__DEV__) Object.assign(globalThis, { viewer: this.viewer });
|
if ($__DEV__) Object.assign(globalThis, { viewer: this.viewer });
|
||||||
|
|
||||||
configureTimeLine(this.viewer);
|
configureTimeLine(this.viewer);
|
||||||
|
|
||||||
|
@@ -6,9 +6,9 @@ import type { I卫星 } from './HCesiumManager.types';
|
|||||||
import { type OrbitCalculationResult, SatelliteCalculator } from '../calculators/SatelliteCalculator';
|
import { type OrbitCalculationResult, SatelliteCalculator } from '../calculators/SatelliteCalculator';
|
||||||
|
|
||||||
interface ManagedSatelliteEntities {
|
interface ManagedSatelliteEntities {
|
||||||
coverageEntity?: Cesium.Entity | null;
|
coverageEntity?: Cesium.Entity;
|
||||||
mainEntity: Cesium.Entity;
|
mainEntity: Cesium.Entity;
|
||||||
orbitEntity?: Cesium.Entity | null;
|
orbitEntity?: Cesium.Entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class HCesiumSatelliteManager {
|
export class HCesiumSatelliteManager {
|
||||||
@@ -108,8 +108,8 @@ export class HCesiumSatelliteManager {
|
|||||||
// 存储实体引用
|
// 存储实体引用
|
||||||
this.currentSatelliteEntities.set(id, {
|
this.currentSatelliteEntities.set(id, {
|
||||||
mainEntity: addedMainEntity, // 存储实际添加成功的实体
|
mainEntity: addedMainEntity, // 存储实际添加成功的实体
|
||||||
orbitEntity: addedOrbitEntity,
|
orbitEntity: addedOrbitEntity ?? undefined,
|
||||||
coverageEntity: addedCoverageEntity,
|
coverageEntity: addedCoverageEntity ?? undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,10 +124,21 @@ export class HCesiumSatelliteManager {
|
|||||||
options: I卫星,
|
options: I卫星,
|
||||||
): Cesium.Entity {
|
): Cesium.Entity {
|
||||||
// 动态轨迹路径 (Path) - 注意:这与完整轨道线 (Polyline) 不同
|
// 动态轨迹路径 (Path) - 注意:这与完整轨道线 (Polyline) 不同
|
||||||
|
const path: Cesium.PathGraphics.ConstructorOptions = {
|
||||||
|
resolution: 1,
|
||||||
|
material: new Cesium.PolylineGlowMaterialProperty({
|
||||||
|
glowPower: 0.15,
|
||||||
|
color: randomBaseColor,
|
||||||
|
}),
|
||||||
|
width: 2,
|
||||||
|
leadTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
|
||||||
|
trailTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
|
||||||
|
};
|
||||||
|
|
||||||
const entityOptions: Cesium.Entity.ConstructorOptions = {
|
return new Cesium.Entity({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
|
path: options.showPath ? path : undefined, // 根据 options.showPath 控制是否显示路径
|
||||||
position: sampledPositionProperty, // 使用计算好的位置属性
|
position: sampledPositionProperty, // 使用计算好的位置属性
|
||||||
orientation: new Cesium.VelocityOrientationProperty(sampledPositionProperty),
|
orientation: new Cesium.VelocityOrientationProperty(sampledPositionProperty),
|
||||||
point: {
|
point: {
|
||||||
@@ -146,22 +157,7 @@ export class HCesiumSatelliteManager {
|
|||||||
fillColor: Cesium.Color.WHITE,
|
fillColor: Cesium.Color.WHITE,
|
||||||
outlineColor: Cesium.Color.BLACK,
|
outlineColor: Cesium.Color.BLACK,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
if (options.showPath) {
|
|
||||||
entityOptions.path = {
|
|
||||||
resolution: 1,
|
|
||||||
material: new Cesium.PolylineGlowMaterialProperty({
|
|
||||||
glowPower: 0.15,
|
|
||||||
color: randomBaseColor,
|
|
||||||
}),
|
|
||||||
width: 2,
|
|
||||||
leadTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
|
|
||||||
trailTime: (options.orbitDurationSeconds ?? 3600 * 2) / 2, // 默认1小时
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Cesium.Entity(entityOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -6,7 +6,7 @@ import { HCesiumManager } from './managers/HCesiumManager';
|
|||||||
export function useHCesiumManager(containerId: string) {
|
export function useHCesiumManager(containerId: string) {
|
||||||
const hCesiumViewerManager = ref(new HCesiumManager());
|
const hCesiumViewerManager = ref(new HCesiumManager());
|
||||||
// 可以在开发模式下暴露 manager 实例,方便调试
|
// 可以在开发模式下暴露 manager 实例,方便调试
|
||||||
if (__DEV__) Object.assign(globalThis, { hCesiumViewerManager });
|
if ($__DEV__) Object.assign(globalThis, { hCesiumViewerManager });
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
hCesiumViewerManager.value.init(containerId);
|
hCesiumViewerManager.value.init(containerId);
|
||||||
|
@@ -18,32 +18,32 @@ const readyPromise = new Promise<void>((resolve) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const 频谱瀑布图Layout = {
|
const 频谱瀑布图Layout = {
|
||||||
title: { text: '频谱瀑布图' },
|
title: '频谱瀑布图',
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: { text: '频率 (Hz)' },
|
title: '频率 (Hz)',
|
||||||
// range: [0, 22_050],
|
// range: [0, 22_050],
|
||||||
showgrid: false,
|
showgrid: false,
|
||||||
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
title: { text: '时间步' },
|
title: '时间步',
|
||||||
showgrid: false,
|
showgrid: false,
|
||||||
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
||||||
},
|
},
|
||||||
margin: { l: 60, r: 40, b: 40, t: 60 },
|
margin: { l: 60, r: 40, b: 40, t: 60 },
|
||||||
} satisfies Partial<import('plotly.js-dist-min').Layout>;
|
};
|
||||||
|
|
||||||
const 频谱图Layout = {
|
const 频谱图Layout = {
|
||||||
title: { text: '频谱图' },
|
title: '频谱图',
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: { text: '频率 (Hz)' },
|
title: '频率 (Hz)',
|
||||||
// range: [0, 22_050],
|
// range: [0, 22_050],
|
||||||
showgrid: true,
|
showgrid: true,
|
||||||
gridcolor: '#eee',
|
gridcolor: '#eee',
|
||||||
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
title: { text: '幅度 (dB)' },
|
title: '幅度 (dB)',
|
||||||
showgrid: true, // 显示网格线
|
showgrid: true, // 显示网格线
|
||||||
gridcolor: '#eee',
|
gridcolor: '#eee',
|
||||||
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
tickformat: ',d', // 设置为带逗号的整数格式 (推荐,更易读)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { isActive: isCounting, remaining: countdownTime, start: startCountdown } = useCountdown(__DEV__ ? 3 : 60);
|
const { isActive: isCounting, remaining: countdownTime, start: startCountdown } = useCountdown($__DEV__ ? 3 : 60);
|
||||||
|
|
||||||
const isSending = ref(false);
|
const isSending = ref(false);
|
||||||
const sendSms = async () => {
|
const sendSms = async () => {
|
||||||
|
@@ -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>
|
|
605
src/layouts/sakai-vue/AppConfigurator.vue
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { $t, updatePreset, updateSurfacePalette } from '@primeuix/themes';
|
||||||
|
import Aura from '@primeuix/themes/aura';
|
||||||
|
import Lara from '@primeuix/themes/lara';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useLayout } from './composables/layout';
|
||||||
|
|
||||||
|
const { isDarkTheme, layoutConfig } = useLayout();
|
||||||
|
const presets = {
|
||||||
|
Aura,
|
||||||
|
Lara,
|
||||||
|
};
|
||||||
|
const preset = ref(layoutConfig.preset);
|
||||||
|
const presetOptions = ref(Object.keys(presets));
|
||||||
|
|
||||||
|
const menuMode = ref(layoutConfig.menuMode);
|
||||||
|
const menuModeOptions = ref([
|
||||||
|
{ label: 'Static', value: 'static' },
|
||||||
|
{ label: 'Overlay', value: 'overlay' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const primaryColors = ref([
|
||||||
|
{ name: 'noir', palette: {} },
|
||||||
|
{
|
||||||
|
name: 'emerald',
|
||||||
|
palette: {
|
||||||
|
50: '#ecfdf5',
|
||||||
|
100: '#d1fae5',
|
||||||
|
200: '#a7f3d0',
|
||||||
|
300: '#6ee7b7',
|
||||||
|
400: '#34d399',
|
||||||
|
500: '#10b981',
|
||||||
|
600: '#059669',
|
||||||
|
700: '#047857',
|
||||||
|
800: '#065f46',
|
||||||
|
900: '#064e3b',
|
||||||
|
950: '#022c22',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'green',
|
||||||
|
palette: {
|
||||||
|
50: '#f0fdf4',
|
||||||
|
100: '#dcfce7',
|
||||||
|
200: '#bbf7d0',
|
||||||
|
300: '#86efac',
|
||||||
|
400: '#4ade80',
|
||||||
|
500: '#22c55e',
|
||||||
|
600: '#16a34a',
|
||||||
|
700: '#15803d',
|
||||||
|
800: '#166534',
|
||||||
|
900: '#14532d',
|
||||||
|
950: '#052e16',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lime',
|
||||||
|
palette: {
|
||||||
|
50: '#f7fee7',
|
||||||
|
100: '#ecfccb',
|
||||||
|
200: '#d9f99d',
|
||||||
|
300: '#bef264',
|
||||||
|
400: '#a3e635',
|
||||||
|
500: '#84cc16',
|
||||||
|
600: '#65a30d',
|
||||||
|
700: '#4d7c0f',
|
||||||
|
800: '#3f6212',
|
||||||
|
900: '#365314',
|
||||||
|
950: '#1a2e05',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'orange',
|
||||||
|
palette: {
|
||||||
|
50: '#fff7ed',
|
||||||
|
100: '#ffedd5',
|
||||||
|
200: '#fed7aa',
|
||||||
|
300: '#fdba74',
|
||||||
|
400: '#fb923c',
|
||||||
|
500: '#f97316',
|
||||||
|
600: '#ea580c',
|
||||||
|
700: '#c2410c',
|
||||||
|
800: '#9a3412',
|
||||||
|
900: '#7c2d12',
|
||||||
|
950: '#431407',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amber',
|
||||||
|
palette: {
|
||||||
|
50: '#fffbeb',
|
||||||
|
100: '#fef3c7',
|
||||||
|
200: '#fde68a',
|
||||||
|
300: '#fcd34d',
|
||||||
|
400: '#fbbf24',
|
||||||
|
500: '#f59e0b',
|
||||||
|
600: '#d97706',
|
||||||
|
700: '#b45309',
|
||||||
|
800: '#92400e',
|
||||||
|
900: '#78350f',
|
||||||
|
950: '#451a03',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'yellow',
|
||||||
|
palette: {
|
||||||
|
50: '#fefce8',
|
||||||
|
100: '#fef9c3',
|
||||||
|
200: '#fef08a',
|
||||||
|
300: '#fde047',
|
||||||
|
400: '#facc15',
|
||||||
|
500: '#eab308',
|
||||||
|
600: '#ca8a04',
|
||||||
|
700: '#a16207',
|
||||||
|
800: '#854d0e',
|
||||||
|
900: '#713f12',
|
||||||
|
950: '#422006',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'teal',
|
||||||
|
palette: {
|
||||||
|
50: '#f0fdfa',
|
||||||
|
100: '#ccfbf1',
|
||||||
|
200: '#99f6e4',
|
||||||
|
300: '#5eead4',
|
||||||
|
400: '#2dd4bf',
|
||||||
|
500: '#14b8a6',
|
||||||
|
600: '#0d9488',
|
||||||
|
700: '#0f766e',
|
||||||
|
800: '#115e59',
|
||||||
|
900: '#134e4a',
|
||||||
|
950: '#042f2e',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cyan',
|
||||||
|
palette: {
|
||||||
|
50: '#ecfeff',
|
||||||
|
100: '#cffafe',
|
||||||
|
200: '#a5f3fc',
|
||||||
|
300: '#67e8f9',
|
||||||
|
400: '#22d3ee',
|
||||||
|
500: '#06b6d4',
|
||||||
|
600: '#0891b2',
|
||||||
|
700: '#0e7490',
|
||||||
|
800: '#155e75',
|
||||||
|
900: '#164e63',
|
||||||
|
950: '#083344',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sky',
|
||||||
|
palette: {
|
||||||
|
50: '#f0f9ff',
|
||||||
|
100: '#e0f2fe',
|
||||||
|
200: '#bae6fd',
|
||||||
|
300: '#7dd3fc',
|
||||||
|
400: '#38bdf8',
|
||||||
|
500: '#0ea5e9',
|
||||||
|
600: '#0284c7',
|
||||||
|
700: '#0369a1',
|
||||||
|
800: '#075985',
|
||||||
|
900: '#0c4a6e',
|
||||||
|
950: '#082f49',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'blue',
|
||||||
|
palette: {
|
||||||
|
50: '#eff6ff',
|
||||||
|
100: '#dbeafe',
|
||||||
|
200: '#bfdbfe',
|
||||||
|
300: '#93c5fd',
|
||||||
|
400: '#60a5fa',
|
||||||
|
500: '#3b82f6',
|
||||||
|
600: '#2563eb',
|
||||||
|
700: '#1d4ed8',
|
||||||
|
800: '#1e40af',
|
||||||
|
900: '#1e3a8a',
|
||||||
|
950: '#172554',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'indigo',
|
||||||
|
palette: {
|
||||||
|
50: '#eef2ff',
|
||||||
|
100: '#e0e7ff',
|
||||||
|
200: '#c7d2fe',
|
||||||
|
300: '#a5b4fc',
|
||||||
|
400: '#818cf8',
|
||||||
|
500: '#6366f1',
|
||||||
|
600: '#4f46e5',
|
||||||
|
700: '#4338ca',
|
||||||
|
800: '#3730a3',
|
||||||
|
900: '#312e81',
|
||||||
|
950: '#1e1b4b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'violet',
|
||||||
|
palette: {
|
||||||
|
50: '#f5f3ff',
|
||||||
|
100: '#ede9fe',
|
||||||
|
200: '#ddd6fe',
|
||||||
|
300: '#c4b5fd',
|
||||||
|
400: '#a78bfa',
|
||||||
|
500: '#8b5cf6',
|
||||||
|
600: '#7c3aed',
|
||||||
|
700: '#6d28d9',
|
||||||
|
800: '#5b21b6',
|
||||||
|
900: '#4c1d95',
|
||||||
|
950: '#2e1065',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'purple',
|
||||||
|
palette: {
|
||||||
|
50: '#faf5ff',
|
||||||
|
100: '#f3e8ff',
|
||||||
|
200: '#e9d5ff',
|
||||||
|
300: '#d8b4fe',
|
||||||
|
400: '#c084fc',
|
||||||
|
500: '#a855f7',
|
||||||
|
600: '#9333ea',
|
||||||
|
700: '#7e22ce',
|
||||||
|
800: '#6b21a8',
|
||||||
|
900: '#581c87',
|
||||||
|
950: '#3b0764',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fuchsia',
|
||||||
|
palette: {
|
||||||
|
50: '#fdf4ff',
|
||||||
|
100: '#fae8ff',
|
||||||
|
200: '#f5d0fe',
|
||||||
|
300: '#f0abfc',
|
||||||
|
400: '#e879f9',
|
||||||
|
500: '#d946ef',
|
||||||
|
600: '#c026d3',
|
||||||
|
700: '#a21caf',
|
||||||
|
800: '#86198f',
|
||||||
|
900: '#701a75',
|
||||||
|
950: '#4a044e',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pink',
|
||||||
|
palette: {
|
||||||
|
50: '#fdf2f8',
|
||||||
|
100: '#fce7f3',
|
||||||
|
200: '#fbcfe8',
|
||||||
|
300: '#f9a8d4',
|
||||||
|
400: '#f472b6',
|
||||||
|
500: '#ec4899',
|
||||||
|
600: '#db2777',
|
||||||
|
700: '#be185d',
|
||||||
|
800: '#9d174d',
|
||||||
|
900: '#831843',
|
||||||
|
950: '#500724',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rose',
|
||||||
|
palette: {
|
||||||
|
50: '#fff1f2',
|
||||||
|
100: '#ffe4e6',
|
||||||
|
200: '#fecdd3',
|
||||||
|
300: '#fda4af',
|
||||||
|
400: '#fb7185',
|
||||||
|
500: '#f43f5e',
|
||||||
|
600: '#e11d48',
|
||||||
|
700: '#be123c',
|
||||||
|
800: '#9f1239',
|
||||||
|
900: '#881337',
|
||||||
|
950: '#4c0519',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const surfaces = ref([
|
||||||
|
{
|
||||||
|
name: 'slate',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f8fafc',
|
||||||
|
100: '#f1f5f9',
|
||||||
|
200: '#e2e8f0',
|
||||||
|
300: '#cbd5e1',
|
||||||
|
400: '#94a3b8',
|
||||||
|
500: '#64748b',
|
||||||
|
600: '#475569',
|
||||||
|
700: '#334155',
|
||||||
|
800: '#1e293b',
|
||||||
|
900: '#0f172a',
|
||||||
|
950: '#020617',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gray',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f9fafb',
|
||||||
|
100: '#f3f4f6',
|
||||||
|
200: '#e5e7eb',
|
||||||
|
300: '#d1d5db',
|
||||||
|
400: '#9ca3af',
|
||||||
|
500: '#6b7280',
|
||||||
|
600: '#4b5563',
|
||||||
|
700: '#374151',
|
||||||
|
800: '#1f2937',
|
||||||
|
900: '#111827',
|
||||||
|
950: '#030712',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zinc',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafafa',
|
||||||
|
100: '#f4f4f5',
|
||||||
|
200: '#e4e4e7',
|
||||||
|
300: '#d4d4d8',
|
||||||
|
400: '#a1a1aa',
|
||||||
|
500: '#71717a',
|
||||||
|
600: '#52525b',
|
||||||
|
700: '#3f3f46',
|
||||||
|
800: '#27272a',
|
||||||
|
900: '#18181b',
|
||||||
|
950: '#09090b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'neutral',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafafa',
|
||||||
|
100: '#f5f5f5',
|
||||||
|
200: '#e5e5e5',
|
||||||
|
300: '#d4d4d4',
|
||||||
|
400: '#a3a3a3',
|
||||||
|
500: '#737373',
|
||||||
|
600: '#525252',
|
||||||
|
700: '#404040',
|
||||||
|
800: '#262626',
|
||||||
|
900: '#171717',
|
||||||
|
950: '#0a0a0a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stone',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fafaf9',
|
||||||
|
100: '#f5f5f4',
|
||||||
|
200: '#e7e5e4',
|
||||||
|
300: '#d6d3d1',
|
||||||
|
400: '#a8a29e',
|
||||||
|
500: '#78716c',
|
||||||
|
600: '#57534e',
|
||||||
|
700: '#44403c',
|
||||||
|
800: '#292524',
|
||||||
|
900: '#1c1917',
|
||||||
|
950: '#0c0a09',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'soho',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f4f4f4',
|
||||||
|
100: '#e8e9e9',
|
||||||
|
200: '#d2d2d4',
|
||||||
|
300: '#bbbcbe',
|
||||||
|
400: '#a5a5a9',
|
||||||
|
500: '#8e8f93',
|
||||||
|
600: '#77787d',
|
||||||
|
700: '#616268',
|
||||||
|
800: '#4a4b52',
|
||||||
|
900: '#34343d',
|
||||||
|
950: '#1d1e27',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'viva',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#f3f3f3',
|
||||||
|
100: '#e7e7e8',
|
||||||
|
200: '#cfd0d0',
|
||||||
|
300: '#b7b8b9',
|
||||||
|
400: '#9fa1a1',
|
||||||
|
500: '#87898a',
|
||||||
|
600: '#6e7173',
|
||||||
|
700: '#565a5b',
|
||||||
|
800: '#3e4244',
|
||||||
|
900: '#262b2c',
|
||||||
|
950: '#0e1315',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ocean',
|
||||||
|
palette: {
|
||||||
|
0: '#ffffff',
|
||||||
|
50: '#fbfcfc',
|
||||||
|
100: '#F7F9F8',
|
||||||
|
200: '#EFF3F2',
|
||||||
|
300: '#DADEDD',
|
||||||
|
400: '#B1B7B6',
|
||||||
|
500: '#828787',
|
||||||
|
600: '#5F7274',
|
||||||
|
700: '#415B61',
|
||||||
|
800: '#29444E',
|
||||||
|
900: '#183240',
|
||||||
|
950: '#0c1920',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function applyTheme(type: string, color: any) {
|
||||||
|
if (type === 'primary') {
|
||||||
|
updatePreset(getPresetExt());
|
||||||
|
} else if (type === 'surface') {
|
||||||
|
updateSurfacePalette(color.palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPresetExt() {
|
||||||
|
const color = primaryColors.value.find((c) => c.name === layoutConfig.primary)!;
|
||||||
|
|
||||||
|
return color.name === 'noir'
|
||||||
|
? {
|
||||||
|
semantic: {
|
||||||
|
colorScheme: {
|
||||||
|
dark: {
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.50}',
|
||||||
|
color: '{primary.950}',
|
||||||
|
focusBackground: '{primary.300}',
|
||||||
|
focusColor: '{primary.950}',
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
activeColor: '{primary.300}',
|
||||||
|
color: '{primary.50}',
|
||||||
|
contrastColor: '{primary.950}',
|
||||||
|
hoverColor: '{primary.200}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.950}',
|
||||||
|
color: '#ffffff',
|
||||||
|
focusBackground: '{primary.700}',
|
||||||
|
focusColor: '#ffffff',
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
activeColor: '{primary.700}',
|
||||||
|
color: '{primary.950}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.800}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
50: '{surface.50}',
|
||||||
|
100: '{surface.100}',
|
||||||
|
200: '{surface.200}',
|
||||||
|
300: '{surface.300}',
|
||||||
|
400: '{surface.400}',
|
||||||
|
500: '{surface.500}',
|
||||||
|
600: '{surface.600}',
|
||||||
|
700: '{surface.700}',
|
||||||
|
800: '{surface.800}',
|
||||||
|
900: '{surface.900}',
|
||||||
|
950: '{surface.950}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
semantic: {
|
||||||
|
colorScheme: {
|
||||||
|
dark: {
|
||||||
|
highlight: {
|
||||||
|
background: 'color-mix(in srgb, {primary.400}, transparent 84%)',
|
||||||
|
color: 'rgba(255,255,255,.87)',
|
||||||
|
focusBackground: 'color-mix(in srgb, {primary.400}, transparent 76%)',
|
||||||
|
focusColor: 'rgba(255,255,255,.87)',
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
activeColor: '{primary.200}',
|
||||||
|
color: '{primary.400}',
|
||||||
|
contrastColor: '{surface.900}',
|
||||||
|
hoverColor: '{primary.300}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
highlight: {
|
||||||
|
background: '{primary.50}',
|
||||||
|
color: '{primary.700}',
|
||||||
|
focusBackground: '{primary.100}',
|
||||||
|
focusColor: '{primary.800}',
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
activeColor: '{primary.700}',
|
||||||
|
color: '{primary.500}',
|
||||||
|
contrastColor: '#ffffff',
|
||||||
|
hoverColor: '{primary.600}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primary: color.palette,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMenuModeChange() {
|
||||||
|
layoutConfig.menuMode = menuMode.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPresetChange() {
|
||||||
|
layoutConfig.preset = preset.value;
|
||||||
|
const presetValue = presets[preset.value as never];
|
||||||
|
const surfacePalette = surfaces.value.find((s) => s.name === layoutConfig.surface)?.palette;
|
||||||
|
|
||||||
|
$t().preset(presetValue).preset(getPresetExt()).surfacePalette(surfacePalette).use({ useDefaultOptions: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateColors(type: string, color: any) {
|
||||||
|
if (type === 'primary') {
|
||||||
|
layoutConfig.primary = color.name;
|
||||||
|
} else if (type === 'surface') {
|
||||||
|
layoutConfig.surface = color.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTheme(type, color);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="config-panel hidden absolute top-[3.25rem] right-0 w-64 p-4 bg-surface-0 dark:bg-surface-900 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)]"
|
||||||
|
>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Primary</span>
|
||||||
|
<div class="pt-2 flex gap-2 flex-wrap justify-between">
|
||||||
|
<button
|
||||||
|
v-for="primaryColor of primaryColors"
|
||||||
|
:key="primaryColor.name"
|
||||||
|
type="button"
|
||||||
|
:title="primaryColor.name"
|
||||||
|
@click="updateColors('primary', primaryColor)"
|
||||||
|
:class="[
|
||||||
|
'border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1',
|
||||||
|
{ 'outline-primary': layoutConfig.primary === primaryColor.name },
|
||||||
|
]"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: `${primaryColor.name === 'noir' ? 'var(--text-color)' : primaryColor.palette['500']}`,
|
||||||
|
}"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Surface</span>
|
||||||
|
<div class="pt-2 flex gap-2 flex-wrap justify-between">
|
||||||
|
<button
|
||||||
|
v-for="surface of surfaces"
|
||||||
|
:key="surface.name"
|
||||||
|
type="button"
|
||||||
|
:title="surface.name"
|
||||||
|
@click="updateColors('surface', surface)"
|
||||||
|
:class="[
|
||||||
|
'border-none w-5 h-5 rounded-full p-0 cursor-pointer outline-none outline-offset-1',
|
||||||
|
{
|
||||||
|
'outline-primary': layoutConfig.surface
|
||||||
|
? layoutConfig.surface === surface.name
|
||||||
|
: isDarkTheme
|
||||||
|
? surface.name === 'zinc'
|
||||||
|
: surface.name === 'slate',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
:style="{ backgroundColor: `${surface.palette['500']}` }"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Presets</span>
|
||||||
|
<SelectButton v-model="preset" @change="onPresetChange" :options="presetOptions" :allowEmpty="false" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<span class="text-sm text-muted-color font-semibold">Menu Mode</span>
|
||||||
|
<SelectButton
|
||||||
|
v-model="menuMode"
|
||||||
|
@change="onMenuModeChange"
|
||||||
|
:options="menuModeOptions"
|
||||||
|
:allowEmpty="false"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
14
src/layouts/sakai-vue/AppFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout-footer">
|
||||||
|
SAKAI by
|
||||||
|
<a
|
||||||
|
href="https://primevue.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="text-primary font-bold hover:underline"
|
||||||
|
>PrimeVue</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
79
src/layouts/sakai-vue/AppLayout.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import './styles/layout.scss';
|
||||||
|
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import AppFooter from './AppFooter.vue';
|
||||||
|
import AppSidebar from './AppSidebar.vue';
|
||||||
|
import AppTopbar from './AppTopbar.vue';
|
||||||
|
import { useLayout } from './composables/layout';
|
||||||
|
|
||||||
|
const { isSidebarActive, layoutConfig, layoutState } = useLayout();
|
||||||
|
|
||||||
|
const outsideClickListener = ref(null as null | Parameters<typeof document.addEventListener>[1]);
|
||||||
|
|
||||||
|
watch(isSidebarActive, (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
bindOutsideClickListener();
|
||||||
|
} else {
|
||||||
|
unbindOutsideClickListener();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerClass = computed(() => {
|
||||||
|
return {
|
||||||
|
'layout-mobile-active': layoutState.staticMenuMobileActive,
|
||||||
|
'layout-overlay': layoutConfig.menuMode === 'overlay',
|
||||||
|
'layout-overlay-active': layoutState.overlayMenuActive,
|
||||||
|
'layout-static': layoutConfig.menuMode === 'static',
|
||||||
|
'layout-static-inactive': layoutState.staticMenuDesktopInactive && layoutConfig.menuMode === 'static',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function bindOutsideClickListener() {
|
||||||
|
if (!outsideClickListener.value) {
|
||||||
|
outsideClickListener.value = (event) => {
|
||||||
|
if (isOutsideClicked(event)) {
|
||||||
|
layoutState.overlayMenuActive = false;
|
||||||
|
layoutState.staticMenuMobileActive = false;
|
||||||
|
layoutState.menuHoverActive = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('click', outsideClickListener.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOutsideClicked(event: Event) {
|
||||||
|
const sidebarEl = document.querySelector('.layout-sidebar')!;
|
||||||
|
const topbarEl = document.querySelector('.layout-menu-button')!;
|
||||||
|
|
||||||
|
return !(
|
||||||
|
sidebarEl.isSameNode(event.target as never) ||
|
||||||
|
sidebarEl.contains(event.target as never) ||
|
||||||
|
topbarEl.isSameNode(event.target as never) ||
|
||||||
|
topbarEl.contains(event.target as never)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unbindOutsideClickListener() {
|
||||||
|
if (outsideClickListener.value) {
|
||||||
|
document.removeEventListener('click', outsideClickListener.value);
|
||||||
|
outsideClickListener.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout-wrapper" :class="containerClass">
|
||||||
|
<app-topbar></app-topbar>
|
||||||
|
<app-sidebar></app-sidebar>
|
||||||
|
<div class="layout-main-container">
|
||||||
|
<div class="layout-main">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
<app-footer></app-footer>
|
||||||
|
</div>
|
||||||
|
<div class="layout-mask animate-fadein"></div>
|
||||||
|
</div>
|
||||||
|
<Toast />
|
||||||
|
</template>
|
76
src/layouts/sakai-vue/AppMenu.vue
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { MenuItem } from 'primevue/menuitem';
|
||||||
|
|
||||||
|
import { createGetRoutes } from '@/plugins/router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
type MenuItemWithRoute = MenuItem & { routeName?: string };
|
||||||
|
const menuItems = computed(() => {
|
||||||
|
let flatArray: MenuItemWithRoute[] = createGetRoutes(router)()
|
||||||
|
.filter((route) => !route.path.includes('/:'))
|
||||||
|
.filter((route) => !route.meta.hidden)
|
||||||
|
.map((route) => ({
|
||||||
|
id: route.path,
|
||||||
|
label: route.meta.title || `${(route.name as string) || route.path}`,
|
||||||
|
routeName: route.name as string,
|
||||||
|
}));
|
||||||
|
|
||||||
|
flatArray = flatArray.map((item /* index */) => {
|
||||||
|
let id = item.id;
|
||||||
|
if (flatArray.some((item) => item.id.startsWith(`${id}/`))) {
|
||||||
|
id = `${id}/index`;
|
||||||
|
}
|
||||||
|
// 去掉最前面的 /
|
||||||
|
id = id.replace(/^\//, '');
|
||||||
|
|
||||||
|
let parentId = id.replace(/\/[^/]+$/, '');
|
||||||
|
if (parentId === id) {
|
||||||
|
parentId = '_ROOT_';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
parentId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const groupItems: Record<string, string>[] = [];
|
||||||
|
for (const flatArrayItem of flatArray) {
|
||||||
|
if (
|
||||||
|
!groupItems.some((item) => item.id === flatArrayItem.parentId) && //
|
||||||
|
flatArrayItem.parentId !== '_ROOT_'
|
||||||
|
) {
|
||||||
|
let groupItemParentId = flatArrayItem.parentId.replace(/\/[^/]+$/, '');
|
||||||
|
if (groupItemParentId === flatArrayItem.parentId) groupItemParentId = '_ROOT_';
|
||||||
|
groupItems.push({
|
||||||
|
id: flatArrayItem.parentId,
|
||||||
|
label: `Group ${flatArrayItem.parentId}`,
|
||||||
|
parentId: groupItemParentId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.debug(`groupItems :>>`, groupItems);
|
||||||
|
const tree = arrayToTree([...flatArray, ...groupItems], { id: 'id', parentId: 'parentId', rootId: '_ROOT_' });
|
||||||
|
|
||||||
|
// 递归把 children 改为 items
|
||||||
|
function _convertChildrenToItems(tree: MenuItemWithRoute[]) {
|
||||||
|
return tree.map((item) => {
|
||||||
|
if (item.children.length > 0) {
|
||||||
|
item.items = _convertChildrenToItems(item.children);
|
||||||
|
} else {
|
||||||
|
item.command = (/* event */) => {
|
||||||
|
router.push({ name: item.routeName as never });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
delete item.children;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _convertChildrenToItems(tree);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PanelMenu :model="menuItems" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
11
src/layouts/sakai-vue/AppSidebar.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import AppMenu from './AppMenu.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout-sidebar">
|
||||||
|
<app-menu></app-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
104
src/layouts/sakai-vue/AppTopbar.vue
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import AppConfigurator from './AppConfigurator.vue';
|
||||||
|
import { useLayout } from './composables/layout';
|
||||||
|
|
||||||
|
const { isDarkTheme, toggleDarkMode, toggleMenu } = useLayout();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="layout-topbar">
|
||||||
|
<div class="layout-topbar-logo-container">
|
||||||
|
<button class="layout-menu-button layout-topbar-action" @click="toggleMenu">
|
||||||
|
<i class="pi pi-bars"></i>
|
||||||
|
</button>
|
||||||
|
<router-link to="/" class="layout-topbar-logo">
|
||||||
|
<svg viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M17.1637 19.2467C17.1566 19.4033 17.1529 19.561 17.1529 19.7194C17.1529 25.3503 21.7203 29.915 27.3546 29.915C32.9887 29.915 37.5561 25.3503 37.5561 19.7194C37.5561 19.5572 37.5524 19.3959 37.5449 19.2355C38.5617 19.0801 39.5759 18.9013 40.5867 18.6994L40.6926 18.6782C40.7191 19.0218 40.7326 19.369 40.7326 19.7194C40.7326 27.1036 34.743 33.0896 27.3546 33.0896C19.966 33.0896 13.9765 27.1036 13.9765 19.7194C13.9765 19.374 13.9896 19.0316 14.0154 18.6927L14.0486 18.6994C15.0837 18.9062 16.1223 19.0886 17.1637 19.2467ZM33.3284 11.4538C31.6493 10.2396 29.5855 9.52381 27.3546 9.52381C25.1195 9.52381 23.0524 10.2421 21.3717 11.4603C20.0078 11.3232 18.6475 11.1387 17.2933 10.907C19.7453 8.11308 23.3438 6.34921 27.3546 6.34921C31.36 6.34921 34.9543 8.10844 37.4061 10.896C36.0521 11.1292 34.692 11.3152 33.3284 11.4538ZM43.826 18.0518C43.881 18.6003 43.9091 19.1566 43.9091 19.7194C43.9091 28.8568 36.4973 36.2642 27.3546 36.2642C18.2117 36.2642 10.8 28.8568 10.8 19.7194C10.8 19.1615 10.8276 18.61 10.8816 18.0663L7.75383 17.4411C7.66775 18.1886 7.62354 18.9488 7.62354 19.7194C7.62354 30.6102 16.4574 39.4388 27.3546 39.4388C38.2517 39.4388 47.0855 30.6102 47.0855 19.7194C47.0855 18.9439 47.0407 18.1789 46.9536 17.4267L43.826 18.0518ZM44.2613 9.54743L40.9084 10.2176C37.9134 5.95821 32.9593 3.1746 27.3546 3.1746C21.7442 3.1746 16.7856 5.96385 13.7915 10.2305L10.4399 9.56057C13.892 3.83178 20.1756 0 27.3546 0C34.5281 0 40.8075 3.82591 44.2613 9.54743Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
<mask
|
||||||
|
id="mask0_1413_1551"
|
||||||
|
style="mask-type: alpha"
|
||||||
|
maskUnits="userSpaceOnUse"
|
||||||
|
x="0"
|
||||||
|
y="8"
|
||||||
|
width="54"
|
||||||
|
height="11"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M27 18.3652C10.5114 19.1944 0 8.88892 0 8.88892C0 8.88892 16.5176 14.5866 27 14.5866C37.4824 14.5866 54 8.88892 54 8.88892C54 8.88892 43.4886 17.5361 27 18.3652Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_1413_1551)">
|
||||||
|
<path
|
||||||
|
d="M-4.673e-05 8.88887L3.73084 -1.91434L-8.00806 17.0473L-4.673e-05 8.88887ZM27 18.3652L26.4253 6.95109L27 18.3652ZM54 8.88887L61.2673 17.7127L50.2691 -1.91434L54 8.88887ZM-4.673e-05 8.88887C-8.00806 17.0473 -8.00469 17.0505 -8.00132 17.0538C-8.00018 17.055 -7.99675 17.0583 -7.9944 17.0607C-7.98963 17.0653 -7.98474 17.0701 -7.97966 17.075C-7.96949 17.0849 -7.95863 17.0955 -7.94707 17.1066C-7.92401 17.129 -7.89809 17.1539 -7.86944 17.1812C-7.8122 17.236 -7.74377 17.3005 -7.66436 17.3743C-7.50567 17.5218 -7.30269 17.7063 -7.05645 17.9221C-6.56467 18.3532 -5.89662 18.9125 -5.06089 19.5534C-3.39603 20.83 -1.02575 22.4605 1.98012 24.0457C7.97874 27.2091 16.7723 30.3226 27.5746 29.7793L26.4253 6.95109C20.7391 7.23699 16.0326 5.61231 12.6534 3.83024C10.9703 2.94267 9.68222 2.04866 8.86091 1.41888C8.45356 1.10653 8.17155 0.867278 8.0241 0.738027C7.95072 0.673671 7.91178 0.637576 7.90841 0.634492C7.90682 0.63298 7.91419 0.639805 7.93071 0.65557C7.93897 0.663455 7.94952 0.673589 7.96235 0.686039C7.96883 0.692262 7.97582 0.699075 7.98338 0.706471C7.98719 0.710167 7.99113 0.714014 7.99526 0.718014C7.99729 0.720008 8.00047 0.723119 8.00148 0.724116C8.00466 0.727265 8.00796 0.730446 -4.673e-05 8.88887ZM27.5746 29.7793C37.6904 29.2706 45.9416 26.3684 51.6602 23.6054C54.5296 22.2191 56.8064 20.8465 58.4186 19.7784C59.2265 19.2431 59.873 18.7805 60.3494 18.4257C60.5878 18.2482 60.7841 18.0971 60.9374 17.977C61.014 17.9169 61.0799 17.8645 61.1349 17.8203C61.1624 17.7981 61.1872 17.7781 61.2093 17.7602C61.2203 17.7512 61.2307 17.7427 61.2403 17.7348C61.2452 17.7308 61.2499 17.727 61.2544 17.7233C61.2566 17.7215 61.2598 17.7188 61.261 17.7179C61.2642 17.7153 61.2673 17.7127 54 8.88887C46.7326 0.0650536 46.7357 0.0625219 46.7387 0.0600241C46.7397 0.0592345 46.7427 0.0567658 46.7446 0.0551857C46.7485 0.0520238 46.7521 0.0489887 46.7557 0.0460799C46.7628 0.0402623 46.7694 0.0349487 46.7753 0.0301318C46.7871 0.0204986 46.7966 0.0128495 46.8037 0.00712562C46.818 -0.00431848 46.8228 -0.00808311 46.8184 -0.00463784C46.8096 0.00228345 46.764 0.0378652 46.6828 0.0983779C46.5199 0.219675 46.2165 0.439161 45.7812 0.727519C44.9072 1.30663 43.5257 2.14765 41.7061 3.02677C38.0469 4.79468 32.7981 6.63058 26.4253 6.95109L27.5746 29.7793ZM54 8.88887C50.2691 -1.91433 50.27 -1.91467 50.271 -1.91498C50.2712 -1.91506 50.272 -1.91535 50.2724 -1.9155C50.2733 -1.91581 50.274 -1.91602 50.2743 -1.91616C50.2752 -1.91643 50.275 -1.91636 50.2738 -1.91595C50.2714 -1.91515 50.2652 -1.91302 50.2552 -1.9096C50.2351 -1.90276 50.1999 -1.89078 50.1503 -1.874C50.0509 -1.84043 49.8938 -1.78773 49.6844 -1.71863C49.2652 -1.58031 48.6387 -1.377 47.8481 -1.13035C46.2609 -0.635237 44.0427 0.0249875 41.5325 0.6823C36.215 2.07471 30.6736 3.15796 27 3.15796V26.0151C33.8087 26.0151 41.7672 24.2495 47.3292 22.7931C50.2586 22.026 52.825 21.2618 54.6625 20.6886C55.5842 20.4011 56.33 20.1593 56.8551 19.986C57.1178 19.8993 57.3258 19.8296 57.4735 19.7797C57.5474 19.7548 57.6062 19.7348 57.6493 19.72C57.6709 19.7127 57.6885 19.7066 57.7021 19.7019C57.7089 19.6996 57.7147 19.6976 57.7195 19.696C57.7219 19.6952 57.7241 19.6944 57.726 19.6938C57.7269 19.6934 57.7281 19.693 57.7286 19.6929C57.7298 19.6924 57.7309 19.692 54 8.88887ZM27 3.15796C23.3263 3.15796 17.7849 2.07471 12.4674 0.6823C9.95717 0.0249875 7.73904 -0.635237 6.15184 -1.13035C5.36118 -1.377 4.73467 -1.58031 4.3155 -1.71863C4.10609 -1.78773 3.94899 -1.84043 3.84961 -1.874C3.79994 -1.89078 3.76474 -1.90276 3.74471 -1.9096C3.73469 -1.91302 3.72848 -1.91515 3.72613 -1.91595C3.72496 -1.91636 3.72476 -1.91643 3.72554 -1.91616C3.72593 -1.91602 3.72657 -1.91581 3.72745 -1.9155C3.72789 -1.91535 3.72874 -1.91506 3.72896 -1.91498C3.72987 -1.91467 3.73084 -1.91433 -4.673e-05 8.88887C-3.73093 19.692 -3.72983 19.6924 -3.72868 19.6929C-3.72821 19.693 -3.72698 19.6934 -3.72603 19.6938C-3.72415 19.6944 -3.72201 19.6952 -3.71961 19.696C-3.71482 19.6976 -3.70901 19.6996 -3.7022 19.7019C-3.68858 19.7066 -3.67095 19.7127 -3.6494 19.72C-3.60629 19.7348 -3.54745 19.7548 -3.47359 19.7797C-3.32589 19.8296 -3.11788 19.8993 -2.85516 19.986C-2.33008 20.1593 -1.58425 20.4011 -0.662589 20.6886C1.17485 21.2618 3.74125 22.026 6.67073 22.7931C12.2327 24.2495 20.1913 26.0151 27 26.0151V3.15796Z"
|
||||||
|
fill="var(--primary-color)"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<span>SAKAI</span>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-topbar-actions">
|
||||||
|
<div class="layout-config-menu">
|
||||||
|
<button type="button" class="layout-topbar-action" @click="toggleDarkMode">
|
||||||
|
<i :class="['pi', { 'pi-moon': isDarkTheme, 'pi-sun': !isDarkTheme }]"></i>
|
||||||
|
</button>
|
||||||
|
<div class="relative">
|
||||||
|
<button
|
||||||
|
v-styleclass="{
|
||||||
|
selector: '@next',
|
||||||
|
enterFromClass: 'hidden',
|
||||||
|
enterActiveClass: 'animate-scalein',
|
||||||
|
leaveToClass: 'hidden',
|
||||||
|
leaveActiveClass: 'animate-fadeout',
|
||||||
|
hideOnOutsideClick: true,
|
||||||
|
}"
|
||||||
|
type="button"
|
||||||
|
class="layout-topbar-action layout-topbar-action-highlight"
|
||||||
|
>
|
||||||
|
<i class="pi pi-palette"></i>
|
||||||
|
</button>
|
||||||
|
<AppConfigurator />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="layout-topbar-menu-button layout-topbar-action"
|
||||||
|
v-styleclass="{
|
||||||
|
selector: '@next',
|
||||||
|
enterFromClass: 'hidden',
|
||||||
|
enterActiveClass: 'animate-scalein',
|
||||||
|
leaveToClass: 'hidden',
|
||||||
|
leaveActiveClass: 'animate-fadeout',
|
||||||
|
hideOnOutsideClick: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<i class="pi pi-ellipsis-v"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="layout-topbar-menu hidden lg:block">
|
||||||
|
<div class="layout-topbar-menu-content">
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-calendar"></i>
|
||||||
|
<span>Calendar</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-inbox"></i>
|
||||||
|
<span>Messages</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="layout-topbar-action">
|
||||||
|
<i class="pi pi-user"></i>
|
||||||
|
<span>Profile</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
2
src/layouts/sakai-vue/README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- https://primevue.org/templates/sakai/
|
||||||
|
- https://sakai.primevue.org/
|
@@ -15,7 +15,7 @@ const getStoredMenuState = (): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const layoutState = reactive({
|
const layoutState = reactive({
|
||||||
activeMenuItem: null as Record<string, never> | null,
|
activeMenuItem: null,
|
||||||
configSidebarVisible: false,
|
configSidebarVisible: false,
|
||||||
menuHoverActive: false,
|
menuHoverActive: false,
|
||||||
overlayMenuActive: false,
|
overlayMenuActive: false,
|
||||||
|
26
src/layouts/sakai-vue/styles/_core.scss
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
// font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Lato', sans-serif;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--surface-ground);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-wrapper {
|
||||||
|
min-height: 100vh;
|
||||||
|
@supports (min-height: 100dvh) {
|
||||||
|
min-height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
8
src/layouts/sakai-vue/styles/_footer.scss
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.layout-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem 0 1rem 0;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-top: 1px solid var(--surface-border);
|
||||||
|
}
|
17
src/layouts/sakai-vue/styles/_main.scss
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.layout-main-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6rem 2rem 0 2rem;
|
||||||
|
transition: margin-left var(--layout-section-transition-duration);
|
||||||
|
@supports (min-height: 100dvh) {
|
||||||
|
min-height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-main {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
164
src/layouts/sakai-vue/styles/_menu.scss
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
@use 'mixins' as *;
|
||||||
|
|
||||||
|
.layout-sidebar {
|
||||||
|
position: fixed;
|
||||||
|
width: 20rem;
|
||||||
|
height: calc(100vh - 8rem);
|
||||||
|
z-index: 999;
|
||||||
|
overflow-y: auto;
|
||||||
|
user-select: none;
|
||||||
|
top: 6rem;
|
||||||
|
left: 2rem;
|
||||||
|
transition:
|
||||||
|
transform var(--layout-section-transition-duration),
|
||||||
|
left var(--layout-section-transition-duration);
|
||||||
|
background-color: var(--surface-overlay);
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
height: calc(100dvh - 8rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-menu {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
.layout-root-menuitem {
|
||||||
|
> .layout-menuitem-root-text {
|
||||||
|
font-size: 0.857rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-color);
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&.active-menuitem {
|
||||||
|
> .layout-submenu-toggler {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active-menuitem {
|
||||||
|
> a {
|
||||||
|
.layout-submenu-toggler {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
outline: 0 none;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
transition:
|
||||||
|
background-color var(--element-transition-duration),
|
||||||
|
box-shadow var(--element-transition-duration);
|
||||||
|
|
||||||
|
.layout-menuitem-icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-toggler {
|
||||||
|
font-size: 75%;
|
||||||
|
margin-left: auto;
|
||||||
|
transition: transform var(--element-transition-duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active-route {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
@include focused-inset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 3.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
margin-left: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-enter-from,
|
||||||
|
.layout-submenu-leave-to {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-enter-to,
|
||||||
|
.layout-submenu-leave-from {
|
||||||
|
max-height: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-leave-active {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.45s cubic-bezier(0, 1, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-submenu-enter-active {
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 1s ease-in-out;
|
||||||
|
}
|
15
src/layouts/sakai-vue/styles/_mixins.scss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@mixin focused() {
|
||||||
|
outline-width: var(--focus-ring-width);
|
||||||
|
outline-style: var(--focus-ring-style);
|
||||||
|
outline-color: var(--focus-ring-color);
|
||||||
|
outline-offset: var(--focus-ring-offset);
|
||||||
|
box-shadow: var(--focus-ring-shadow);
|
||||||
|
transition:
|
||||||
|
box-shadow var(--transition-duration),
|
||||||
|
outline-color var(--transition-duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin focused-inset() {
|
||||||
|
outline-offset: -1px;
|
||||||
|
box-shadow: inset var(--focus-ring-shadow);
|
||||||
|
}
|
48
src/layouts/sakai-vue/styles/_preloading.scss
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.preloader {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999999;
|
||||||
|
background: #edf1f5;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.preloader-content {
|
||||||
|
border: 0 solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50vh - 75px);
|
||||||
|
left: calc(50vw - 75px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-content:before,
|
||||||
|
.preloader-content:after {
|
||||||
|
content: '';
|
||||||
|
border: 1em solid var(--primary-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
animation: loader 2s linear infinite;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-content:before {
|
||||||
|
animation-delay: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
118
src/layouts/sakai-vue/styles/_responsive.scss
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
@media screen and (min-width: 1960px) {
|
||||||
|
.layout-main,
|
||||||
|
.landing-wrapper {
|
||||||
|
width: 1504px;
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.layout-wrapper {
|
||||||
|
&.layout-overlay {
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-right: 1px solid var(--surface-border);
|
||||||
|
transition:
|
||||||
|
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
|
||||||
|
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||||
|
box-shadow:
|
||||||
|
0px 3px 5px rgba(0, 0, 0, 0.02),
|
||||||
|
0px 0px 2px rgba(0, 0, 0, 0.05),
|
||||||
|
0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-overlay-active {
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-static {
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 22rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-static-inactive {
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mask {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.blocked-scroll {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-wrapper {
|
||||||
|
.layout-main-container {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
transition:
|
||||||
|
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
|
||||||
|
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||||
|
|
||||||
|
@supports (height: 100dvh) {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mask {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 998;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--maskbg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-mobile-active {
|
||||||
|
.layout-sidebar {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mask {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
202
src/layouts/sakai-vue/styles/_topbar.scss
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
@use 'mixins' as *;
|
||||||
|
|
||||||
|
.layout-topbar {
|
||||||
|
position: fixed;
|
||||||
|
height: 4rem;
|
||||||
|
z-index: 997;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 2rem;
|
||||||
|
background-color: var(--surface-card);
|
||||||
|
transition: left var(--layout-section-transition-duration);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.layout-topbar-logo-container {
|
||||||
|
width: 20rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-logo {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
@include focused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-action {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
transition: background-color var(--element-transition-duration);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
@include focused();
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 1rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-topbar-action-highlight {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--primary-contrast-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-menu-button {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-actions {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-config-menu {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.layout-topbar {
|
||||||
|
padding: 0 2rem;
|
||||||
|
|
||||||
|
.layout-topbar-logo-container {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-menu-button {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-button {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--surface-overlay);
|
||||||
|
transform-origin: top;
|
||||||
|
box-shadow:
|
||||||
|
0px 3px 5px rgba(0, 0, 0, 0.02),
|
||||||
|
0px 0px 2px rgba(0, 0, 0, 0.05),
|
||||||
|
0px 1px 4px rgba(0, 0, 0, 0.08);
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
padding: 1rem;
|
||||||
|
right: 2rem;
|
||||||
|
top: 4rem;
|
||||||
|
min-width: 15rem;
|
||||||
|
border: 1px solid var(--surface-border);
|
||||||
|
|
||||||
|
.layout-topbar-menu-content {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-action {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
justify-content: flex-start;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-weight: medium;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-topbar-menu-content {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-panel {
|
||||||
|
.config-panel-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary-color);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-panel-colors {
|
||||||
|
> div {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
outline-color: transparent;
|
||||||
|
outline-width: 2px;
|
||||||
|
outline-style: solid;
|
||||||
|
outline-offset: 1px;
|
||||||
|
|
||||||
|
&.active-color {
|
||||||
|
outline-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-panel-settings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
68
src/layouts/sakai-vue/styles/_typography.scss
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 1.5rem 0 1rem 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background: #fff8e1;
|
||||||
|
padding: 0.25rem 0.4rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0 2rem;
|
||||||
|
border-left: 4px solid #90a4ae;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-top: solid var(--surface-border);
|
||||||
|
border-width: 1px 0 0 0;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
25
src/layouts/sakai-vue/styles/_utils.scss
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/* Utils */
|
||||||
|
.clearfix:after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--surface-card);
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border-radius: var(--content-border-radius);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-toast {
|
||||||
|
&.p-toast-top-right,
|
||||||
|
&.p-toast-top-left,
|
||||||
|
&.p-toast-top-center {
|
||||||
|
top: 100px;
|
||||||
|
}
|
||||||
|
}
|
13
src/layouts/sakai-vue/styles/layout.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@use './variables/_common';
|
||||||
|
@use './variables/_light';
|
||||||
|
@use './variables/_dark';
|
||||||
|
@use './_mixins';
|
||||||
|
@use './_preloading';
|
||||||
|
@use './_core';
|
||||||
|
@use './_main';
|
||||||
|
@use './_topbar';
|
||||||
|
@use './_menu';
|
||||||
|
@use './_footer';
|
||||||
|
@use './_responsive';
|
||||||
|
@use './_utils';
|
||||||
|
@use './_typography';
|
20
src/layouts/sakai-vue/styles/variables/_common.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
:root {
|
||||||
|
--primary-color: var(--p-primary-color);
|
||||||
|
--primary-contrast-color: var(--p-primary-contrast-color);
|
||||||
|
--text-color: var(--p-text-color);
|
||||||
|
--text-color-secondary: var(--p-text-muted-color);
|
||||||
|
--surface-border: var(--p-content-border-color);
|
||||||
|
--surface-card: var(--p-content-background);
|
||||||
|
--surface-hover: var(--p-content-hover-background);
|
||||||
|
--surface-overlay: var(--p-overlay-popover-background);
|
||||||
|
--transition-duration: var(--p-transition-duration);
|
||||||
|
--maskbg: var(--p-mask-background);
|
||||||
|
--content-border-radius: var(--p-content-border-radius);
|
||||||
|
--layout-section-transition-duration: 0.2s;
|
||||||
|
--element-transition-duration: var(--p-transition-duration);
|
||||||
|
--focus-ring-width: var(--p-focus-ring-width);
|
||||||
|
--focus-ring-style: var(--p-focus-ring-style);
|
||||||
|
--focus-ring-color: var(--p-focus-ring-color);
|
||||||
|
--focus-ring-offset: var(--p-focus-ring-offset);
|
||||||
|
--focus-ring-shadow: var(--p-focus-ring-shadow);
|
||||||
|
}
|
5
src/layouts/sakai-vue/styles/variables/_dark.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
:root[class*='app-dark'] {
|
||||||
|
--surface-ground: var(--p-surface-950);
|
||||||
|
--code-background: var(--p-surface-800);
|
||||||
|
--code-color: var(--p-surface-100);
|
||||||
|
}
|
5
src/layouts/sakai-vue/styles/variables/_light.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
:root {
|
||||||
|
--surface-ground: var(--p-surface-100);
|
||||||
|
--code-background: var(--p-surface-900);
|
||||||
|
--code-color: var(--p-surface-200);
|
||||||
|
}
|
@@ -1,12 +1,7 @@
|
|||||||
import './styles';
|
import './styles';
|
||||||
|
|
||||||
import { LogLevels } from 'consola';
|
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import { setupPlugins } from './plugins';
|
import { setupPlugins } from './plugins';
|
||||||
|
|
||||||
const autoInstallModules = import.meta.glob('./plugins/*.ts', { eager: true });
|
const autoInstallModules = import.meta.glob('./plugins/*.ts', { eager: true });
|
||||||
|
|
||||||
setupPlugins(createApp(App), autoInstallModules).mount('#app');
|
setupPlugins(createApp(App), autoInstallModules).mount('#app');
|
||||||
|
|
||||||
consola.level = LogLevels.verbose;
|
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const baseURL = '/fake-api';
|
const baseURL = '/fake-api';
|
||||||
|
|
||||||
let fakeApiResult = $ref<null | Record<string, unknown>>(null);
|
let fakeApiResult = $ref<null | Record<string, unknown>>(null);
|
||||||
@@ -11,124 +9,6 @@ onMounted(() => {
|
|||||||
.then((json) => (fakeApiResult = json));
|
.then((json) => (fakeApiResult = json));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用 axios 请求 XML,并演示以文本获取后用 DOMParser 解析
|
|
||||||
// 选择此方式的原因:可控性更强,便于处理编码、容错与字段抽取策略
|
|
||||||
let xmlText = $ref<string | null>(null);
|
|
||||||
let xmlParsed = $ref<Record<string, string> | null>(null);
|
|
||||||
let xmlPostText = $ref<string | null>(null);
|
|
||||||
let xmlPostParsed = $ref<Record<string, string> | null>(null);
|
|
||||||
let xmlPostXmlText = $ref<string | null>(null);
|
|
||||||
let xmlPostXmlParsed = $ref<Record<string, string> | null>(null);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
const res = await axios.get<string>(`${baseURL}/xml/sample`, {
|
|
||||||
responseType: 'text',
|
|
||||||
headers: { Accept: 'application/xml' },
|
|
||||||
transformResponse: [(data: string) => data],
|
|
||||||
params: {
|
|
||||||
to: 'Alice',
|
|
||||||
from: 'Bob',
|
|
||||||
heading: 'Greeting',
|
|
||||||
body: 'Hello from API.page.vue',
|
|
||||||
id: '1001',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
xmlText = res.data;
|
|
||||||
|
|
||||||
// 将 XML 字符串解析为 Document,再选择性抽取业务所需字段
|
|
||||||
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
|
|
||||||
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
|
|
||||||
xmlParsed = {
|
|
||||||
to: pick('to'),
|
|
||||||
from: pick('from'),
|
|
||||||
heading: pick('heading'),
|
|
||||||
body: pick('body'),
|
|
||||||
id: pick('meta > id'),
|
|
||||||
createdAt: pick('meta > createdAt'),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 备用方案:直接请求为 Document(部分运行环境可能不支持)
|
|
||||||
// const docRes = await axios.get<Document>(`${baseURL}/xml/sample`, { responseType: 'document' });
|
|
||||||
// console.log('Document:', docRes.data);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('XML 请求失败: ', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 演示 POST 提交 JSON,由服务端返回 XML,再解析
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
const res = await axios.post<string>(
|
|
||||||
`${baseURL}/xml/submit`,
|
|
||||||
{
|
|
||||||
to: 'Tom',
|
|
||||||
from: 'Jerry',
|
|
||||||
heading: 'PostXML',
|
|
||||||
body: 'This is a POST body to XML service',
|
|
||||||
id: '9001',
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: { 'Content-Type': 'application/json', Accept: 'application/xml' },
|
|
||||||
responseType: 'text',
|
|
||||||
transformResponse: [(data: string) => data],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
xmlPostText = res.data;
|
|
||||||
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
|
|
||||||
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
|
|
||||||
xmlPostParsed = {
|
|
||||||
to: pick('to'),
|
|
||||||
from: pick('from'),
|
|
||||||
heading: pick('heading'),
|
|
||||||
body: pick('body'),
|
|
||||||
id: pick('meta > id'),
|
|
||||||
createdAt: pick('meta > createdAt'),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('XML POST 请求失败: ', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 演示 POST 以 XML 请求体提交,服务端解析 XML 并返回规范化的 XML
|
|
||||||
onMounted(async () => {
|
|
||||||
try {
|
|
||||||
const payload = `<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<note>
|
|
||||||
<to>XML-Client</to>
|
|
||||||
<from>Browser</from>
|
|
||||||
<heading>XMLSubmit</heading>
|
|
||||||
<body>Send by XML body</body>
|
|
||||||
<meta>
|
|
||||||
<id>777</id>
|
|
||||||
<createdAt>${new Date().toISOString()}</createdAt>
|
|
||||||
</meta>
|
|
||||||
</note>`;
|
|
||||||
|
|
||||||
const res = await axios.post<string>(`${baseURL}/xml/submit`, payload, {
|
|
||||||
headers: { 'Content-Type': 'application/xml', Accept: 'application/xml' },
|
|
||||||
responseType: 'text',
|
|
||||||
transformResponse: [(data: string) => data],
|
|
||||||
});
|
|
||||||
xmlPostXmlText = res.data;
|
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
|
|
||||||
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
|
|
||||||
xmlPostXmlParsed = {
|
|
||||||
to: pick('to'),
|
|
||||||
from: pick('from'),
|
|
||||||
heading: pick('heading'),
|
|
||||||
body: pick('body'),
|
|
||||||
id: pick('meta > id'),
|
|
||||||
createdAt: pick('meta > createdAt'),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('XML POST(XML body) 请求失败: ', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* fetch('https://jsonplaceholder.typicode.com/posts/1')
|
/* fetch('https://jsonplaceholder.typicode.com/posts/1')
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((json) => console.log(json)); */
|
.then((json) => console.log(json)); */
|
||||||
@@ -142,23 +22,6 @@ onMounted(async () => {
|
|||||||
<template>
|
<template>
|
||||||
<pre>{{ JSON.stringify(fakeApiResult, null, 2) }}</pre>
|
<pre>{{ JSON.stringify(fakeApiResult, null, 2) }}</pre>
|
||||||
<!-- <div>{{ npmRegistryApiResult?.['_id'] }}</div> -->
|
<!-- <div>{{ npmRegistryApiResult?.['_id'] }}</div> -->
|
||||||
<h3>XML 字符串</h3>
|
|
||||||
<pre class="break-all whitespace-pre-wrap">{{ xmlText }}</pre>
|
|
||||||
|
|
||||||
<h3>解析后的结果</h3>
|
|
||||||
<pre>{{ xmlParsed }}</pre>
|
|
||||||
|
|
||||||
<h3>POST 返回的 XML 字符串</h3>
|
|
||||||
<pre class="break-all whitespace-pre-wrap">{{ xmlPostText }}</pre>
|
|
||||||
|
|
||||||
<h3>POST 解析后的结果</h3>
|
|
||||||
<pre>{{ xmlPostParsed }}</pre>
|
|
||||||
|
|
||||||
<h3>POST(XML body) 返回的 XML 字符串</h3>
|
|
||||||
<pre class="break-all whitespace-pre-wrap">{{ xmlPostXmlText }}</pre>
|
|
||||||
|
|
||||||
<h3>POST(XML body) 解析后的结果</h3>
|
|
||||||
<pre>{{ xmlPostXmlParsed }}</pre>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@@ -32,7 +32,7 @@ function getItemById(id: number | string): ComponentItem | undefined {
|
|||||||
*/
|
*/
|
||||||
function addToStartList(item: ComponentItem): boolean {
|
function addToStartList(item: ComponentItem): boolean {
|
||||||
const newStartIndex = getItemIndex(item);
|
const newStartIndex = getItemIndex(item);
|
||||||
const endIndex = 流程终点列表.value.length > 0 ? getItemIndex(流程终点列表.value[0]!) : -1;
|
const endIndex = 流程终点列表.value.length > 0 ? getItemIndex(流程终点列表.value[0]) : -1;
|
||||||
|
|
||||||
// 约束检查:如果终点已存在,则新起点必须在终点之前
|
// 约束检查:如果终点已存在,则新起点必须在终点之前
|
||||||
if (endIndex !== -1 && newStartIndex >= endIndex) {
|
if (endIndex !== -1 && newStartIndex >= endIndex) {
|
||||||
@@ -54,7 +54,7 @@ function addToStartList(item: ComponentItem): boolean {
|
|||||||
*/
|
*/
|
||||||
function addToEndList(item: ComponentItem): boolean {
|
function addToEndList(item: ComponentItem): boolean {
|
||||||
const newEndIndex = getItemIndex(item);
|
const newEndIndex = getItemIndex(item);
|
||||||
const startIndex = 流程起点列表.value.length > 0 ? getItemIndex(流程起点列表.value[0]!) : -1;
|
const startIndex = 流程起点列表.value.length > 0 ? getItemIndex(流程起点列表.value[0]) : -1;
|
||||||
|
|
||||||
// 约束检查:如果起点已存在,则新终点必须在起点之后
|
// 约束检查:如果起点已存在,则新终点必须在起点之后
|
||||||
if (startIndex !== -1 && newEndIndex <= startIndex) {
|
if (startIndex !== -1 && newEndIndex <= startIndex) {
|
||||||
@@ -227,13 +227,13 @@ const 完整流程节点 = computed(() => {
|
|||||||
const nodes: ComponentItem[] = [];
|
const nodes: ComponentItem[] = [];
|
||||||
// 添加起点
|
// 添加起点
|
||||||
if (流程起点列表.value.length > 0) {
|
if (流程起点列表.value.length > 0) {
|
||||||
nodes.push(流程起点列表.value[0]!);
|
nodes.push(流程起点列表.value[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加中间节点
|
// 添加中间节点
|
||||||
if (流程起点列表.value.length > 0 && 流程终点列表.value.length > 0) {
|
if (流程起点列表.value.length > 0 && 流程终点列表.value.length > 0) {
|
||||||
const startIndex = getItemIndex(流程起点列表.value[0]!);
|
const startIndex = getItemIndex(流程起点列表.value[0]);
|
||||||
const endIndex = getItemIndex(流程终点列表.value[0]!);
|
const endIndex = getItemIndex(流程终点列表.value[0]);
|
||||||
|
|
||||||
if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex - 1) {
|
if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex - 1) {
|
||||||
nodes.push(...组件列表.value.slice(startIndex + 1, endIndex));
|
nodes.push(...组件列表.value.slice(startIndex + 1, endIndex));
|
||||||
@@ -242,7 +242,7 @@ const 完整流程节点 = computed(() => {
|
|||||||
|
|
||||||
// 添加终点
|
// 添加终点
|
||||||
if (流程终点列表.value.length > 0) {
|
if (流程终点列表.value.length > 0) {
|
||||||
nodes.push(流程终点列表.value[0]!);
|
nodes.push(流程终点列表.value[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
# 测试
|
|
||||||
|
|
||||||
这个文件是被 import 的
|
|
@@ -1,11 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import MD from './MDPageImportMD.md';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<MD />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
@@ -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>
|
|
@@ -20,7 +20,7 @@ function generateFakeSpectrogramData(len = 30, baseLevel = -90, noiseRange = 30)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 定时更新数据以模拟实时效果
|
// 定时更新数据以模拟实时效果
|
||||||
let intervalId: null | NodeJS.Timeout = null;
|
let intervalId: null | number = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 模拟: 每x秒更新一次数据并通过 ref 发送
|
// 模拟: 每x秒更新一次数据并通过 ref 发送
|
||||||
|
@@ -79,10 +79,10 @@ const sketch = (p: P5) => {
|
|||||||
// 更新和显示所有粒子
|
// 更新和显示所有粒子
|
||||||
// 从后向前遍历数组,这样可以在遍历过程中安全地删除元素
|
// 从后向前遍历数组,这样可以在遍历过程中安全地删除元素
|
||||||
for (let i = particles.length - 1; i >= 0; i--) {
|
for (let i = particles.length - 1; i >= 0; i--) {
|
||||||
particles[i]!.update();
|
particles[i].update();
|
||||||
particles[i]!.display();
|
particles[i].display();
|
||||||
// 如果粒子生命周期结束,则从数组中移除
|
// 如果粒子生命周期结束,则从数组中移除
|
||||||
if (particles[i]!.isDead()) {
|
if (particles[i].isDead()) {
|
||||||
particles.splice(i, 1);
|
particles.splice(i, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
|
@@ -1,12 +1,12 @@
|
|||||||
<!-- https://github.com/intlify/vue-i18n/blob/master/examples/type-safe/type-annotation/src/components/en-US.json -->
|
<!-- https://github.com/intlify/vue-i18n/blob/master/examples/type-safe/type-annotation/src/components/en-US.json -->
|
||||||
<i18n lang="json5">
|
<i18n lang="json">
|
||||||
{
|
{
|
||||||
en: {
|
"en": {
|
||||||
'unplugin-hello': 'Hello, unplugin-vue-i18n!',
|
"unplugin-hello": "Hello, unplugin-vue-i18n!"
|
||||||
},
|
|
||||||
zh: {
|
|
||||||
'unplugin-hello': '你好,unplugin-vue-i18n!',
|
|
||||||
},
|
},
|
||||||
|
"zh": {
|
||||||
|
"unplugin-hello": "你好,unplugin-vue-i18n!"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|
||||||
|
@@ -27,7 +27,15 @@ import { Input as ShaInput } from '@/shadcn/components/ui/input';
|
|||||||
<ShadcnButton variant="ghost">Ghost</ShadcnButton>
|
<ShadcnButton variant="ghost">Ghost</ShadcnButton>
|
||||||
<ShadcnButton variant="link">Link</ShadcnButton>
|
<ShadcnButton variant="link">Link</ShadcnButton>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="demo-block">
|
<div class="demo-block">
|
||||||
<a-input placeholder="Ant Design" />
|
<a-input placeholder="Ant Design" />
|
||||||
<InputText placeholder="Primevue" />
|
<InputText placeholder="Primevue" />
|
||||||
|
@@ -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>
|
|
@@ -45,7 +45,7 @@ export const openDialog = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await nextTick();
|
await nextTick();
|
||||||
// if (__DEV__) return;
|
// if ($__DEV__) return;
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
DialogService.open(dialogContent, {
|
DialogService.open(dialogContent, {
|
||||||
|
@@ -8,7 +8,7 @@ const onClick = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ShadcnButton as="button" :onClick>ShadcnButton</ShadcnButton>
|
<ShadcnButton :onClick>ShadcnButton</ShadcnButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -28,7 +28,7 @@ defineOptions({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
state.list.splice(0);
|
state.list.splice(0, state.list.length);
|
||||||
state.page = 0;
|
state.page = 0;
|
||||||
state.complete = false;
|
state.complete = false;
|
||||||
loadMore();
|
loadMore();
|
||||||
@@ -53,7 +53,7 @@ async function loadMore() {
|
|||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
state.list = [...state.list, ...data];
|
state.list = [...state.list, ...data];
|
||||||
if (__DEV__) await new Promise((resolve) => setTimeout(resolve, 500));
|
if ($__DEV__) await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
state.complete = state.list.length >= 5;
|
state.complete = state.list.length >= 5;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
state.error = error;
|
state.error = error;
|
||||||
|
@@ -152,7 +152,7 @@ line2 += '9';
|
|||||||
// '',
|
// '',
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// if (__DEV__) {
|
// if ($__DEV__) {
|
||||||
// const tle = TLE_LIST[0] as string;
|
// const tle = TLE_LIST[0] as string;
|
||||||
// const line1 = tle.split('\n')[1] as string;
|
// const line1 = tle.split('\n')[1] as string;
|
||||||
// const line2 = tle.split('\n')[2] as string;
|
// const line2 = tle.split('\n')[2] as string;
|
||||||
@@ -160,7 +160,7 @@ line2 += '9';
|
|||||||
// twoline2satrecFake(line1, line2);
|
// twoline2satrecFake(line1, line2);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// if (__DEV__) {
|
// if ($__DEV__) {
|
||||||
// const satrec2 = twoline2satrec(
|
// const satrec2 = twoline2satrec(
|
||||||
// `1 62949 25070.91668981 .00039463 00000+0 99294-3 `,
|
// `1 62949 25070.91668981 .00039463 00000+0 99294-3 `,
|
||||||
// `2 53.1596 120.9032 0001355 101.1211 35.9659 15.39574303 `,
|
// `2 53.1596 120.9032 0001355 101.1211 35.9659 15.39574303 `,
|
||||||
|
@@ -66,7 +66,7 @@ onMounted(async () => {
|
|||||||
const satelliteCheckboxOptions = computed(() =>
|
const satelliteCheckboxOptions = computed(() =>
|
||||||
satelliteState.satellites.map((sat) => ({
|
satelliteState.satellites.map((sat) => ({
|
||||||
// 从 TLE 字符串的第一行提取名称作为标签
|
// 从 TLE 字符串的第一行提取名称作为标签
|
||||||
label: sat.tle.split('\n')[0]!.trim(),
|
label: sat.tle.split('\n')[0].trim(),
|
||||||
value: sat.id,
|
value: sat.id,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
@@ -2,7 +2,7 @@ import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
|
|||||||
import { createHead } from '@unhead/vue/client';
|
import { createHead } from '@unhead/vue/client';
|
||||||
|
|
||||||
export function install({ app }: { app: import('vue').App<Element> }) {
|
export function install({ app }: { app: import('vue').App<Element> }) {
|
||||||
app.config.globalProperties.__DEV__ = __DEV__;
|
app.config.globalProperties.$__DEV__ = $__DEV__;
|
||||||
|
|
||||||
app.use(autoAnimatePlugin); // v-auto-animate="{ duration: 100 }"
|
app.use(autoAnimatePlugin); // v-auto-animate="{ duration: 100 }"
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
|
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
|
||||||
import { setupLayouts } from 'virtual:meta-layouts';
|
import { setupLayouts } from 'virtual:meta-layouts';
|
||||||
// import { createGetRoutes, setupLayouts } from 'virtual:generated-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';
|
import { handleHotUpdate, routes } from 'vue-router/auto-routes';
|
||||||
|
|
||||||
const setupLayoutsResult = setupLayouts(routes);
|
const setupLayoutsResult = setupLayouts(routes);
|
||||||
@@ -14,7 +14,7 @@ const router = createRouter({
|
|||||||
strict: true,
|
strict: true,
|
||||||
});
|
});
|
||||||
if (import.meta.hot) handleHotUpdate(router);
|
if (import.meta.hot) handleHotUpdate(router);
|
||||||
if (__DEV__) Object.assign(globalThis, { router });
|
if ($__DEV__) Object.assign(globalThis, { router });
|
||||||
router.onError((error) => {
|
router.onError((error) => {
|
||||||
console.debug('🚨 [router error]:', error);
|
console.debug('🚨 [router error]:', error);
|
||||||
});
|
});
|
||||||
|
11
src/plugins/vuetify.ts
Normal 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);
|
||||||
|
}
|
@@ -1,26 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { PrimitiveProps } from "reka-ui"
|
import type { HTMLAttributes } from 'vue'
|
||||||
import type { HTMLAttributes } from "vue"
|
|
||||||
import type { ButtonVariants } from "."
|
|
||||||
import { Primitive } from "reka-ui"
|
|
||||||
import { cn } from '@/shadcn/lib/utils'
|
import { cn } from '@/shadcn/lib/utils'
|
||||||
import { buttonVariants } from "."
|
import { Primitive, type PrimitiveProps } from 'reka-ui'
|
||||||
|
import { type ButtonVariants, buttonVariants } from '.'
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
interface Props extends PrimitiveProps {
|
||||||
variant?: ButtonVariants["variant"]
|
variant?: ButtonVariants['variant']
|
||||||
size?: ButtonVariants["size"]
|
size?: ButtonVariants['size']
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes['class']
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
as: "button",
|
as: 'button',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Primitive
|
<Primitive
|
||||||
:as="props.as"
|
:as="as"
|
||||||
:as-child="props.asChild"
|
:as-child="asChild"
|
||||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
@@ -1,34 +1,33 @@
|
|||||||
import type { VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
import { cva } from "class-variance-authority"
|
|
||||||
|
|
||||||
export { default as Button } from "./Button.vue"
|
export { default as Button } from './Button.vue'
|
||||||
|
|
||||||
export const buttonVariants = cva(
|
export const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||||
outline:
|
outline:
|
||||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: 'text-primary underline-offset-4 hover:underline',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2",
|
default: 'h-9 px-4 py-2',
|
||||||
xs: "h-7 rounded px-2",
|
xs: 'h-7 rounded px-2',
|
||||||
sm: "h-8 rounded-md px-3 text-xs",
|
sm: 'h-8 rounded-md px-3 text-xs',
|
||||||
lg: "h-10 rounded-md px-8",
|
lg: 'h-10 rounded-md px-8',
|
||||||
icon: "h-9 w-9",
|
icon: 'h-9 w-9',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: 'default',
|
||||||
size: "default",
|
size: 'default',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@@ -4,8 +4,8 @@ import { cn } from '@/shadcn/lib/utils'
|
|||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
defaultValue?: string | number | undefined
|
defaultValue?: string | number
|
||||||
modelValue?: string | number | undefined
|
modelValue?: string | number
|
||||||
class?: HTMLAttributes['class']
|
class?: HTMLAttributes['class']
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
@@ -13,9 +13,4 @@ import 'primeicons/primeicons.css';
|
|||||||
import './reset/reset-primevue.css';
|
import './reset/reset-primevue.css';
|
||||||
import './reset/reset-antdv.less';
|
import './reset/reset-antdv.less';
|
||||||
|
|
||||||
// 通用字体
|
|
||||||
import 'vfonts/Lato.css';
|
|
||||||
// 等宽字体
|
|
||||||
import 'vfonts/FiraCode.css';
|
|
||||||
//
|
|
||||||
import 'virtual:uno.css';
|
import 'virtual:uno.css';
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
declare global {
|
|
||||||
const __DEV__: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'vue' {
|
|
||||||
export interface ComponentCustomProperties {
|
|
||||||
__DEV__: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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';
|
import type { ComponentOptions } from 'vue';
|
||||||
|
|
||||||
const Component: ComponentOptions;
|
const Component: ComponentOptions;
|
||||||
@@ -11,3 +19,5 @@ declare module '*.md' {
|
|||||||
const Component: ComponentOptions;
|
const Component: ComponentOptions;
|
||||||
export default Component;
|
export default Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {};
|
@@ -16,3 +16,5 @@ declare module 'vue' {
|
|||||||
interface HTMLAttributes
|
interface HTMLAttributes
|
||||||
extends Partial<Record<import('@unocss/preset-attributify').AttributifyNames, boolean | string>> {}
|
extends Partial<Record<import('@unocss/preset-attributify').AttributifyNames, boolean | string>> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export {};
|
@@ -1,8 +1,9 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { test } from '@playwright/test';
|
||||||
import { consola } from 'consola';
|
import { consola } from 'consola';
|
||||||
|
|
||||||
test('页面加载正常', async ({ page }, testInfo) => {
|
test('页面加载正常', async ({ page }, testInfo) => {
|
||||||
await page.goto('/Home');
|
await page.goto('/Home');
|
||||||
|
consola.info(`page.url() === ${page.url()}`);
|
||||||
|
|
||||||
// await expect(page.getByRole('link', { name: '中文-页面.page.vue' })).toBeVisible();
|
// await expect(page.getByRole('link', { name: '中文-页面.page.vue' })).toBeVisible();
|
||||||
|
|
||||||
@@ -14,5 +15,4 @@ test('页面加载正常', async ({ page }, testInfo) => {
|
|||||||
// 获取元素的文本内容
|
// 获取元素的文本内容
|
||||||
const innerText = await commitElement.textContent();
|
const innerText = await commitElement.textContent();
|
||||||
consola.debug(`Commit 文本内容: "${innerText}"`);
|
consola.debug(`Commit 文本内容: "${innerText}"`);
|
||||||
expect(innerText).toContain('commit:');
|
|
||||||
});
|
});
|
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json", // https://github.com/vuejs/create-vue/blob/main/template/tsconfig/base/tsconfig.app.json
|
"extends": "@vue/tsconfig/tsconfig.dom.json", // https://github.com/vuejs/create-vue/blob/main/template/tsconfig/base/tsconfig.app.json
|
||||||
"include": [
|
"include": [
|
||||||
"env.d.ts",
|
|
||||||
"./typed-router.d.ts",
|
"./typed-router.d.ts",
|
||||||
"./auto-imports.d.ts",
|
"./auto-imports.d.ts",
|
||||||
"./components.d.ts",
|
"./components.d.ts",
|
||||||
@@ -10,15 +9,26 @@
|
|||||||
"src/**/*",
|
"src/**/*",
|
||||||
"src/**/*.vue"
|
"src/**/*.vue"
|
||||||
],
|
],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": [
|
||||||
|
"src/**/__tests__/*",
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
|
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"allowJs": true, // 允许编译 JavaScript 文件
|
"allowJs": true, // 允许编译 JavaScript 文件
|
||||||
"checkJs": true, // 启用 JavaScript 文件的类型检查
|
"checkJs": true, // 启用 JavaScript 文件的类型检查
|
||||||
|
"types": [
|
||||||
|
"vite/client",
|
||||||
|
// "vitest",
|
||||||
|
|
||||||
|
"vite-plugin-vue-layouts/client",
|
||||||
|
"vite-plugin-vue-meta-layouts/client",
|
||||||
|
// "vite-plugin-pwa/client",
|
||||||
|
"unplugin-vue-macros/macros-global",
|
||||||
|
"unplugin-vue-router/client",
|
||||||
|
"unplugin-icons/types/vue",
|
||||||
|
"@intlify/unplugin-vue-i18n/messages"
|
||||||
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,6 @@
|
|||||||
{
|
{
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [
|
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
|
||||||
{
|
|
||||||
"path": "./tsconfig.app.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.node.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.vitest.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// https://www.shadcn-vue.com/docs/components-json.html#aliases
|
// https://www.shadcn-vue.com/docs/components-json.html#aliases
|
||||||
// A fallback to tsconfig.app.json if no paths were found in tsconfig.json
|
// A fallback to tsconfig.app.json if no paths were found in tsconfig.json
|
||||||
|
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.app.json",
|
|
||||||
"include": ["src/**/__tests__/*", "env.d.ts"],
|
|
||||||
"exclude": [],
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
|
||||||
|
|
||||||
"lib": [],
|
|
||||||
"types": ["node", "jsdom"]
|
|
||||||
}
|
|
||||||
}
|
|
168
typed-router.d.ts
vendored
@@ -34,12 +34,9 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'PageIframePageSpectrogram': RouteRecordInfo<'PageIframePageSpectrogram', '/Page/iframe-page/Spectrogram', Record<never, never>, Record<never, never>>,
|
'PageIframePageSpectrogram': RouteRecordInfo<'PageIframePageSpectrogram', '/Page/iframe-page/Spectrogram', Record<never, never>, Record<never, never>>,
|
||||||
'PageJSPage': RouteRecordInfo<'PageJSPage', '/Page/JSPage', 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>>,
|
'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>>,
|
'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>>,
|
'PageStyle': RouteRecordInfo<'PageStyle', '/Page/Style', Record<never, never>, Record<never, never>>,
|
||||||
'PageViteAssets': RouteRecordInfo<'PageViteAssets', '/Page/vite-assets', 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>>,
|
'PkgsUsageI18n': RouteRecordInfo<'PkgsUsageI18n', '/PkgsUsage/I18n', Record<never, never>, Record<never, never>>,
|
||||||
'PkgsUsageTsEnumUtil': RouteRecordInfo<'PkgsUsageTsEnumUtil', '/PkgsUsage/ts-enum-util', 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>>,
|
'UIComponentsAntdV': RouteRecordInfo<'UIComponentsAntdV', '/UI-components/AntdV', Record<never, never>, Record<never, never>>,
|
||||||
@@ -47,175 +44,10 @@ declare module 'vue-router/auto-routes' {
|
|||||||
'UIComponentsInfiniteLoading': RouteRecordInfo<'UIComponentsInfiniteLoading', '/UI-components/infinite-loading', Record<never, never>, Record<never, never>>,
|
'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>>,
|
'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>>,
|
'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>>,
|
'UIComponentsPrimeVue': RouteRecordInfo<'UIComponentsPrimeVue', '/UI-components/PrimeVue', Record<never, never>, Record<never, never>>,
|
||||||
'UIComponentsShadcnVue': RouteRecordInfo<'UIComponentsShadcnVue', '/UI-components/ShadcnVue', 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>>,
|
'VueMacrosDefineRender': RouteRecordInfo<'VueMacrosDefineRender', '/VueMacros/DefineRender', Record<never, never>, Record<never, never>>,
|
||||||
'VueMacrosReactivityTransform': RouteRecordInfo<'VueMacrosReactivityTransform', '/VueMacros/ReactivityTransform', Record<never, never>, Record<never, never>>,
|
'VueMacrosReactivityTransform': RouteRecordInfo<'VueMacrosReactivityTransform', '/VueMacros/ReactivityTransform', Record<never, never>, Record<never, never>>,
|
||||||
'VueMacrosReusableTemplate': RouteRecordInfo<'VueMacrosReusableTemplate', '/VueMacros/ReusableTemplate', Record<never, never>, Record<never, never>>,
|
'VueMacrosReusableTemplate': RouteRecordInfo<'VueMacrosReusableTemplate', '/VueMacros/ReusableTemplate', Record<never, never>, Record<never, never>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Route file to route info map by unplugin-vue-router.
|
|
||||||
* Used by the volar plugin to automatically type useRoute()
|
|
||||||
*
|
|
||||||
* Each key is a file path relative to the project root with 2 properties:
|
|
||||||
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
|
|
||||||
* - views: names of nested views (can be passed to <RouterView name="...">)
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export interface _RouteFileInfoMap {
|
|
||||||
'src/pages/index.page.vue': {
|
|
||||||
routes: 'Root'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/[...path].page.vue': {
|
|
||||||
routes: '$Path'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/cesium-viewer.page.vue': {
|
|
||||||
routes: 'CesiumViewer'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/data-loaders.[id]/index.page.vue': {
|
|
||||||
routes: 'DataLoadersId'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/data-loaders.[id]/sub-1.[userId].page.vue': {
|
|
||||||
routes: 'DataLoadersIdSub1UserId'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/FlowbiteSidebar.page.vue': {
|
|
||||||
routes: 'FlowbiteSidebar'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Home.page.vue': {
|
|
||||||
routes: 'Home'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/API.page.vue': {
|
|
||||||
routes: 'PageAPI'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/Dom-Draggable.page.vue': {
|
|
||||||
routes: 'PageDomDraggable'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/fonts.page.vue': {
|
|
||||||
routes: 'PageFonts'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/Icons.page.vue': {
|
|
||||||
routes: 'PageIcons'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/iframe-page/Iframe-PlotlyJs.page.vue': {
|
|
||||||
routes: 'PageIframePageIframePlotlyJs'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/iframe-page/IframeConstellationDiagram.page.vue': {
|
|
||||||
routes: 'PageIframePageIframeConstellationDiagram'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/iframe-page/Spectrogram.page.vue': {
|
|
||||||
routes: 'PageIframePageSpectrogram'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/JSPage/index.page.vue': {
|
|
||||||
routes: 'PageJSPage'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/MDPage.page.md': {
|
|
||||||
routes: 'PageMDPage'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/MDPageImportMD.page.vue': {
|
|
||||||
routes: 'PageMDPageImportMD'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/p5_js/index.page.vue': {
|
|
||||||
routes: 'PageP5Js'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/PinPuYi.page.vue': {
|
|
||||||
routes: 'PagePinPuYi'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/Style/index.page.vue': {
|
|
||||||
routes: 'PageStyle'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/vite-assets/index.page.vue': {
|
|
||||||
routes: 'PageViteAssets'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/Page/vue-data-ui.page.vue': {
|
|
||||||
routes: 'PageVueDataUi'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/PkgsUsage/I18n.page.vue': {
|
|
||||||
routes: 'PkgsUsageI18n'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/PkgsUsage/ts-enum-util.page.vue': {
|
|
||||||
routes: 'PkgsUsageTsEnumUtil'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/AntdV/index.page.vue': {
|
|
||||||
routes: 'UIComponentsAntdV'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/Components/index.page.vue': {
|
|
||||||
routes: 'UIComponentsComponents'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/infinite-loading/index.page.vue': {
|
|
||||||
routes: 'UIComponentsInfiniteLoading'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/infinite-loading/detail.page.vue': {
|
|
||||||
routes: 'UIComponentsInfiniteLoadingDetail'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/InspiraUI/index.page.vue': {
|
|
||||||
routes: 'UIComponentsInspiraUI'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/NaiveUI/index.page.vue': {
|
|
||||||
routes: 'UIComponentsNaiveUI'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/PrimeVue/index.page.vue': {
|
|
||||||
routes: 'UIComponentsPrimeVue'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/UI-components/ShadcnVue/index.page.vue': {
|
|
||||||
routes: 'UIComponentsShadcnVue'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/VueMacros/DefineRender.page.vue': {
|
|
||||||
routes: 'VueMacrosDefineRender'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/VueMacros/ReactivityTransform.page.vue': {
|
|
||||||
routes: 'VueMacrosReactivityTransform'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
'src/pages/VueMacros/ReusableTemplate.page.vue': {
|
|
||||||
routes: 'VueMacrosReusableTemplate'
|
|
||||||
views: never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a union of possible route names in a certain route component file.
|
|
||||||
* Used by the volar plugin to automatically type useRoute()
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export type _RouteNamesForFilePath<FilePath extends string> =
|
|
||||||
_RouteFileInfoMap extends Record<FilePath, infer Info>
|
|
||||||
? Info['routes']
|
|
||||||
: keyof RouteNamedMap
|
|
||||||
}
|
}
|
||||||
|
@@ -11,11 +11,11 @@ import Vue from '@vitejs/plugin-vue';
|
|||||||
import VueJsx from '@vitejs/plugin-vue-jsx';
|
import VueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import UnoCSS from 'unocss/vite';
|
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 { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||||
import IconsResolver from 'unplugin-icons/resolver';
|
import IconsResolver from 'unplugin-icons/resolver';
|
||||||
import Icons from 'unplugin-icons/vite';
|
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 Components from 'unplugin-vue-components/vite';
|
||||||
import VueMacros from 'unplugin-vue-macros/vite';
|
import VueMacros from 'unplugin-vue-macros/vite';
|
||||||
import Markdown from 'unplugin-vue-markdown/vite';
|
import Markdown from 'unplugin-vue-markdown/vite';
|
||||||
@@ -25,12 +25,12 @@ import { createUtils4uAutoImports } from 'utils4u/auto-imports';
|
|||||||
import { type PluginOption } from 'vite';
|
import { type PluginOption } from 'vite';
|
||||||
import { checker } from 'vite-plugin-checker';
|
import { checker } from 'vite-plugin-checker';
|
||||||
import { vitePluginFakeServer } from 'vite-plugin-fake-server';
|
import { vitePluginFakeServer } from 'vite-plugin-fake-server';
|
||||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
|
|
||||||
import pluginPurgeCss from 'vite-plugin-purgecss-updated-v5';
|
import pluginPurgeCss from 'vite-plugin-purgecss-updated-v5';
|
||||||
import { viteSingleFile } from 'vite-plugin-singlefile';
|
import { viteSingleFile } from 'vite-plugin-singlefile';
|
||||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||||
import VueDevTools from 'vite-plugin-vue-devtools';
|
import VueDevTools from 'vite-plugin-vue-devtools';
|
||||||
import MetaLayouts from 'vite-plugin-vue-meta-layouts';
|
import MetaLayouts from 'vite-plugin-vue-meta-layouts';
|
||||||
|
import vuetify from 'vite-plugin-vuetify';
|
||||||
import { ViteWebfontDownload } from 'vite-plugin-webfont-dl';
|
import { ViteWebfontDownload } from 'vite-plugin-webfont-dl';
|
||||||
|
|
||||||
import { viteArchiverPlugin } from './vite.config.plugin.archiver';
|
import { viteArchiverPlugin } from './vite.config.plugin.archiver';
|
||||||
@@ -66,8 +66,7 @@ export function Plugins() {
|
|||||||
|
|
||||||
// https://github.com/dishait/vite-plugin-vue-meta-layouts
|
// https://github.com/dishait/vite-plugin-vue-meta-layouts
|
||||||
MetaLayouts({
|
MetaLayouts({
|
||||||
// defaultLayout: 'sakai-vue/AppLayout',
|
defaultLayout: 'sakai-vue/AppLayout',
|
||||||
defaultLayout: 'naive-ui/AppLayout',
|
|
||||||
skipTopLevelRouteLayout: false, // 打开修复 https://github.com/JohnCampionJr/vite-plugin-vue-layouts/issues/134,默认为 false 关闭
|
skipTopLevelRouteLayout: false, // 打开修复 https://github.com/JohnCampionJr/vite-plugin-vue-layouts/issues/134,默认为 false 关闭
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -80,8 +79,11 @@ export function Plugins() {
|
|||||||
headEnabled: true,
|
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
|
// https://github.com/antfu/unplugin-auto-import
|
||||||
AutoImport({
|
UnpluginAutoImport({
|
||||||
dirs: [
|
dirs: [
|
||||||
// 'src/composables',
|
// 'src/composables',
|
||||||
'src/stores',
|
'src/stores',
|
||||||
@@ -99,7 +101,6 @@ export function Plugins() {
|
|||||||
{
|
{
|
||||||
'consola/browser': ['consola'],
|
'consola/browser': ['consola'],
|
||||||
'vue-router/auto': ['useLink'],
|
'vue-router/auto': ['useLink'],
|
||||||
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
resolvers: [TDesignResolver({ esm: true, library: 'mobile-vue' }), VantResolver({ importStyle: true })],
|
resolvers: [TDesignResolver({ esm: true, library: 'mobile-vue' }), VantResolver({ importStyle: true })],
|
||||||
@@ -126,7 +127,6 @@ export function Plugins() {
|
|||||||
TDesignResolver({ esm: true, library: 'mobile-vue' }),
|
TDesignResolver({ esm: true, library: 'mobile-vue' }),
|
||||||
VantResolver({ importStyle: true }),
|
VantResolver({ importStyle: true }),
|
||||||
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
|
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
|
||||||
NaiveUiResolver(),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ export function Plugins() {
|
|||||||
// https://github.com/condorheroblog/vite-plugin-fake-server?tab=readme-ov-file#usage
|
// https://github.com/condorheroblog/vite-plugin-fake-server?tab=readme-ov-file#usage
|
||||||
vitePluginFakeServer({
|
vitePluginFakeServer({
|
||||||
basename: 'fake-api',
|
basename: 'fake-api',
|
||||||
enableProd: !true,
|
enableProd: true,
|
||||||
include: 'fake',
|
include: 'fake',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -194,10 +194,6 @@ export function Plugins() {
|
|||||||
{ dest: cesiumBaseUrl, src: `${cesiumSource}/Widgets` },
|
{ dest: cesiumBaseUrl, src: `${cesiumSource}/Widgets` },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
// https://github.com/FatehAK/vite-plugin-image-optimizer?tab=readme-ov-file#default-configuration
|
|
||||||
ViteImageOptimizer({
|
|
||||||
/* pass your config */
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 检查是否在VS Code终端中运行
|
// 检查是否在VS Code终端中运行
|
||||||
|