chore: initial commit
This commit is contained in:
3
.dev.vars.example
Normal file
3
.dev.vars.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Wrangler 开发环境配置示例文件
|
||||||
|
# 复制此文件为 .dev.vars 并填入实际的环境变量值
|
||||||
|
# 该文件用于本地开发时的环境变量设置
|
||||||
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
end_of_line = lf
|
||||||
|
max_line_length = 100
|
||||||
5
.env
Normal file
5
.env
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
VITE_APP_TITLE=vue-ts-example-2025
|
||||||
|
VITE_APP_BASE=/
|
||||||
|
VITE_APP_BUILD_SOURCE_MAP=true
|
||||||
|
VITE_APP_BUILD_COMMIT=
|
||||||
|
VITE_APP_BUILD_TIME=
|
||||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
66
.github/workflows/ci-cd.yaml
vendored
Normal file
66
.github/workflows/ci-cd.yaml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: CI/CD Pipeline
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: gitea/runner-images:ubuntu-latest-slim # https://github.com/cloudflare/wrangler-action/issues/329#issuecomment-3046747722
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 🛠️ 设置Node环境
|
||||||
|
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
|
||||||
|
|
||||||
|
- name: 🔍 静态代码分析
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: 📦 构建项目
|
||||||
|
run: pnpm run build-only
|
||||||
|
env:
|
||||||
|
VITE_APP_BUILD_COMMIT: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: 🧪 单元测试
|
||||||
|
run: pnpm run test:unit
|
||||||
|
|
||||||
|
- name: 📊 计算构建大小
|
||||||
|
run: |
|
||||||
|
echo "📊 构建大小统计:"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo "🔹 人类可读格式: $(du -sh dist | cut -f1)"
|
||||||
|
echo "🔹 以MB为单位: $(du -sm dist | cut -f1) MB"
|
||||||
|
echo "🔹 以KB为单位: $(du -sk dist | cut -f1) KB"
|
||||||
|
echo "🔹 文件总数: $(find dist -type f | wc -l) 个文件"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
- name: ✅ 类型检查
|
||||||
|
run: pnpm run type-check # 要先 build,保证 components.d.ts 存在
|
||||||
|
|
||||||
|
- name: 🚀 部署到 Cloudflare
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: cloudflare/wrangler-action@v3
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
command: deploy
|
||||||
|
|
||||||
|
playwright:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: mcr.microsoft.com/playwright:v1.56.0-noble
|
||||||
|
steps:
|
||||||
|
- name: ⚙️ 设置 Node 环境
|
||||||
|
uses: yanhao98/composite-actions/setup-node-environment@25eb4dc0c134cc9df2b7c569aa54140a366b45a8
|
||||||
|
# - name: 📥 安装 Playwright 浏览器
|
||||||
|
# run: pnpm exec playwright install --with-deps
|
||||||
|
- name: 📦 构建项目
|
||||||
|
run: pnpm run build-only
|
||||||
|
- name: ▶️ 运行 Playwright 测试
|
||||||
|
run: pnpm exec playwright test
|
||||||
85
.github/workflows/测试最新依赖.yaml
vendored
Normal file
85
.github/workflows/测试最新依赖.yaml
vendored
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
TZ: Asia/Shanghai
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
schedule:
|
||||||
|
- cron: "30 22 * * *" # 22:30 UTC = 6:30 CST
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: gitea/runner-images:ubuntu-latest-slim # https://github.com/cloudflare/wrangler-action/issues/329#issuecomment-3046747722
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@main
|
||||||
|
with:
|
||||||
|
# fetch-depth: 0 # 0 代表完整检出,semantic-release 需要
|
||||||
|
filter: blob:none # 我们不需要所有 blob,只需要完整的树
|
||||||
|
show-progress: false
|
||||||
|
- run: rm pnpm-lock.yaml
|
||||||
|
- uses: pnpm/action-setup@master # https://github.com/pnpm/action-setup?tab=readme-ov-file#inputs
|
||||||
|
|
||||||
|
- uses: actions/setup-node@main # https://github.com/actions/setup-node?tab=readme-ov-file#usage
|
||||||
|
with:
|
||||||
|
cache: ""
|
||||||
|
|
||||||
|
- run: pnpm up --latest
|
||||||
|
- run: pnpm outdated
|
||||||
|
|
||||||
|
- name: 🔍 静态代码分析
|
||||||
|
run: pnpm run lint
|
||||||
|
|
||||||
|
- name: 📦 构建项目
|
||||||
|
run: pnpm run build-only
|
||||||
|
env:
|
||||||
|
VITE_APP_BUILD_COMMIT: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: 🧪 单元测试
|
||||||
|
run: pnpm run test:unit
|
||||||
|
|
||||||
|
- name: 📊 计算构建大小
|
||||||
|
run: |
|
||||||
|
echo "📊 构建大小统计:"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
echo "🔹 人类可读格式: $(du -sh dist | cut -f1)"
|
||||||
|
echo "🔹 以MB为单位: $(du -sm dist | cut -f1) MB"
|
||||||
|
echo "🔹 以KB为单位: $(du -sk dist | cut -f1) KB"
|
||||||
|
echo "🔹 文件总数: $(find dist -type f | wc -l) 个文件"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
|
||||||
|
- name: ✅ 类型检查
|
||||||
|
run: pnpm run type-check # 要先 build,保证 components.d.ts 存在
|
||||||
|
|
||||||
|
playwright:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: mcr.microsoft.com/playwright:v1.56.0-noble
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@main
|
||||||
|
with:
|
||||||
|
# fetch-depth: 0 # 0 代表完整检出,semantic-release 需要
|
||||||
|
filter: blob:none # 我们不需要所有 blob,只需要完整的树
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
|
- run: rm pnpm-lock.yaml
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@master # https://github.com/pnpm/action-setup?tab=readme-ov-file#inputs
|
||||||
|
|
||||||
|
- uses: actions/setup-node@main # https://github.com/actions/setup-node?tab=readme-ov-file#usage
|
||||||
|
with:
|
||||||
|
cache: ""
|
||||||
|
|
||||||
|
- run: pnpm up --latest
|
||||||
|
- run: pnpm outdated
|
||||||
|
|
||||||
|
- name: 📦 构建项目
|
||||||
|
run: pnpm run build-only
|
||||||
|
|
||||||
|
- name: ▶️ 运行 Playwright 测试
|
||||||
|
run: pnpm exec playwright test
|
||||||
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
playwright-test-results/
|
||||||
|
playwright-report/
|
||||||
|
|
||||||
|
# wrangler files
|
||||||
|
.wrangler
|
||||||
|
.dev.vars*
|
||||||
|
!.dev.vars.example
|
||||||
|
# .env*
|
||||||
|
# !.env.example
|
||||||
|
|
||||||
|
|
||||||
|
# Generated
|
||||||
|
components.d.ts
|
||||||
20
.husky/README.md
Normal file
20
.husky/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
### Husky 遇到 command not found: husky
|
||||||
|
|
||||||
|
- https://typicode.github.io/husky/zh/troubleshoot.html#找不到命令-command-not-found
|
||||||
|
- https://typicode.github.io/husky/zh/how-to.html#node-版本管理器和-gui
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ln -s $(which pnpm) $HOME/.local/bin/pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# if command -v pnpm >/dev/null 2>&1; then
|
||||||
|
# # 如果 pnpm 可用,直接使用它
|
||||||
|
# pnpm exec lint-staged
|
||||||
|
# else
|
||||||
|
# # 如果 pnpm 不可用,使用 $HOME/.local/bin/pnpm
|
||||||
|
# # ln -s $(which pnpm) $HOME/.local/bin/pnpm
|
||||||
|
# echo "找不到 pnpm,使用 $HOME/.local/bin/pnpm"
|
||||||
|
# "$HOME"/.local/bin/pnpm exec lint-staged
|
||||||
|
# fi
|
||||||
|
```
|
||||||
5
.husky/commit-msg
Normal file
5
.husky/commit-msg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 此钩子在 pre-commit 钩子成功完成后,用于检查提交消息。
|
||||||
|
echo "📝 [Commit-msg] 正在运行 commit-msg 钩子..."
|
||||||
|
echo "检查提交消息:$1"
|
||||||
|
pnpm exec commitlint --edit $1
|
||||||
|
echo "✅ [Commit-msg] commit-msg 钩子完成!"
|
||||||
4
.husky/post-merge
Normal file
4
.husky/post-merge
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# 此钩子在 git merge 或 git pull 成功完成后运行。
|
||||||
|
echo "🔗 [Post-merge] 正在安装依赖..."
|
||||||
|
pnpm install
|
||||||
|
echo "✅ [Post-merge] 依赖安装完成!"
|
||||||
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# 此钩子在执行 git commit 命令时,在创建提交之前运行。
|
||||||
|
echo "🧹 [Pre-commit] 正在运行 lint-staged..."
|
||||||
|
pnpm exec lint-staged
|
||||||
|
echo "✅ [Pre-commit] lint-staged 完成!"
|
||||||
9
.npmrc
Normal file
9
.npmrc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# registry=https://registry.npmmirror.com/
|
||||||
|
|
||||||
|
# https://pnpm.io/zh/npmrc#node-mirrorltreleasedir
|
||||||
|
use-node-version=24.7.0
|
||||||
|
node-mirror:release=https://npmmirror.com/mirrors/node/ # pnpm config set node-mirror:release=https://npmmirror.com/mirrors/node/
|
||||||
|
node-mirror:rc=https://npmmirror.com/mirrors/node-rc/
|
||||||
|
node-mirror:nightly=https://npmmirror.com/mirrors/node-nightly/
|
||||||
|
|
||||||
|
# shamefully-hoist=true
|
||||||
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
11
.vscode/extensions.json
vendored
Normal file
11
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"vitest.explorer",
|
||||||
|
"ms-playwright.playwright",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"oxc.oxc-vscode",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Firefox Dev Edition",
|
||||||
|
"type": "firefox",
|
||||||
|
"request": "launch",
|
||||||
|
"url": "http://localhost:4730/",
|
||||||
|
"webRoot": "${workspaceFolder}",
|
||||||
|
"firefoxExecutable": "/Applications/Firefox Nightly.app/Contents/MacOS/firefox",
|
||||||
|
"preLaunchTask": "🚀 dev"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
.vscode/settings.json
vendored
Normal file
20
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": "explicit",
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.fixAll.stylelint": "explicit",
|
||||||
|
"source.fixAll.oxc": "explicit",
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
},
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
|
||||||
|
"stylelint.enable": true,
|
||||||
|
"stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
|
||||||
|
|
||||||
|
"eslint.enable": true,
|
||||||
|
"oxc.enable": true,
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
33
.vscode/tasks.json
vendored
Normal file
33
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
AGENTS.bak.md
Normal file
1
AGENTS.bak.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
- **vite-plugin-fake-server**: Mock API under `/fake-api` (dev only) from `fake/` directory
|
||||||
57
AGENTS.md
Normal file
57
AGENTS.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
This file provides guidance to AI when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Vue 3 TypeScript application with Vite.
|
||||||
|
|
||||||
|
### Routing & Layouts
|
||||||
|
|
||||||
|
- **File-based routing**: Uses `unplugin-vue-router` with `.page.vue` and `.page.md` extensions in `src/pages/`
|
||||||
|
- **Route naming**: Converts to PascalCase (e.g., `user-profile.page.vue` → `UserProfile`)
|
||||||
|
- **Layouts**: `vite-plugin-vue-meta-layouts` with default layout `base-layout/base-layout`
|
||||||
|
|
||||||
|
### Auto-Import Configuration
|
||||||
|
|
||||||
|
Multiple auto-import systems are active:
|
||||||
|
|
||||||
|
- **Vue APIs**: Core Vue, VueUse, Pinia, Vue Router, vue-i18n
|
||||||
|
- **Components**: Auto-registered from multiple UI libraries (Naive UI, PrimeVue)
|
||||||
|
- **Icons**: Uses `unplugin-icons` with `icon-` prefix; custom SVGs from `src/assets/icons/svgs/` available via `icon-svg:filename`
|
||||||
|
|
||||||
|
**IMPORTANT - Auto-Import Limitations**:
|
||||||
|
|
||||||
|
- Auto-imported components do NOT work with dynamic components (e.g., `<component :is="dynamicName" />`)
|
||||||
|
- When using icons or components conditionally, use `v-if`/`v-else-if`/`v-else` instead of dynamic component syntax
|
||||||
|
- Example: Use `<icon-foo v-if="condition" />` instead of `<component :is="`icon-${name}`" />`
|
||||||
|
|
||||||
|
### UI Component Libraries
|
||||||
|
|
||||||
|
Project has multiple UI frameworks configured:
|
||||||
|
|
||||||
|
- **Naive UI**
|
||||||
|
- **PrimeVue**:
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
|
||||||
|
- **UnoCSS**: Wind preset
|
||||||
|
- **SCSS**: Modern compiler API with global imports from `@/styles/scss/global.scss`
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
Pinia stores
|
||||||
|
|
||||||
|
### Cloudflare Workers Integration
|
||||||
|
|
||||||
|
- **Server entry**: `server/index.ts` handles `/api/*` routes with KV storage
|
||||||
|
- **KV binding**: Named `KV`
|
||||||
|
|
||||||
|
### Vite Plugins (notable)
|
||||||
|
|
||||||
|
- **vue-macros**: Enhanced Vue features
|
||||||
|
- **unplugin-vue-markdown**: `.md` files as Vue components with frontmatter
|
||||||
|
|
||||||
|
### Path Aliases
|
||||||
|
|
||||||
|
- `@/` maps to `src/` directory
|
||||||
4
README.md
Normal file
4
README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# vue-ts-example-2025
|
||||||
|
|
||||||
|
- https://github.com/soybeanjs/soybean-admin
|
||||||
|
- https://vitejs.cn/vite3-cn/guide/static-deploy.html
|
||||||
666
auto-imports.d.ts
vendored
Normal file
666
auto-imports.d.ts
vendored
Normal file
@@ -0,0 +1,666 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const APP_THEME_MODES: typeof import('./src/stores/app-store')['APP_THEME_MODES']
|
||||||
|
const EffectScope: typeof import('vue')['EffectScope']
|
||||||
|
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||||
|
const arrayToTree: typeof import('utils4u/array')['arrayToTree']
|
||||||
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
|
const computed: typeof import('vue')['computed']
|
||||||
|
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||||
|
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||||
|
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||||
|
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||||
|
const consola: typeof import('consola/browser')['consola']
|
||||||
|
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||||
|
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||||
|
const convertFileToBase64: typeof import('utils4u/browser')['convertFileToBase64']
|
||||||
|
const createApp: typeof import('vue')['createApp']
|
||||||
|
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||||
|
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||||
|
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||||
|
const createLogGuard: typeof import('utils4u/vue-router')['createLogGuard']
|
||||||
|
const createNProgressGuard: typeof import('utils4u/vue-router')['createNProgressGuard']
|
||||||
|
const createPinia: typeof import('pinia')['createPinia']
|
||||||
|
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||||
|
const createRef: typeof import('@vueuse/core')['createRef']
|
||||||
|
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
||||||
|
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||||
|
const createStackGuard: typeof import('utils4u/vue-router')['createStackGuard']
|
||||||
|
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
||||||
|
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||||
|
const customRef: typeof import('vue')['customRef']
|
||||||
|
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||||
|
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||||
|
const deepFreeze: typeof import('deep-freeze-es6')['default']
|
||||||
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||||
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
|
const defineStore: typeof import('pinia')['defineStore']
|
||||||
|
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||||
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
|
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||||
|
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
|
||||||
|
const h: typeof import('vue')['h']
|
||||||
|
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||||
|
const inject: typeof import('vue')['inject']
|
||||||
|
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||||
|
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||||
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const isShallow: typeof import('vue')['isShallow']
|
||||||
|
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||||
|
const mapActions: typeof import('pinia')['mapActions']
|
||||||
|
const mapGetters: typeof import('pinia')['mapGetters']
|
||||||
|
const mapState: typeof import('pinia')['mapState']
|
||||||
|
const mapStores: typeof import('pinia')['mapStores']
|
||||||
|
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||||
|
const markRaw: typeof import('vue')['markRaw']
|
||||||
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||||
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||||
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
|
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||||
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
|
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
|
||||||
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
|
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||||
|
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||||
|
const onMounted: typeof import('vue')['onMounted']
|
||||||
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||||
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||||
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||||
|
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||||
|
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||||
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
|
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
||||||
|
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||||
|
const provide: typeof import('vue')['provide']
|
||||||
|
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||||
|
const reactify: typeof import('@vueuse/core')['reactify']
|
||||||
|
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||||
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
||||||
|
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
||||||
|
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||||
|
const readonly: typeof import('vue')['readonly']
|
||||||
|
const ref: typeof import('vue')['ref']
|
||||||
|
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||||
|
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||||
|
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||||
|
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||||
|
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||||
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
|
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||||
|
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||||
|
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||||
|
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||||
|
const setViewportCSSVars: typeof import('utils4u/browser')['setViewportCSSVars']
|
||||||
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
|
const showOpenFilePicker: typeof import('utils4u/browser')['showOpenFilePicker']
|
||||||
|
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||||
|
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||||
|
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||||
|
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||||
|
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||||
|
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||||
|
const toRaw: typeof import('vue')['toRaw']
|
||||||
|
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||||
|
const toRef: typeof import('vue')['toRef']
|
||||||
|
const toRefs: typeof import('vue')['toRefs']
|
||||||
|
const toValue: typeof import('vue')['toValue']
|
||||||
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
|
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||||
|
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||||
|
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||||
|
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||||
|
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||||
|
const unref: typeof import('vue')['unref']
|
||||||
|
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||||
|
const until: typeof import('@vueuse/core')['until']
|
||||||
|
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||||
|
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||||
|
const useAppStore: typeof import('./src/stores/app-store')['useAppStore']
|
||||||
|
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||||
|
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
||||||
|
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
||||||
|
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
|
||||||
|
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
|
||||||
|
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
|
||||||
|
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
|
||||||
|
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
|
||||||
|
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
|
||||||
|
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
|
||||||
|
const useArraySome: typeof import('@vueuse/core')['useArraySome']
|
||||||
|
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
|
||||||
|
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||||
|
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||||
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
|
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||||
|
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||||
|
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||||
|
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||||
|
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||||
|
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||||
|
const useCached: typeof import('@vueuse/core')['useCached']
|
||||||
|
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||||
|
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||||
|
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||||
|
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||||
|
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||||
|
const useCountdown: typeof import('@vueuse/core')['useCountdown']
|
||||||
|
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||||
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
|
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||||
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
|
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||||
|
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||||
|
const useDark: typeof import('@vueuse/core')['useDark']
|
||||||
|
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||||
|
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||||
|
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||||
|
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||||
|
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||||
|
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||||
|
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||||
|
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||||
|
const useDialog: typeof import('naive-ui')['useDialog']
|
||||||
|
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||||
|
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||||
|
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||||
|
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
||||||
|
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||||
|
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||||
|
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||||
|
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||||
|
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||||
|
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||||
|
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||||
|
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||||
|
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||||
|
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||||
|
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||||
|
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||||
|
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||||
|
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||||
|
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||||
|
const useFps: typeof import('@vueuse/core')['useFps']
|
||||||
|
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||||
|
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||||
|
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||||
|
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||||
|
const useId: typeof import('vue')['useId']
|
||||||
|
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||||
|
const useImage: typeof import('@vueuse/core')['useImage']
|
||||||
|
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||||
|
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||||
|
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||||
|
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||||
|
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||||
|
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||||
|
const useLink: typeof import('vue-router/auto')['useLink']
|
||||||
|
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
||||||
|
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||||
|
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||||
|
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||||
|
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||||
|
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||||
|
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||||
|
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||||
|
const useMessage: typeof import('naive-ui')['useMessage']
|
||||||
|
const useModal: typeof import('naive-ui')['useModal']
|
||||||
|
const useModel: typeof import('vue')['useModel']
|
||||||
|
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||||
|
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||||
|
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||||
|
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||||
|
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||||
|
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||||
|
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||||
|
const useNotification: typeof import('naive-ui')['useNotification']
|
||||||
|
const useNow: typeof import('@vueuse/core')['useNow']
|
||||||
|
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
||||||
|
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
||||||
|
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||||
|
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||||
|
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||||
|
const useParentElement: typeof import('@vueuse/core')['useParentElement']
|
||||||
|
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
|
||||||
|
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||||
|
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||||
|
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
||||||
|
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||||
|
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||||
|
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
||||||
|
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||||
|
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||||
|
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||||
|
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
|
||||||
|
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||||
|
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||||
|
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||||
|
const useRefs: typeof import('utils4u/vue-use')['useRefs']
|
||||||
|
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||||
|
const useRoute: typeof import('vue-router')['useRoute']
|
||||||
|
const useRouter: typeof import('vue-router')['useRouter']
|
||||||
|
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
|
||||||
|
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||||
|
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||||
|
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||||
|
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||||
|
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||||
|
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||||
|
const useShare: typeof import('@vueuse/core')['useShare']
|
||||||
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
|
const useSorted: typeof import('@vueuse/core')['useSorted']
|
||||||
|
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||||
|
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||||
|
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||||
|
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||||
|
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||||
|
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||||
|
const useSupported: typeof import('@vueuse/core')['useSupported']
|
||||||
|
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||||
|
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||||
|
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||||
|
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
||||||
|
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||||
|
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
||||||
|
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||||
|
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||||
|
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||||
|
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||||
|
const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl']
|
||||||
|
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||||
|
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||||
|
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||||
|
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||||
|
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||||
|
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
||||||
|
const useToString: typeof import('@vueuse/core')['useToString']
|
||||||
|
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||||
|
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||||
|
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||||
|
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||||
|
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||||
|
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||||
|
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||||
|
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||||
|
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||||
|
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||||
|
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||||
|
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||||
|
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||||
|
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||||
|
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||||
|
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||||
|
const watch: typeof import('vue')['watch']
|
||||||
|
const watchArray: typeof import('@vueuse/core')['watchArray']
|
||||||
|
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||||
|
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||||
|
const watchDeep: typeof import('@vueuse/core')['watchDeep']
|
||||||
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
|
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||||
|
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
|
||||||
|
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||||
|
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||||
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
|
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||||
|
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||||
|
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
||||||
|
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||||
|
const whenever: typeof import('@vueuse/core')['whenever']
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
// @ts-ignore
|
||||||
|
export type { AppThemeMode } from './src/stores/app-store'
|
||||||
|
import('./src/stores/app-store')
|
||||||
|
}
|
||||||
|
|
||||||
|
// for vue template auto import
|
||||||
|
import { UnwrapRef } from 'vue'
|
||||||
|
declare module 'vue' {
|
||||||
|
interface GlobalComponents {}
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
readonly APP_THEME_MODES: UnwrapRef<typeof import('./src/stores/app-store')['APP_THEME_MODES']>
|
||||||
|
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||||
|
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||||
|
readonly arrayToTree: UnwrapRef<typeof import('utils4u/array')['arrayToTree']>
|
||||||
|
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||||
|
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||||
|
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||||
|
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
||||||
|
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
||||||
|
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
|
||||||
|
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
|
||||||
|
readonly consola: UnwrapRef<typeof import('consola/browser')['consola']>
|
||||||
|
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
||||||
|
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
||||||
|
readonly convertFileToBase64: UnwrapRef<typeof import('utils4u/browser')['convertFileToBase64']>
|
||||||
|
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||||
|
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
|
||||||
|
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
|
||||||
|
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
|
||||||
|
readonly createLogGuard: UnwrapRef<typeof import('utils4u/vue-router')['createLogGuard']>
|
||||||
|
readonly createNProgressGuard: UnwrapRef<typeof import('utils4u/vue-router')['createNProgressGuard']>
|
||||||
|
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||||
|
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
||||||
|
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
|
||||||
|
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
|
||||||
|
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
||||||
|
readonly createStackGuard: UnwrapRef<typeof import('utils4u/vue-router')['createStackGuard']>
|
||||||
|
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
|
||||||
|
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
|
||||||
|
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||||
|
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
|
||||||
|
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
|
||||||
|
readonly deepFreeze: UnwrapRef<typeof import('deep-freeze-es6')['default']>
|
||||||
|
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||||
|
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||||
|
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
||||||
|
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
|
||||||
|
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||||
|
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
||||||
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
|
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
|
||||||
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
|
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||||
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||||
|
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||||
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||||
|
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||||
|
readonly isShallow: UnwrapRef<typeof import('vue')['isShallow']>
|
||||||
|
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
||||||
|
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||||
|
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||||
|
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||||
|
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
|
||||||
|
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||||
|
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||||
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
|
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
||||||
|
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
|
||||||
|
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||||
|
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||||
|
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
||||||
|
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||||
|
readonly onElementRemoval: UnwrapRef<typeof import('@vueuse/core')['onElementRemoval']>
|
||||||
|
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||||
|
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
||||||
|
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
||||||
|
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||||
|
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||||
|
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||||
|
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||||
|
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||||
|
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
|
||||||
|
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||||
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
|
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
||||||
|
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||||
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
|
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||||
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
|
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
|
||||||
|
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
|
||||||
|
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
|
||||||
|
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||||
|
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||||
|
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
|
||||||
|
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
|
||||||
|
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
|
||||||
|
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
||||||
|
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
||||||
|
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||||
|
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
||||||
|
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||||
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
|
readonly setViewportCSSVars: UnwrapRef<typeof import('utils4u/browser')['setViewportCSSVars']>
|
||||||
|
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||||
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
|
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||||
|
readonly showOpenFilePicker: UnwrapRef<typeof import('utils4u/browser')['showOpenFilePicker']>
|
||||||
|
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
|
||||||
|
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
|
||||||
|
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
|
||||||
|
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
|
||||||
|
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
|
||||||
|
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
|
||||||
|
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||||
|
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
|
||||||
|
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||||
|
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||||
|
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||||
|
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||||
|
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
|
||||||
|
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
|
||||||
|
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
|
||||||
|
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
|
||||||
|
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
|
||||||
|
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||||
|
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
||||||
|
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||||
|
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||||
|
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
|
||||||
|
readonly useAppStore: UnwrapRef<typeof import('./src/stores/app-store')['useAppStore']>
|
||||||
|
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
|
||||||
|
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
||||||
|
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
||||||
|
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
|
||||||
|
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
|
||||||
|
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
|
||||||
|
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
|
||||||
|
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
|
||||||
|
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
|
||||||
|
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
|
||||||
|
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
|
||||||
|
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
|
||||||
|
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
|
||||||
|
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
|
||||||
|
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||||
|
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
|
||||||
|
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
|
||||||
|
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
|
||||||
|
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
|
||||||
|
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
|
||||||
|
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||||
|
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||||
|
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||||
|
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||||
|
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||||
|
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||||
|
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||||
|
readonly useCountdown: UnwrapRef<typeof import('@vueuse/core')['useCountdown']>
|
||||||
|
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
||||||
|
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||||
|
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
||||||
|
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||||
|
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
||||||
|
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
||||||
|
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
||||||
|
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
||||||
|
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
|
||||||
|
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
|
||||||
|
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
|
||||||
|
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
|
||||||
|
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
|
||||||
|
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
|
||||||
|
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
|
||||||
|
readonly useDialog: UnwrapRef<typeof import('naive-ui')['useDialog']>
|
||||||
|
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
|
||||||
|
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
|
||||||
|
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
|
||||||
|
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
|
||||||
|
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
|
||||||
|
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
|
||||||
|
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
|
||||||
|
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
|
||||||
|
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
|
||||||
|
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
|
||||||
|
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
|
||||||
|
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
|
||||||
|
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
|
||||||
|
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
|
||||||
|
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
|
||||||
|
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
|
||||||
|
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
|
||||||
|
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
|
||||||
|
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
|
||||||
|
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
|
||||||
|
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
||||||
|
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
||||||
|
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
||||||
|
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
|
||||||
|
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
||||||
|
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
||||||
|
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
||||||
|
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
||||||
|
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
|
||||||
|
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
|
||||||
|
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
|
||||||
|
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
||||||
|
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||||
|
readonly useLink: UnwrapRef<typeof import('vue-router/auto')['useLink']>
|
||||||
|
readonly useLoadingBar: UnwrapRef<typeof import('naive-ui')['useLoadingBar']>
|
||||||
|
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||||
|
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||||
|
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
|
||||||
|
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
|
||||||
|
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
|
||||||
|
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
|
||||||
|
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
|
||||||
|
readonly useMessage: UnwrapRef<typeof import('naive-ui')['useMessage']>
|
||||||
|
readonly useModal: UnwrapRef<typeof import('naive-ui')['useModal']>
|
||||||
|
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
|
||||||
|
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
|
||||||
|
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
|
||||||
|
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
||||||
|
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
|
||||||
|
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
|
||||||
|
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
|
||||||
|
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
|
||||||
|
readonly useNotification: UnwrapRef<typeof import('naive-ui')['useNotification']>
|
||||||
|
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
|
||||||
|
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
|
||||||
|
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
|
||||||
|
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
|
||||||
|
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
|
||||||
|
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
|
||||||
|
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
|
||||||
|
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
|
||||||
|
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
|
||||||
|
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
|
||||||
|
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
|
||||||
|
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
|
||||||
|
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
|
||||||
|
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
|
||||||
|
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
|
||||||
|
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
||||||
|
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||||
|
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
|
||||||
|
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||||
|
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||||
|
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||||
|
readonly useRefs: UnwrapRef<typeof import('utils4u/vue-use')['useRefs']>
|
||||||
|
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
||||||
|
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||||
|
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||||
|
readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
|
||||||
|
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
|
||||||
|
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
|
||||||
|
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
||||||
|
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
|
||||||
|
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
|
||||||
|
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
||||||
|
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
||||||
|
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||||
|
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
|
||||||
|
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
|
||||||
|
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
|
||||||
|
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
|
||||||
|
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
|
||||||
|
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
|
||||||
|
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
|
||||||
|
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
|
||||||
|
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
||||||
|
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
||||||
|
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
||||||
|
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
||||||
|
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
||||||
|
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
|
||||||
|
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
|
||||||
|
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
||||||
|
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
||||||
|
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
||||||
|
readonly useTimeAgoIntl: UnwrapRef<typeof import('@vueuse/core')['useTimeAgoIntl']>
|
||||||
|
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
||||||
|
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
||||||
|
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
||||||
|
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
|
||||||
|
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
|
||||||
|
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
|
||||||
|
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
|
||||||
|
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
|
||||||
|
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
||||||
|
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
||||||
|
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
|
||||||
|
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
|
||||||
|
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
||||||
|
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
||||||
|
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
||||||
|
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
||||||
|
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
||||||
|
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
||||||
|
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
||||||
|
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
|
||||||
|
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
|
||||||
|
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
|
||||||
|
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
|
||||||
|
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||||
|
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
|
||||||
|
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
|
||||||
|
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
|
||||||
|
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
|
||||||
|
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||||
|
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
|
||||||
|
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
|
||||||
|
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
|
||||||
|
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
|
||||||
|
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||||
|
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||||
|
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
|
||||||
|
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
|
||||||
|
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
|
||||||
|
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
|
||||||
|
}
|
||||||
|
}
|
||||||
9
commitlint.config.ts
Normal file
9
commitlint.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { UserConfig } from '@commitlint/types';
|
||||||
|
|
||||||
|
|
||||||
|
const Configuration: UserConfig = {
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
formatter: '@commitlint/format',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Configuration;
|
||||||
12
e2e/playwright/vue.spec.ts
Normal file
12
e2e/playwright/vue.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('Vue App', () => {
|
||||||
|
test('app renders correctly', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const app = page.locator('#app');
|
||||||
|
await expect(app).toBeVisible();
|
||||||
|
|
||||||
|
await page.locator('.app-loading').waitFor({ state: 'detached' });
|
||||||
|
});
|
||||||
|
});
|
||||||
4
e2e/tsconfig.json
Normal file
4
e2e/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": ["./**/*"]
|
||||||
|
}
|
||||||
9
env.d.ts
vendored
Normal file
9
env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/// <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" />
|
||||||
55
eslint.config.ts
Normal file
55
eslint.config.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { globalIgnores } from 'eslint/config';
|
||||||
|
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
|
||||||
|
import pluginVue from 'eslint-plugin-vue';
|
||||||
|
import pluginVitest from '@vitest/eslint-plugin';
|
||||||
|
import pluginPlaywright from 'eslint-plugin-playwright';
|
||||||
|
import pluginOxlint from 'eslint-plugin-oxlint';
|
||||||
|
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
|
||||||
|
|
||||||
|
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
||||||
|
import { configureVueProject } from '@vue/eslint-config-typescript';
|
||||||
|
configureVueProject({ scriptLangs: ['ts', 'tsx'] });
|
||||||
|
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
||||||
|
|
||||||
|
export default defineConfigWithVueTs(
|
||||||
|
{
|
||||||
|
name: 'app/files-to-lint',
|
||||||
|
files: ['**/*.{ts,mts,tsx,vue}'],
|
||||||
|
},
|
||||||
|
|
||||||
|
globalIgnores(['worker-configuration.d.ts', '**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
||||||
|
|
||||||
|
pluginVue.configs['flat/essential'],
|
||||||
|
vueTsConfigs.recommended,
|
||||||
|
|
||||||
|
{
|
||||||
|
...pluginVitest.configs.recommended,
|
||||||
|
files: ['src/**/__tests__/*'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
...pluginPlaywright.configs['flat/recommended'],
|
||||||
|
files: ['e2e/**/*.{test,spec}.{js,ts,jsx,tsx}'],
|
||||||
|
},
|
||||||
|
...pluginOxlint.configs['flat/recommended'],
|
||||||
|
skipFormatting,
|
||||||
|
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'vue/block-order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
order: ['script', 'template', 'style'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'vue/define-macros-order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
26
fake/upload.fake.ts
Normal file
26
fake/upload.fake.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// fake/user.fake.ts
|
||||||
|
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
|
||||||
|
|
||||||
|
let fail = !false;
|
||||||
|
export default defineFakeRoute([
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
rawResponse(req, res) {
|
||||||
|
fail = !fail;
|
||||||
|
if (fail) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: 'Upload failed' }));
|
||||||
|
} else {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ url: 'https://picsum.photos/200/300' }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: () => {
|
||||||
|
return {
|
||||||
|
url: 'https://picsum.photos/200/300',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
timeout: 2000,
|
||||||
|
url: '/fake/upload',
|
||||||
|
},
|
||||||
|
]);
|
||||||
126
index.html
Normal file
126
index.html
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN" data-build-time="%VITE_APP_BUILD_TIME%" data-commit="%VITE_APP_BUILD_COMMIT%">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<!-- <link rel="icon" href="data:;base64,iVBORw0KGgo=" /> -->
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover, user-scalable=no"
|
||||||
|
/>
|
||||||
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
<meta name="format-detection" content="telephone=no" />
|
||||||
|
<title>%VITE_APP_TITLE%</title>
|
||||||
|
<style type="text/css">
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style type="text/css">
|
||||||
|
#app {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (min-height: 100dvh) {
|
||||||
|
#app {
|
||||||
|
min-height: 100dvh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<!-- ontouchstart ontouchend -->
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<style type="text/css">
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.app-loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-top-color: #333;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background-color: #0f0f0f;
|
||||||
|
}
|
||||||
|
.app-loading-spinner {
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-top-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div
|
||||||
|
class="app-loading"
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="app-loading-spinner"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
<!-- <script src="https://testingcf.jsdelivr.net/npm/@vant/touch-emulator/dist/index.min.js"></script> -->
|
||||||
|
<!-- .min.js 是 jsDelivr 的特殊处理 -->
|
||||||
|
<!-- <script src="https://unpkg.luckincdn.com/@vant/touch-emulator@1.4.0/dist/index.js"></script> -->
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
(function (d) {
|
||||||
|
var config = {
|
||||||
|
kitId: 'whk2tto',
|
||||||
|
scriptTimeout: 3000,
|
||||||
|
async: true,
|
||||||
|
},
|
||||||
|
h = d.documentElement,
|
||||||
|
t = setTimeout(function () {
|
||||||
|
h.className = h.className.replace(/\bwf-loading\b/g, '') + ' wf-inactive';
|
||||||
|
}, config.scriptTimeout),
|
||||||
|
tk = d.createElement('script'),
|
||||||
|
f = false,
|
||||||
|
s = d.getElementsByTagName('script')[0],
|
||||||
|
a;
|
||||||
|
h.className += ' wf-loading';
|
||||||
|
tk.src = 'https://use.typekit.net/' + config.kitId + '.js';
|
||||||
|
tk.async = true;
|
||||||
|
tk.onload = tk.onreadystatechange = function () {
|
||||||
|
a = this.readyState;
|
||||||
|
if (f || (a && a != 'complete' && a != 'loaded')) return;
|
||||||
|
f = true;
|
||||||
|
clearTimeout(t);
|
||||||
|
try {
|
||||||
|
Typekit.load(config);
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
s.parentNode.insertBefore(tk, s);
|
||||||
|
}); /* (document) */
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
144
package.json
Normal file
144
package.json
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"packageManager": "pnpm@10.17.1",
|
||||||
|
"name": "vue-ts-example-2025",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"_all": "run-p build-only format type-check lint",
|
||||||
|
"dev": "vite --port 4730 --host --strictPort",
|
||||||
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"preview:wrangler": "pnpm run build && wrangler dev",
|
||||||
|
"lint": "run-s lint:*",
|
||||||
|
"format": "prettier --write src/",
|
||||||
|
"type-check": "vue-tsc --build",
|
||||||
|
"lint:stylelint": "stylelint \"**/*.{css,less,scss,vue}\" --fix --ignore-path .gitignore",
|
||||||
|
"lint:oxlint": "oxlint . --fix -D correctness --ignore-path .gitignore",
|
||||||
|
"lint:eslint": "eslint . --fix",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"test:unit:DisableWatch": "vitest --run",
|
||||||
|
"test:playwright": "playwright test",
|
||||||
|
"test:playwright:headless": "HEADLESS=true playwright test",
|
||||||
|
"test:playwright:ui": "playwright test --ui",
|
||||||
|
"test:playwright:chromium": "playwright test --project=chromium --quiet",
|
||||||
|
"_oxlint_cfg": "oxlint . --fix --ignore-path=.gitignore --print-config",
|
||||||
|
"__oxlint_-D": "oxlint . --fix --deny=correctness",
|
||||||
|
"-wrangler:pages:deploy:preview": "wrangler pages deploy dist --project-name=vue-ts-example-2025 --branch=preview",
|
||||||
|
"-wrangler:pages:deploy:prod": "wrangler pages deploy dist --project-name=vue-ts-example-2025",
|
||||||
|
"-deploy:preview": "run-s build-only wrangler:pages:deploy:preview",
|
||||||
|
"-deploy:prod": "run-s build-only wrangler:pages:deploy:prod",
|
||||||
|
"wrangler:deploy": "pnpm run build && wrangler deploy",
|
||||||
|
"wrangler:versions:upload": "pnpm run build && wrangler versions upload",
|
||||||
|
"cf-typegen": "wrangler types",
|
||||||
|
"postinstall": "wrangler types",
|
||||||
|
"_dep:dedupe": "pnpm dedupe",
|
||||||
|
"_dep:update": "pnpm dlx taze major --interactive",
|
||||||
|
"_sizecheck:Treemap": "pnpm dlx vite-bundle-visualizer -t treemap",
|
||||||
|
"_sizecheck:Sunburst": "pnpm dlx vite-bundle-visualizer -t sunburst",
|
||||||
|
"_sizecheck:Network": "pnpm dlx vite-bundle-visualizer -t network",
|
||||||
|
"_knip": "pnpm dlx knip",
|
||||||
|
"prepare": "husky"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"{src,e2e}/**/*.{js,jsx,ts,tsx,vue}": [
|
||||||
|
"prettier --write",
|
||||||
|
"eslint --fix",
|
||||||
|
"oxlint --fix"
|
||||||
|
],
|
||||||
|
"{src,packages}/**/*.{css,less,scss,vue}": [
|
||||||
|
"stylelint --fix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"vue-tsc": "$vue-tsc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@commitlint/cli": "^20.0.0",
|
||||||
|
"@commitlint/config-conventional": "^20.0.0",
|
||||||
|
"@formkit/auto-animate": "^0.9.0",
|
||||||
|
"@pinia/colada": "^0.17.4",
|
||||||
|
"@primeuix/themes": "^1.2.3",
|
||||||
|
"@sa/materials": "workspace:*",
|
||||||
|
"@unhead/vue": "^2.0.14",
|
||||||
|
"@vueuse/core": "^13.9.0",
|
||||||
|
"naive-ui": "^2.43.1",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"primelocale": "^2.1.7",
|
||||||
|
"primevue": "^4.3.9",
|
||||||
|
"ts-enum-util": "^4.1.0",
|
||||||
|
"utils4u": "^4.2.3",
|
||||||
|
"vue": "^3.5.21",
|
||||||
|
"vue-i18n": "^11.1.12",
|
||||||
|
"vue-router": "^4.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/vite-plugin": "^1.13.2",
|
||||||
|
"@commitlint/types": "^20.0.0",
|
||||||
|
"@iconify-json/carbon": "^1.2.13",
|
||||||
|
"@iconify-json/line-md": "^1.2.11",
|
||||||
|
"@intlify/unplugin-vue-i18n": "^11.0.0",
|
||||||
|
"@playwright/test": "^1.55.0",
|
||||||
|
"@prettier/plugin-oxc": "^0.0.4",
|
||||||
|
"@primevue/auto-import-resolver": "^4.3.9",
|
||||||
|
"@primevue/metadata": "^4.3.9",
|
||||||
|
"@tsconfig/node22": "^22.0.2",
|
||||||
|
"@types/html-minifier-terser": "^7.0.2",
|
||||||
|
"@types/jsdom": "^27.0.0",
|
||||||
|
"@types/node": "^22.18.1",
|
||||||
|
"@vant/auto-import-resolver": "^1.3.0",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||||
|
"@vitest/eslint-plugin": "^1.3.9",
|
||||||
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
"@vue/eslint-config-typescript": "^14.6.0",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"@vue/tsconfig": "^0.8.1",
|
||||||
|
"eslint": "^9.35.0",
|
||||||
|
"eslint-plugin-oxlint": "~1.22.0",
|
||||||
|
"eslint-plugin-playwright": "^2.2.2",
|
||||||
|
"eslint-plugin-vue": "~10.5.0",
|
||||||
|
"happy-dom": "^20.0.1",
|
||||||
|
"html-minifier-terser": "^7.2.0",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"jiti": "^2.5.1",
|
||||||
|
"jsdom": "^27.0.0",
|
||||||
|
"lint-staged": "^16.1.6",
|
||||||
|
"npm-run-all2": "^8.0.4",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"oxlint": "~1.22.0",
|
||||||
|
"postcss-html": "^1.8.0",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"sass-embedded": "^1.93.2",
|
||||||
|
"stylelint": "^16.25.0",
|
||||||
|
"stylelint-config-recess-order": "^7.3.0",
|
||||||
|
"stylelint-config-standard": "^39.0.1",
|
||||||
|
"stylelint-config-standard-scss": "^16.0.0",
|
||||||
|
"stylelint-config-standard-vue": "^1.0.0",
|
||||||
|
"typescript": "~5.9.2",
|
||||||
|
"unocss": "^66.5.1",
|
||||||
|
"unocss-preset-animations": "^1.2.1",
|
||||||
|
"unplugin-auto-import": "^20.1.0",
|
||||||
|
"unplugin-icons": "^22.2.0",
|
||||||
|
"unplugin-vue-components": "^29.0.0",
|
||||||
|
"unplugin-vue-markdown": "^29.1.0",
|
||||||
|
"unplugin-vue-router": "^0.15.0",
|
||||||
|
"vite": "^7.1.5",
|
||||||
|
"vite-plugin-checker": "^0.11.0",
|
||||||
|
"vite-plugin-fake-server": "^2.2.0",
|
||||||
|
"vite-plugin-image-optimizer": "^2.0.2",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.1",
|
||||||
|
"vite-plugin-vue-meta-layouts": "^0.6.0",
|
||||||
|
"vite-plugin-webfont-dl": "^3.11.1",
|
||||||
|
"vitest": "^3.2.4",
|
||||||
|
"vue-macros": "3.1.1",
|
||||||
|
"vue-tsc": "^3.1.0",
|
||||||
|
"wrangler": "^4.37.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/materials/package.json
Normal file
20
packages/materials/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@sa/materials",
|
||||||
|
"version": "1.3.15",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"typesVersions": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"gen:css-types": "bunx --bun typed-css-modules src --pattern '**/*.module.css'"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"simplebar-vue": "2.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
packages/materials/src/index.ts
Normal file
6
packages/materials/src/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import AdminLayout, { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './libs/admin-layout';
|
||||||
|
|
||||||
|
import SimpleScrollbar from './libs/simple-scrollbar';
|
||||||
|
|
||||||
|
export { AdminLayout, LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX, SimpleScrollbar };
|
||||||
|
export * from './types';
|
||||||
63
packages/materials/src/libs/admin-layout/index.module.css
Normal file
63
packages/materials/src/libs/admin-layout/index.module.css
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/* @type */
|
||||||
|
|
||||||
|
.layout-header,
|
||||||
|
.layout-header-placement {
|
||||||
|
height: var(--soy-header-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-header {
|
||||||
|
z-index: var(--soy-header-z-index);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-tab {
|
||||||
|
top: var(--soy-header-height);
|
||||||
|
z-index: var(--soy-tab-z-index);
|
||||||
|
height: var(--soy-tab-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-tab-placement {
|
||||||
|
height: var(--soy-tab-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sider {
|
||||||
|
z-index: var(--soy-sider-z-index);
|
||||||
|
width: var(--soy-sider-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile-sider {
|
||||||
|
z-index: var(--soy-sider-z-index);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-mobile-sider-mask {
|
||||||
|
z-index: var(--soy-mobile-sider-z-index);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-sider-collapsed {
|
||||||
|
z-index: var(--soy-sider-z-index);
|
||||||
|
width: var(--soy-sider-collapsed-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-footer,
|
||||||
|
.layout-footer-placement {
|
||||||
|
height: var(--soy-footer-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-footer {
|
||||||
|
z-index: var(--soy-footer-z-index);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-gap {
|
||||||
|
padding-left: var(--soy-sider-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-gap-collapsed {
|
||||||
|
padding-left: var(--soy-sider-collapsed-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sider-padding-top {
|
||||||
|
padding-top: var(--soy-header-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sider-padding-bottom {
|
||||||
|
padding-bottom: var(--soy-footer-height);
|
||||||
|
}
|
||||||
17
packages/materials/src/libs/admin-layout/index.module.css.d.ts
vendored
Normal file
17
packages/materials/src/libs/admin-layout/index.module.css.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
declare const styles: {
|
||||||
|
readonly 'layout-footer': string;
|
||||||
|
readonly 'layout-footer-placement': string;
|
||||||
|
readonly 'layout-header': string;
|
||||||
|
readonly 'layout-header-placement': string;
|
||||||
|
readonly 'layout-mobile-sider': string;
|
||||||
|
readonly 'layout-mobile-sider-mask': string;
|
||||||
|
readonly 'layout-sider': string;
|
||||||
|
readonly 'layout-sider-collapsed': string;
|
||||||
|
readonly 'layout-tab': string;
|
||||||
|
readonly 'layout-tab-placement': string;
|
||||||
|
readonly 'left-gap': string;
|
||||||
|
readonly 'left-gap-collapsed': string;
|
||||||
|
readonly 'sider-padding-bottom': string;
|
||||||
|
readonly 'sider-padding-top': string;
|
||||||
|
};
|
||||||
|
export = styles;
|
||||||
5
packages/materials/src/libs/admin-layout/index.ts
Normal file
5
packages/materials/src/libs/admin-layout/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import AdminLayout from './index.vue';
|
||||||
|
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './shared';
|
||||||
|
|
||||||
|
export default AdminLayout;
|
||||||
|
export { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX };
|
||||||
238
packages/materials/src/libs/admin-layout/index.vue
Normal file
238
packages/materials/src/libs/admin-layout/index.vue
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import type { AdminLayoutProps } from '../../types';
|
||||||
|
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID, createLayoutCssVars } from './shared';
|
||||||
|
import style from './index.module.css';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'AdminLayout',
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<AdminLayoutProps>(), {
|
||||||
|
mode: 'vertical',
|
||||||
|
scrollMode: 'content',
|
||||||
|
scrollElId: LAYOUT_SCROLL_EL_ID,
|
||||||
|
commonClass: 'transition-all-300',
|
||||||
|
fixedTop: true,
|
||||||
|
maxZIndex: LAYOUT_MAX_Z_INDEX,
|
||||||
|
headerVisible: true,
|
||||||
|
headerHeight: 56,
|
||||||
|
tabVisible: true,
|
||||||
|
tabHeight: 48,
|
||||||
|
siderVisible: true,
|
||||||
|
siderCollapse: false,
|
||||||
|
siderWidth: 220,
|
||||||
|
siderCollapsedWidth: 64,
|
||||||
|
footerVisible: true,
|
||||||
|
footerHeight: 48,
|
||||||
|
rightFooter: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
|
||||||
|
const slots = defineSlots<Slots>();
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
/** Update siderCollapse */
|
||||||
|
(e: 'update:siderCollapse', collapse: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||||
|
|
||||||
|
type Slots = {
|
||||||
|
/** Main */
|
||||||
|
default?: SlotFn;
|
||||||
|
/** Header */
|
||||||
|
header?: SlotFn;
|
||||||
|
/** Tab */
|
||||||
|
tab?: SlotFn;
|
||||||
|
/** Sider */
|
||||||
|
sider?: SlotFn;
|
||||||
|
/** Footer */
|
||||||
|
footer?: SlotFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cssVars = computed(() => createLayoutCssVars(props));
|
||||||
|
|
||||||
|
// config visible
|
||||||
|
const showHeader = computed(() => Boolean(slots.header) && props.headerVisible);
|
||||||
|
const showTab = computed(() => Boolean(slots.tab) && props.tabVisible);
|
||||||
|
const showSider = computed(() => !props.isMobile && Boolean(slots.sider) && props.siderVisible);
|
||||||
|
const showMobileSider = computed(
|
||||||
|
() => props.isMobile && Boolean(slots.sider) && props.siderVisible,
|
||||||
|
);
|
||||||
|
const showFooter = computed(() => Boolean(slots.footer) && props.footerVisible);
|
||||||
|
|
||||||
|
// scroll mode
|
||||||
|
const isWrapperScroll = computed(() => props.scrollMode === 'wrapper');
|
||||||
|
const isContentScroll = computed(() => props.scrollMode === 'content');
|
||||||
|
|
||||||
|
// layout direction
|
||||||
|
const isVertical = computed(() => props.mode === 'vertical');
|
||||||
|
const isHorizontal = computed(() => props.mode === 'horizontal');
|
||||||
|
|
||||||
|
const fixedHeaderAndTab = computed(
|
||||||
|
() => props.fixedTop || (isHorizontal.value && isWrapperScroll.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
// css
|
||||||
|
const leftGapClass = computed(() => {
|
||||||
|
if (!props.fullContent && showSider.value) {
|
||||||
|
return props.siderCollapse ? style['left-gap-collapsed'] : style['left-gap'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const headerLeftGapClass = computed(() => (isVertical.value ? leftGapClass.value : ''));
|
||||||
|
|
||||||
|
const footerLeftGapClass = computed(() => {
|
||||||
|
const condition1 = isVertical.value;
|
||||||
|
const condition2 = isHorizontal.value && isWrapperScroll.value && !props.fixedFooter;
|
||||||
|
const condition3 = Boolean(isHorizontal.value && props.rightFooter);
|
||||||
|
|
||||||
|
if (condition1 || condition2 || condition3) {
|
||||||
|
return leftGapClass.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const siderPaddingClass = computed(() => {
|
||||||
|
let cls = '';
|
||||||
|
|
||||||
|
if (showHeader.value && !headerLeftGapClass.value) {
|
||||||
|
cls += style['sider-padding-top'];
|
||||||
|
}
|
||||||
|
if (showFooter.value && !footerLeftGapClass.value) {
|
||||||
|
cls += ` ${style['sider-padding-bottom']}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cls;
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleClickMask() {
|
||||||
|
emit('update:siderCollapse', true);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative h-full" :class="[commonClass]" :style="cssVars">
|
||||||
|
<div
|
||||||
|
:id="isWrapperScroll ? scrollElId : undefined"
|
||||||
|
class="h-full flex flex-col"
|
||||||
|
:class="[commonClass, scrollWrapperClass, { 'overflow-y-auto': isWrapperScroll }]"
|
||||||
|
>
|
||||||
|
<!-- Header -->
|
||||||
|
<template v-if="showHeader">
|
||||||
|
<header
|
||||||
|
v-show="!fullContent"
|
||||||
|
class="flex-shrink-0"
|
||||||
|
:class="[
|
||||||
|
style['layout-header'],
|
||||||
|
commonClass,
|
||||||
|
headerLeftGapClass,
|
||||||
|
{ 'absolute top-0 left-0 w-full': fixedHeaderAndTab },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot name="header"></slot>
|
||||||
|
</header>
|
||||||
|
<div
|
||||||
|
v-show="!fullContent && fixedHeaderAndTab"
|
||||||
|
class="flex-shrink-0 overflow-hidden"
|
||||||
|
:class="[style['layout-header-placement']]"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Tab -->
|
||||||
|
<template v-if="showTab">
|
||||||
|
<div
|
||||||
|
class="flex-shrink-0 overflow-hidden"
|
||||||
|
:class="[
|
||||||
|
style['layout-tab'],
|
||||||
|
commonClass,
|
||||||
|
tabClass,
|
||||||
|
{ 'top-0!': fullContent || !showHeader },
|
||||||
|
leftGapClass,
|
||||||
|
{ 'absolute left-0 w-full': fixedHeaderAndTab },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot name="tab"></slot>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-show="fullContent || fixedHeaderAndTab"
|
||||||
|
class="flex-shrink-0 overflow-hidden"
|
||||||
|
:class="[style['layout-tab-placement']]"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Sider -->
|
||||||
|
<template v-if="showSider">
|
||||||
|
<aside
|
||||||
|
v-show="!fullContent"
|
||||||
|
class="absolute left-0 top-0 h-full"
|
||||||
|
:class="[
|
||||||
|
commonClass,
|
||||||
|
siderClass,
|
||||||
|
siderPaddingClass,
|
||||||
|
siderCollapse ? style['layout-sider-collapsed'] : style['layout-sider'],
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot name="sider"></slot>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Mobile Sider -->
|
||||||
|
<template v-if="showMobileSider">
|
||||||
|
<aside
|
||||||
|
class="absolute left-0 top-0 h-full w-0 bg-white"
|
||||||
|
:class="[
|
||||||
|
commonClass,
|
||||||
|
mobileSiderClass,
|
||||||
|
style['layout-mobile-sider'],
|
||||||
|
siderCollapse ? 'overflow-hidden' : style['layout-sider'],
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot name="sider"></slot>
|
||||||
|
</aside>
|
||||||
|
<div
|
||||||
|
v-show="!siderCollapse"
|
||||||
|
class="absolute left-0 top-0 h-full w-full bg-[rgba(0,0,0,0.2)]"
|
||||||
|
:class="[style['layout-mobile-sider-mask']]"
|
||||||
|
@click="handleClickMask"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main
|
||||||
|
:id="isContentScroll ? scrollElId : undefined"
|
||||||
|
class="flex flex-col flex-grow"
|
||||||
|
:class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<template v-if="showFooter">
|
||||||
|
<footer
|
||||||
|
v-show="!fullContent"
|
||||||
|
class="flex-shrink-0"
|
||||||
|
:class="[
|
||||||
|
style['layout-footer'],
|
||||||
|
commonClass,
|
||||||
|
footerClass,
|
||||||
|
footerLeftGapClass,
|
||||||
|
{ 'absolute left-0 bottom-0 w-full': fixedFooter },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</footer>
|
||||||
|
<div
|
||||||
|
v-show="!fullContent && fixedFooter"
|
||||||
|
class="flex-shrink-0 overflow-hidden"
|
||||||
|
:class="[style['layout-footer-placement']]"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
68
packages/materials/src/libs/admin-layout/shared.ts
Normal file
68
packages/materials/src/libs/admin-layout/shared.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import type { AdminLayoutProps, LayoutCssVars, LayoutCssVarsProps } from '../../types';
|
||||||
|
|
||||||
|
/** The id of the scroll element of the layout */
|
||||||
|
export const LAYOUT_SCROLL_EL_ID = '__SCROLL_EL_ID__';
|
||||||
|
|
||||||
|
/** The max z-index of the layout */
|
||||||
|
export const LAYOUT_MAX_Z_INDEX = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create layout css vars by css vars props
|
||||||
|
*
|
||||||
|
* @param props Css vars props
|
||||||
|
*/
|
||||||
|
function createLayoutCssVarsByCssVarsProps(props: LayoutCssVarsProps) {
|
||||||
|
const cssVars: LayoutCssVars = {
|
||||||
|
'--soy-header-height': `${props.headerHeight}px`,
|
||||||
|
'--soy-header-z-index': props.headerZIndex,
|
||||||
|
'--soy-tab-height': `${props.tabHeight}px`,
|
||||||
|
'--soy-tab-z-index': props.tabZIndex,
|
||||||
|
'--soy-sider-width': `${props.siderWidth}px`,
|
||||||
|
'--soy-sider-collapsed-width': `${props.siderCollapsedWidth}px`,
|
||||||
|
'--soy-sider-z-index': props.siderZIndex,
|
||||||
|
'--soy-mobile-sider-z-index': props.mobileSiderZIndex,
|
||||||
|
'--soy-footer-height': `${props.footerHeight}px`,
|
||||||
|
'--soy-footer-z-index': props.footerZIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
return cssVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create layout css vars
|
||||||
|
*
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
export function createLayoutCssVars(props: AdminLayoutProps) {
|
||||||
|
const {
|
||||||
|
mode,
|
||||||
|
isMobile,
|
||||||
|
maxZIndex = LAYOUT_MAX_Z_INDEX,
|
||||||
|
headerHeight,
|
||||||
|
tabHeight,
|
||||||
|
siderWidth,
|
||||||
|
siderCollapsedWidth,
|
||||||
|
footerHeight
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const headerZIndex = maxZIndex - 3;
|
||||||
|
const tabZIndex = maxZIndex - 5;
|
||||||
|
const siderZIndex = mode === 'vertical' || isMobile ? maxZIndex - 1 : maxZIndex - 4;
|
||||||
|
const mobileSiderZIndex = isMobile ? maxZIndex - 2 : 0;
|
||||||
|
const footerZIndex = maxZIndex - 5;
|
||||||
|
|
||||||
|
const cssProps: LayoutCssVarsProps = {
|
||||||
|
headerHeight,
|
||||||
|
headerZIndex,
|
||||||
|
tabHeight,
|
||||||
|
tabZIndex,
|
||||||
|
siderWidth,
|
||||||
|
siderZIndex,
|
||||||
|
mobileSiderZIndex,
|
||||||
|
siderCollapsedWidth,
|
||||||
|
footerHeight,
|
||||||
|
footerZIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
return createLayoutCssVarsByCssVarsProps(cssProps);
|
||||||
|
}
|
||||||
3
packages/materials/src/libs/simple-scrollbar/index.ts
Normal file
3
packages/materials/src/libs/simple-scrollbar/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import SimpleScrollbar from './index.vue';
|
||||||
|
|
||||||
|
export default SimpleScrollbar;
|
||||||
16
packages/materials/src/libs/simple-scrollbar/index.vue
Normal file
16
packages/materials/src/libs/simple-scrollbar/index.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Simplebar from 'simplebar-vue';
|
||||||
|
import 'simplebar-vue/dist/simplebar.min.css';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'SimpleScrollbar',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex-1-hidden">
|
||||||
|
<Simplebar class="h-full">
|
||||||
|
<slot />
|
||||||
|
</Simplebar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
288
packages/materials/src/types/index.ts
Normal file
288
packages/materials/src/types/index.ts
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
/** Header config */
|
||||||
|
interface AdminLayoutHeaderConfig {
|
||||||
|
/**
|
||||||
|
* Whether header is visible
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
headerVisible?: boolean;
|
||||||
|
/**
|
||||||
|
* Header height
|
||||||
|
*
|
||||||
|
* @default 56px
|
||||||
|
*/
|
||||||
|
headerHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tab config */
|
||||||
|
interface AdminLayoutTabConfig {
|
||||||
|
/**
|
||||||
|
* Whether tab is visible
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
tabVisible?: boolean;
|
||||||
|
/**
|
||||||
|
* Tab class
|
||||||
|
*
|
||||||
|
* @default ''
|
||||||
|
*/
|
||||||
|
tabClass?: string;
|
||||||
|
/**
|
||||||
|
* Tab height
|
||||||
|
*
|
||||||
|
* @default 48px
|
||||||
|
*/
|
||||||
|
tabHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sider config */
|
||||||
|
interface AdminLayoutSiderConfig {
|
||||||
|
/**
|
||||||
|
* Whether sider is visible
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
siderVisible?: boolean;
|
||||||
|
/**
|
||||||
|
* Sider class
|
||||||
|
*
|
||||||
|
* @default ''
|
||||||
|
*/
|
||||||
|
siderClass?: string;
|
||||||
|
/**
|
||||||
|
* Mobile sider class
|
||||||
|
*
|
||||||
|
* @default ''
|
||||||
|
*/
|
||||||
|
mobileSiderClass?: string;
|
||||||
|
/**
|
||||||
|
* Sider collapse status
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
siderCollapse?: boolean;
|
||||||
|
/**
|
||||||
|
* Sider width when collapse is false
|
||||||
|
*
|
||||||
|
* @default '220px'
|
||||||
|
*/
|
||||||
|
siderWidth?: number;
|
||||||
|
/**
|
||||||
|
* Sider width when collapse is true
|
||||||
|
*
|
||||||
|
* @default '64px'
|
||||||
|
*/
|
||||||
|
siderCollapsedWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Content config */
|
||||||
|
export interface AdminLayoutContentConfig {
|
||||||
|
/**
|
||||||
|
* Content class
|
||||||
|
*
|
||||||
|
* @default ''
|
||||||
|
*/
|
||||||
|
contentClass?: string;
|
||||||
|
/**
|
||||||
|
* Whether content is full the page
|
||||||
|
*
|
||||||
|
* If true, other elements will be hidden by `display: none`
|
||||||
|
*/
|
||||||
|
fullContent?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Footer config */
|
||||||
|
export interface AdminLayoutFooterConfig {
|
||||||
|
/**
|
||||||
|
* Whether footer is visible
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
footerVisible?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether footer is fixed
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
fixedFooter?: boolean;
|
||||||
|
/**
|
||||||
|
* Footer class
|
||||||
|
*
|
||||||
|
* @default ''
|
||||||
|
*/
|
||||||
|
footerClass?: string;
|
||||||
|
/**
|
||||||
|
* Footer height
|
||||||
|
*
|
||||||
|
* @default 48px
|
||||||
|
*/
|
||||||
|
footerHeight?: number;
|
||||||
|
/**
|
||||||
|
* Whether footer is on the right side
|
||||||
|
*
|
||||||
|
* When the layout is vertical, the footer is on the right side
|
||||||
|
*/
|
||||||
|
rightFooter?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout mode
|
||||||
|
*
|
||||||
|
* - Horizontal
|
||||||
|
* - Vertical
|
||||||
|
*/
|
||||||
|
export type LayoutMode = 'horizontal' | 'vertical';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scroll mode when content overflow
|
||||||
|
*
|
||||||
|
* - Wrapper: the layout component's wrapper element has a scrollbar
|
||||||
|
* - Content: the layout component's content element has a scrollbar
|
||||||
|
*
|
||||||
|
* @default 'wrapper'
|
||||||
|
*/
|
||||||
|
export type LayoutScrollMode = 'wrapper' | 'content';
|
||||||
|
|
||||||
|
/** Admin layout props */
|
||||||
|
export interface AdminLayoutProps
|
||||||
|
extends AdminLayoutHeaderConfig,
|
||||||
|
AdminLayoutTabConfig,
|
||||||
|
AdminLayoutSiderConfig,
|
||||||
|
AdminLayoutContentConfig,
|
||||||
|
AdminLayoutFooterConfig {
|
||||||
|
/**
|
||||||
|
* Layout mode
|
||||||
|
*
|
||||||
|
* - {@link LayoutMode}
|
||||||
|
*/
|
||||||
|
mode?: LayoutMode;
|
||||||
|
/** Is mobile layout */
|
||||||
|
isMobile?: boolean;
|
||||||
|
/**
|
||||||
|
* Scroll mode
|
||||||
|
*
|
||||||
|
* - {@link ScrollMode}
|
||||||
|
*/
|
||||||
|
scrollMode?: LayoutScrollMode;
|
||||||
|
/**
|
||||||
|
* The id of the scroll element of the layout
|
||||||
|
*
|
||||||
|
* It can be used to get the corresponding Dom and scroll it
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* use the default id by import
|
||||||
|
* ```ts
|
||||||
|
* import { adminLayoutScrollElId } from '@sa/vue-materials';
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @default
|
||||||
|
* ```ts
|
||||||
|
* const adminLayoutScrollElId = '__ADMIN_LAYOUT_SCROLL_EL_ID__'
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
scrollElId?: string;
|
||||||
|
/** The class of the scroll element */
|
||||||
|
scrollElClass?: string;
|
||||||
|
/** The class of the scroll wrapper element */
|
||||||
|
scrollWrapperClass?: string;
|
||||||
|
/**
|
||||||
|
* The common class of the layout
|
||||||
|
*
|
||||||
|
* Is can be used to configure the transition animation
|
||||||
|
*
|
||||||
|
* @default 'transition-all-300'
|
||||||
|
*/
|
||||||
|
commonClass?: string;
|
||||||
|
/**
|
||||||
|
* Whether fix the header and tab
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
fixedTop?: boolean;
|
||||||
|
/**
|
||||||
|
* The max z-index of the layout
|
||||||
|
*
|
||||||
|
* The z-index of Header,Tab,Sider and Footer will not exceed this value
|
||||||
|
*/
|
||||||
|
maxZIndex?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Kebab<S extends string> = S extends Uncapitalize<S> ? S : `-${Uncapitalize<S>}`;
|
||||||
|
|
||||||
|
type KebabCase<S extends string> = S extends `${infer Start}${infer End}`
|
||||||
|
? `${Uncapitalize<Start>}${KebabCase<Kebab<End>>}`
|
||||||
|
: S;
|
||||||
|
|
||||||
|
type Prefix = '--soy-';
|
||||||
|
|
||||||
|
export type LayoutCssVarsProps = Pick<
|
||||||
|
AdminLayoutProps,
|
||||||
|
'headerHeight' | 'tabHeight' | 'siderWidth' | 'siderCollapsedWidth' | 'footerHeight'
|
||||||
|
> & {
|
||||||
|
headerZIndex?: number;
|
||||||
|
tabZIndex?: number;
|
||||||
|
siderZIndex?: number;
|
||||||
|
mobileSiderZIndex?: number;
|
||||||
|
footerZIndex?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LayoutCssVars = {
|
||||||
|
[K in keyof LayoutCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mode of the tab
|
||||||
|
*
|
||||||
|
* - Button: button style
|
||||||
|
* - Chrome: chrome style
|
||||||
|
*
|
||||||
|
* @default chrome
|
||||||
|
*/
|
||||||
|
export type PageTabMode = 'button' | 'chrome';
|
||||||
|
|
||||||
|
export interface PageTabProps {
|
||||||
|
/** Whether is dark mode */
|
||||||
|
darkMode?: boolean;
|
||||||
|
/**
|
||||||
|
* The mode of the tab
|
||||||
|
*
|
||||||
|
* - {@link TabMode}
|
||||||
|
*/
|
||||||
|
mode?: PageTabMode;
|
||||||
|
/**
|
||||||
|
* The common class of the layout
|
||||||
|
*
|
||||||
|
* Is can be used to configure the transition animation
|
||||||
|
*
|
||||||
|
* @default 'transition-all-300'
|
||||||
|
*/
|
||||||
|
commonClass?: string;
|
||||||
|
/** The class of the button tab */
|
||||||
|
buttonClass?: string;
|
||||||
|
/** The class of the chrome tab */
|
||||||
|
chromeClass?: string;
|
||||||
|
/** Whether the tab is active */
|
||||||
|
active?: boolean;
|
||||||
|
/** The color of the active tab */
|
||||||
|
activeColor?: string;
|
||||||
|
/**
|
||||||
|
* Whether the tab is closable
|
||||||
|
*
|
||||||
|
* Show the close icon when true
|
||||||
|
*/
|
||||||
|
closable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageTabCssVarsProps = {
|
||||||
|
primaryColor: string;
|
||||||
|
primaryColor1: string;
|
||||||
|
primaryColor2: string;
|
||||||
|
primaryColorOpacity1: string;
|
||||||
|
primaryColorOpacity2: string;
|
||||||
|
primaryColorOpacity3: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PageTabCssVars = {
|
||||||
|
[K in keyof PageTabCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
|
||||||
|
};
|
||||||
20
packages/materials/tsconfig.json
Normal file
20
packages/materials/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"lib": ["DOM", "ESNext"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"types": ["node"],
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
113
playwright.config.ts
Normal file
113
playwright.config.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
|
// const runningInVSCode = process.env.TERM_PROGRAM === 'vscode'
|
||||||
|
const baseURL = 'http://localhost:4173';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './e2e/playwright',
|
||||||
|
/* Maximum time one test can run for. */
|
||||||
|
timeout: 30 * 1000,
|
||||||
|
expect: {
|
||||||
|
/**
|
||||||
|
* Maximum time expect() should wait for the condition to be met.
|
||||||
|
* For example in `await expect(locator).toHaveText();`
|
||||||
|
*/
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: 'html',
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||||
|
actionTimeout: 0,
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL,
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
|
||||||
|
/* Only on CI systems run the tests headless */
|
||||||
|
headless: !!process.env.CI || process.env.HEADLESS === 'true',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Firefox'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Safari'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: {
|
||||||
|
// ...devices['Pixel 5'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: {
|
||||||
|
// ...devices['iPhone 12'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: {
|
||||||
|
// channel: 'msedge',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: {
|
||||||
|
// channel: 'chrome',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||||
|
outputDir: 'playwright-test-results/',
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
/**
|
||||||
|
* Use the dev server by default for faster feedback loop.
|
||||||
|
* Use the preview server on CI for more realistic testing.
|
||||||
|
* Playwright will re-use the local server if there is already a dev-server running.
|
||||||
|
*/
|
||||||
|
command: 'pnpm run build-only; pnpm run preview',
|
||||||
|
port: Number(new URL(baseURL).port),
|
||||||
|
reuseExistingServer: true /* !process.env.CI */,
|
||||||
|
},
|
||||||
|
});
|
||||||
10639
pnpm-lock.yaml
generated
Normal file
10639
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- "packages/*"
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
8
renovate.json
Normal file
8
renovate.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"https://git.1-h.cc/examples/renovate-example/raw/branch/main/default.json5",
|
||||||
|
":automergeMinor"
|
||||||
|
],
|
||||||
|
"postUpdateOptions": ["pnpmDedupe"]
|
||||||
|
}
|
||||||
18
server/index.ts
Normal file
18
server/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
async fetch(request, env) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1')
|
||||||
|
await new Promise((r) => setTimeout(r, 250));
|
||||||
|
|
||||||
|
if (url.pathname.startsWith('/api/')) {
|
||||||
|
await env.KV.put('last-api-call', `${Date.now()} ${request.method} ${url.pathname}`);
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
lastApiCall: await env.KV.get('last-api-call'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Response(null, { status: 404 });
|
||||||
|
},
|
||||||
|
} satisfies ExportedHandler<Env>;
|
||||||
35
src/App.spec.ts
Normal file
35
src/App.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* @vitest-environment happy-dom
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* https://pinia.vuejs.org/zh/cookbook/testing.html#unit-testing-components
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createMemoryHistory(),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: {
|
||||||
|
template: 'Welcome to the blogging app',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import { createMemoryHistory, createRouter } from 'vue-router';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
it('renders RouterView', async () => {
|
||||||
|
router.push('/');
|
||||||
|
await router.isReady();
|
||||||
|
|
||||||
|
const wrapper = mount(App, { global: { plugins: [router, createPinia()] } });
|
||||||
|
expect(wrapper.text()).toContain('Welcome to the blogging app');
|
||||||
|
});
|
||||||
|
});
|
||||||
14
src/App.vue
Normal file
14
src/App.vue
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
import { darkTheme } from 'naive-ui';
|
||||||
|
const appStore = useAppStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DynamicDialog />
|
||||||
|
<ConfirmDialog />
|
||||||
|
<Toast />
|
||||||
|
<n-config-provider preflight-style-disabled :theme="appStore.isDark ? darkTheme : null" abstract>
|
||||||
|
<RouterView />
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
16
src/assets/icons/svgs/demo.svg
Normal file
16
src/assets/icons/svgs/demo.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- 中心圆形 -->
|
||||||
|
<circle cx="100" cy="100" r="35" fill="#FDB813"/>
|
||||||
|
|
||||||
|
<!-- 光芒 -->
|
||||||
|
<g stroke="#FDB813" stroke-width="8" stroke-linecap="round">
|
||||||
|
<line x1="100" y1="30" x2="100" y2="50"/>
|
||||||
|
<line x1="141" y1="41" x2="129" y2="59"/>
|
||||||
|
<line x1="170" y1="100" x2="150" y2="100"/>
|
||||||
|
<line x1="141" y1="159" x2="129" y2="141"/>
|
||||||
|
<line x1="100" y1="170" x2="100" y2="150"/>
|
||||||
|
<line x1="59" y1="159" x2="71" y2="141"/>
|
||||||
|
<line x1="30" y1="100" x2="50" y2="100"/>
|
||||||
|
<line x1="59" y1="41" x2="71" y2="59"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 608 B |
50
src/layouts/base-layout/base-layout-header.vue
Normal file
50
src/layouts/base-layout/base-layout-header.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const collapsed = defineModel<boolean>('collapsed');
|
||||||
|
const buttonRef = useTemplateRef('buttonRef');
|
||||||
|
const appStore = useAppStore();
|
||||||
|
function toggleCollapsed() {
|
||||||
|
// https://github.com/tusen-ai/naive-ui/issues/3688
|
||||||
|
// hover style 鼠标移出就会消失 如果是点击 button 会聚焦需要失去焦点才会恢复
|
||||||
|
buttonRef.value?.$el.blur();
|
||||||
|
collapsed.value = !collapsed.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeLabels: Record<AppThemeMode, string> = {
|
||||||
|
light: '浅色',
|
||||||
|
dark: '深色',
|
||||||
|
system: '跟随系统',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex items-center justify-between px-12px shadow-header dark:shadow-gray-700">
|
||||||
|
<NTooltip :disabled="appStore.isMobile" placement="bottom-start">
|
||||||
|
{{ collapsed ? '展开菜单' : '收起菜单' }}
|
||||||
|
<template #trigger>
|
||||||
|
<NButton ref="buttonRef" quaternary @click="toggleCollapsed">
|
||||||
|
<icon-line-md:menu-fold-right v-if="collapsed" w-4.5 h-4.5 />
|
||||||
|
<icon-line-md:menu-fold-left v-else w-4.5 h-4.5 />
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
</NTooltip>
|
||||||
|
|
||||||
|
<NTooltip :disabled="appStore.isMobile" placement="bottom-end">
|
||||||
|
{{ themeLabels[appStore.themeMode] }}
|
||||||
|
<template #trigger>
|
||||||
|
<NButton quaternary @click="appStore.cycleTheme">
|
||||||
|
<icon-line-md:sunny-filled-loop-to-moon-filled-loop-transition
|
||||||
|
v-if="appStore.themeMode === 'light'"
|
||||||
|
w-4.5
|
||||||
|
h-4.5
|
||||||
|
/>
|
||||||
|
<icon-line-md:moon-filled-to-sunny-filled-loop-transition
|
||||||
|
v-else-if="appStore.themeMode === 'dark'"
|
||||||
|
w-4.5
|
||||||
|
h-4.5
|
||||||
|
/>
|
||||||
|
<icon-line-md:computer v-else w-4.5 h-4.5 />
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
</NTooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
44
src/layouts/base-layout/base-layout.vue
Normal file
44
src/layouts/base-layout/base-layout.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { AdminLayout } from '@sa/materials';
|
||||||
|
import BaseLayoutHeader from './base-layout-header.vue';
|
||||||
|
|
||||||
|
const siderCollapse = ref(false);
|
||||||
|
const appStore = useAppStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AdminLayout :is-mobile="appStore.isMobile" v-model:sider-collapse="siderCollapse">
|
||||||
|
<template #header>
|
||||||
|
<BaseLayoutHeader v-model:collapsed="siderCollapse" />
|
||||||
|
</template>
|
||||||
|
<template #tab>
|
||||||
|
<div class="bg-green-100 dark:bg-green-900 text-green-900 dark:text-green-100 p-4">
|
||||||
|
2#GlobalTab
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #sider>
|
||||||
|
<div
|
||||||
|
class="bg-purple-100 dark:bg-purple-900 text-purple-900 dark:text-purple-100 p-4 h-full overflow-hidden"
|
||||||
|
>
|
||||||
|
3#GlobalSider
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="bg-yellow-100 dark:bg-yellow-900 text-yellow-900 dark:text-yellow-100 p-4">
|
||||||
|
4#GlobalMenu
|
||||||
|
</div>
|
||||||
|
<!-- <div>GlobalContent</div> -->
|
||||||
|
<RouterView />
|
||||||
|
<!-- <div>ThemeDrawer</div> -->
|
||||||
|
<template #footer>
|
||||||
|
<div class="bg-red-100 dark:bg-red-900 text-red-900 dark:text-red-100 h-full">
|
||||||
|
5#GlobalFooter
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</AdminLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#__SCROLL_EL_ID__ {
|
||||||
|
@include scrollbar;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
9
src/layouts/naive-ui/AppLayout.vue
Normal file
9
src/layouts/naive-ui/AppLayout.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="app-layout min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||||
|
<main class="max-w-7xl mx-auto px-4 py-8">
|
||||||
|
<router-view />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
12
src/main.ts
Normal file
12
src/main.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import './styles/index.ts';
|
||||||
|
|
||||||
|
// import { LogLevels } from 'consola';
|
||||||
|
// consola.level = LogLevels.verbose;
|
||||||
|
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
const autoInstallModules = import.meta.glob('./plugins/!(index).ts', { eager: true });
|
||||||
|
|
||||||
|
import { setupPlugins } from './plugins';
|
||||||
|
|
||||||
|
setupPlugins(createApp(App), autoInstallModules).mount('#app');
|
||||||
16
src/pages/[...path].page.vue
Normal file
16
src/pages/[...path].page.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
defineProps<{ path: string }>();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<main flex-1 class="flex flex-col items-center justify-center h-full space-y-4">
|
||||||
|
<h1>Not Found</h1>
|
||||||
|
<p>{{ path }} does not exist.</p>
|
||||||
|
<Button @click="$router.back()">Back</Button>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<route lang="yaml">
|
||||||
|
props: true
|
||||||
|
meta:
|
||||||
|
layout: false
|
||||||
|
</route>
|
||||||
40
src/pages/index.page.vue
Normal file
40
src/pages/index.page.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const apiResult = ref<string>('');
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const callApi = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/');
|
||||||
|
const data = await response.json();
|
||||||
|
apiResult.value = JSON.stringify(data, null, 2);
|
||||||
|
} catch (error) {
|
||||||
|
apiResult.value = `Error: ${error}`;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-3">
|
||||||
|
<div class="bg-white rounded-lg shadow-md p-4 max-w-xs w-full">
|
||||||
|
<h1 class="text-xl font-bold text-gray-800 mb-3 text-center">API 示例</h1>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="callApi"
|
||||||
|
:disabled="loading"
|
||||||
|
class="w-full bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold py-1.5 px-3 rounded-md hover:from-blue-600 hover:to-purple-700 transition-all duration-200 disabled:opacity-50 shadow-sm text-sm"
|
||||||
|
>
|
||||||
|
{{ loading ? '调用中...' : '调用 API' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div v-if="apiResult" class="mt-3 bg-gray-50 rounded-md p-2">
|
||||||
|
<h3 class="text-gray-700 font-semibold mb-1.5 text-xs">响应结果:</h3>
|
||||||
|
<pre class="text-gray-600 text-xs overflow-x-auto">{{ apiResult }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
32
src/plugins/_.ts
Normal file
32
src/plugins/_.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { autoAnimatePlugin } from '@formkit/auto-animate/vue';
|
||||||
|
import { createHead } from '@unhead/vue/client';
|
||||||
|
|
||||||
|
export function install({ app }: { app: import('vue').App<Element> }) {
|
||||||
|
app.config.globalProperties.__DEV__ = __DEV__;
|
||||||
|
|
||||||
|
app.use(autoAnimatePlugin); // v-auto-animate="{ duration: 100 }"
|
||||||
|
|
||||||
|
app.use(createHead());
|
||||||
|
app.config.errorHandler = (error, instance, info) => {
|
||||||
|
console.error('Global error:', error);
|
||||||
|
console.error('Component:', instance);
|
||||||
|
console.error('Error Info:', info);
|
||||||
|
// 这里你可以:
|
||||||
|
// 1. 发送错误到日志服务
|
||||||
|
// 2. 显示全局错误提示
|
||||||
|
// 3. 进行错误分析和处理
|
||||||
|
};
|
||||||
|
|
||||||
|
// if (import.meta.env.MODE === 'development' && '1' === ('2' as never)) {
|
||||||
|
// // TODO: https://github.com/hu3dao/vite-plugin-debug/
|
||||||
|
// // https://eruda.liriliri.io/zh/docs/#快速上手
|
||||||
|
// import('eruda').then(({ default: eruda }) => {
|
||||||
|
// eruda.init({
|
||||||
|
// defaults: {
|
||||||
|
// transparency: 0.9,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// /* eruda.show(); */
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
}
|
||||||
24
src/plugins/index.ts
Normal file
24
src/plugins/index.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* https://github.com/antfu-collective/vitesse/blob/47618e72dfba76c77b9b85b94784d739e35c492b/src/modules/README.md
|
||||||
|
*/
|
||||||
|
type UserPlugin = (ctx: UserPluginContext) => void;
|
||||||
|
type AutoInstallModule = { [K: string]: unknown; install?: UserPlugin };
|
||||||
|
type UserPluginContext = { app: import('vue').App<Element> };
|
||||||
|
export function setupPlugins(
|
||||||
|
app: import('vue').App,
|
||||||
|
modules: AutoInstallModule | Record<string, unknown>,
|
||||||
|
) {
|
||||||
|
console.group('🔌 Plugins');
|
||||||
|
for (const path in modules) {
|
||||||
|
const module = modules[path] as AutoInstallModule;
|
||||||
|
if (module.install) {
|
||||||
|
module.install({ app });
|
||||||
|
console.debug(`%c✔ ${path}`, 'color: #07a');
|
||||||
|
} else {
|
||||||
|
if (typeof module.setupPlugins === 'function') continue;
|
||||||
|
console.warn(`%c✘ ${path} has no install function`, 'color: #f50');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.groupEnd();
|
||||||
|
return app;
|
||||||
|
}
|
||||||
6
src/plugins/pinia-plugin.ts
Normal file
6
src/plugins/pinia-plugin.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { PiniaColada } from '@pinia/colada';
|
||||||
|
// import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||||
|
export function install({ app }: { app: import('vue').App<Element> }) {
|
||||||
|
app.use(createPinia() /* .use(piniaPluginPersistedstate) */);
|
||||||
|
app.use(PiniaColada, {});
|
||||||
|
}
|
||||||
28
src/plugins/primevue-plugin.ts
Normal file
28
src/plugins/primevue-plugin.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 需要把 <DynamicDialog /> <ConfirmDialog /> <Toast /> 放在 App.vue 的 template 中
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Aura from '@primeuix/themes/aura';
|
||||||
|
import zhCN from 'primelocale/zh-CN.json';
|
||||||
|
import PrimeVue from 'primevue/config';
|
||||||
|
import StyleClass from 'primevue/styleclass';
|
||||||
|
|
||||||
|
export function install({ app }: { app: import('vue').App<Element> }) {
|
||||||
|
app.directive('styleclass', StyleClass);
|
||||||
|
app.use(PrimeVue, {
|
||||||
|
locale: {
|
||||||
|
...zhCN['zh-CN'],
|
||||||
|
completed: '已上传',
|
||||||
|
noFileChosenMessage: '未选择文件',
|
||||||
|
pending: '待上传',
|
||||||
|
}, // usePrimeVue().config.locale
|
||||||
|
theme: {
|
||||||
|
options: {
|
||||||
|
cssLayer: false,
|
||||||
|
darkModeSelector: '.app-dark' /* 'system' */,
|
||||||
|
prefix: 'p',
|
||||||
|
},
|
||||||
|
preset: Aura,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
58
src/plugins/router-plugin.ts
Normal file
58
src/plugins/router-plugin.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders';
|
||||||
|
import { setupLayouts } from 'virtual:meta-layouts';
|
||||||
|
// import { createGetRoutes, setupLayouts } from 'virtual:generated-layouts';
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
import { handleHotUpdate, routes } from 'vue-router/auto-routes';
|
||||||
|
|
||||||
|
const setupLayoutsResult = setupLayouts(routes);
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: /* routes ?? */ setupLayoutsResult,
|
||||||
|
scrollBehavior: (_to, _from, savedPosition) => {
|
||||||
|
return savedPosition ?? { left: 0, top: 0 };
|
||||||
|
},
|
||||||
|
strict: true,
|
||||||
|
});
|
||||||
|
if (import.meta.hot) handleHotUpdate(router);
|
||||||
|
if (__DEV__) Object.assign(globalThis, { router });
|
||||||
|
router.onError((error) => {
|
||||||
|
console.debug('🚨 [router error]:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { router, setupLayoutsResult };
|
||||||
|
export function install({ app }: { app: import('vue').App<Element> }) {
|
||||||
|
app
|
||||||
|
// 在路由之前注册插件
|
||||||
|
.use(DataLoaderPlugin, { router })
|
||||||
|
// 添加路由会触发初始导航
|
||||||
|
.use(router);
|
||||||
|
}
|
||||||
|
// ========================================================================
|
||||||
|
// =========================== Router Guards ==============================
|
||||||
|
// ========================================================================
|
||||||
|
{
|
||||||
|
// 警告:路由守卫的创建顺序会影响执行流程,请勿调整
|
||||||
|
createNProgressGuard(router);
|
||||||
|
createLogGuard(router);
|
||||||
|
Object.assign(globalThis, { stack: createStackGuard(router) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
definePage({
|
||||||
|
meta: { },
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
declare module 'vue-router' {
|
||||||
|
interface RouteMeta {
|
||||||
|
/**
|
||||||
|
* @description 是否在菜单中隐藏
|
||||||
|
*/
|
||||||
|
hidden?: boolean;
|
||||||
|
/**
|
||||||
|
* @description 菜单标题
|
||||||
|
*/
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createGetRoutes } from 'virtual:meta-layouts';
|
||||||
17
src/plugins/vueI18n-plugin.ts
Normal file
17
src/plugins/vueI18n-plugin.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n#static-bundle-importing
|
||||||
|
* All i18n resources specified in the plugin `include` option can be loaded
|
||||||
|
* at once using the import syntax
|
||||||
|
*/
|
||||||
|
import messages from '@intlify/unplugin-vue-i18n/messages';
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
export function install({ app }: { app: import('vue').App<Element> }) {
|
||||||
|
app.use(
|
||||||
|
// https://vue-i18n.intlify.dev/guide/essentials/started.html#registering-the-i18n-plugin
|
||||||
|
createI18n({
|
||||||
|
legacy: false, // you must set `false`, to use Composition API
|
||||||
|
locale: navigator.language,
|
||||||
|
messages,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
61
src/stores/app-store.ts
Normal file
61
src/stores/app-store.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { useMediaQuery, usePreferredColorScheme } from '@vueuse/core';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { computed, watch } from 'vue';
|
||||||
|
|
||||||
|
export const APP_THEME_MODES = ['light', 'dark', 'system'] as const;
|
||||||
|
export type AppThemeMode = (typeof APP_THEME_MODES)[number];
|
||||||
|
|
||||||
|
const DARK_CLASS = 'app-dark';
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('app', () => {
|
||||||
|
const themeMode = useLocalStorage<AppThemeMode>('app-theme-mode', 'system');
|
||||||
|
const preferredColor = usePreferredColorScheme();
|
||||||
|
|
||||||
|
// 计算实际使用的主题
|
||||||
|
const actualTheme = computed(() =>
|
||||||
|
themeMode.value === 'system'
|
||||||
|
? preferredColor.value === 'dark'
|
||||||
|
? 'dark'
|
||||||
|
: 'light'
|
||||||
|
: themeMode.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 是否是暗色主题
|
||||||
|
const isDark = computed(() => actualTheme.value === 'dark');
|
||||||
|
|
||||||
|
// 是否是移动端
|
||||||
|
const isMobile = useMediaQuery('(max-width: 768px)');
|
||||||
|
|
||||||
|
// 更新 DOM 类名
|
||||||
|
function updateDomClass() {
|
||||||
|
document.documentElement.classList.toggle(DARK_CLASS, isDark.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置主题
|
||||||
|
function setTheme(mode: AppThemeMode) {
|
||||||
|
themeMode.value = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环切换主题
|
||||||
|
function cycleTheme() {
|
||||||
|
const currentIndex = APP_THEME_MODES.indexOf(themeMode.value);
|
||||||
|
const nextIndex = (currentIndex + 1) % APP_THEME_MODES.length;
|
||||||
|
setTheme(APP_THEME_MODES[nextIndex]!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听主题变化,更新 DOM
|
||||||
|
watch(isDark, updateDomClass, { immediate: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
themeMode,
|
||||||
|
actualTheme,
|
||||||
|
isDark,
|
||||||
|
isMobile,
|
||||||
|
setTheme,
|
||||||
|
cycleTheme,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useAppStore, import.meta.hot));
|
||||||
|
}
|
||||||
4
src/styles/index.ts
Normal file
4
src/styles/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import 'nprogress/nprogress.css'; // <link rel="stylesheet" href="https://testingcf.jsdelivr.net/npm/nprogress/nprogress.css" />
|
||||||
|
|
||||||
|
//
|
||||||
|
import 'virtual:uno.css';
|
||||||
1
src/styles/scss/global.scss
Normal file
1
src/styles/scss/global.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@forward 'scrollbar';
|
||||||
24
src/styles/scss/scrollbar.scss
Normal file
24
src/styles/scss/scrollbar.scss
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
@mixin scrollbar($size: 7px, $color: rgba(0, 0, 0, 0.5)) {
|
||||||
|
scrollbar-color: $color transparent;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: $color;
|
||||||
|
border-radius: $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: $color;
|
||||||
|
border-radius: $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track-piece {
|
||||||
|
background-color: rgb(0 0 0 / 0%);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/types/global.ts
Normal file
9
src/types/global.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
declare global {
|
||||||
|
const __DEV__: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface ComponentCustomProperties {
|
||||||
|
__DEV__: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
stylelint.config.ts
Normal file
35
stylelint.config.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { Config } from 'stylelint';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: [
|
||||||
|
'stylelint-config-standard',
|
||||||
|
'stylelint-config-recess-order',
|
||||||
|
'stylelint-config-standard-scss',
|
||||||
|
'stylelint-config-standard-vue/scss',
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['**/*.scss'],
|
||||||
|
|
||||||
|
customSyntax: 'postcss-scss',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.less'],
|
||||||
|
customSyntax: 'postcss-less',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.vue'],
|
||||||
|
customSyntax: 'postcss-html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
// 允许非 kebab-case 的 ID(Vue 使用 __ID__ 约定)
|
||||||
|
'selector-id-pattern': null,
|
||||||
|
// >>>>>
|
||||||
|
// 禁用默认的 at-rule-no-unknown,使用 SCSS 专用的规则
|
||||||
|
// 'at-rule-no-unknown': null,
|
||||||
|
// SCSS 专用的 at-rule 规则会自动处理 @include, @mixin 等
|
||||||
|
// 'scss/at-rule-no-unknown': true,
|
||||||
|
// <<<<<
|
||||||
|
},
|
||||||
|
} satisfies Config;
|
||||||
19
tsconfig.app.json
Normal file
19
tsconfig.app.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": [
|
||||||
|
"env.d.ts",
|
||||||
|
"src/**/*",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"./auto-imports.d.ts",
|
||||||
|
"./typed-router.d.ts",
|
||||||
|
"./components.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vitest.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.worker.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
tsconfig.node.json
Normal file
21
tsconfig.node.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*",
|
||||||
|
"eslint.config.*",
|
||||||
|
"stylelint.config.*",
|
||||||
|
"fake/**/*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
11
tsconfig.vitest.json
Normal file
11
tsconfig.vitest.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"include": ["src/**/__tests__/*", "env.d.ts"],
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
|
||||||
|
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"]
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tsconfig.worker.json
Normal file
8
tsconfig.worker.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.node.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo",
|
||||||
|
"types": [ "./worker-configuration.d.ts","vite/client"],
|
||||||
|
},
|
||||||
|
"include": ["server"],
|
||||||
|
}
|
||||||
56
typed-router.d.ts
vendored
Normal file
56
typed-router.d.ts
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
|
||||||
|
// It's recommended to commit this file.
|
||||||
|
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||||
|
|
||||||
|
declare module 'vue-router/auto-routes' {
|
||||||
|
import type {
|
||||||
|
RouteRecordInfo,
|
||||||
|
ParamValue,
|
||||||
|
ParamValueOneOrMore,
|
||||||
|
ParamValueZeroOrMore,
|
||||||
|
ParamValueZeroOrOne,
|
||||||
|
} from 'vue-router'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route name map generated by unplugin-vue-router
|
||||||
|
*/
|
||||||
|
export interface RouteNamedMap {
|
||||||
|
'Root': RouteRecordInfo<'Root', '/', Record<never, never>, Record<never, never>>,
|
||||||
|
'$Path': RouteRecordInfo<'$Path', '/:path(.*)', { path: ParamValue<true> }, { path: ParamValue<false> }>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
43
unocss.config.ts
Normal file
43
unocss.config.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// 请确保在 `main.ts` 文件中添加以下导入语句:import 'virtual:uno.css';
|
||||||
|
|
||||||
|
// https://github.dev/unocss/unocss/tree/main/examples/vite-vue3
|
||||||
|
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetAttributify,
|
||||||
|
presetWind4,
|
||||||
|
transformerDirectives,
|
||||||
|
transformerVariantGroup,
|
||||||
|
} from 'unocss';
|
||||||
|
import { presetAnimations } from 'unocss-preset-animations';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind4({ dark: { dark: '.app-dark' } }),
|
||||||
|
|
||||||
|
// https://unocss-preset-animations.aelita.me
|
||||||
|
presetAnimations(),
|
||||||
|
|
||||||
|
// https://unocss.dev/presets/attributify
|
||||||
|
presetAttributify(),
|
||||||
|
],
|
||||||
|
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
'logo-transform': 'i-icon:pacman w-6em h-6em transform transition-800',
|
||||||
|
pacman: 'i-icon:pacman text-(pink 36)',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
transformers: [
|
||||||
|
//https://unocss.dev/transformers/variant-group
|
||||||
|
transformerVariantGroup(),
|
||||||
|
|
||||||
|
// https://unocss.dev/transformers/directives
|
||||||
|
transformerDirectives(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
示例:
|
||||||
|
- text-[var(--h-gray-1)]
|
||||||
|
*/
|
||||||
33
vite.config.optimizeDeps.ts
Normal file
33
vite.config.optimizeDeps.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { DepOptimizationOptions } from 'vite';
|
||||||
|
|
||||||
|
const primevuecomponents = await (async () => {
|
||||||
|
const { components } = await import('@primevue/metadata');
|
||||||
|
return components.map((c) => c.from).filter((c) => c !== undefined);
|
||||||
|
})();
|
||||||
|
export function optimizeDeps(): DepOptimizationOptions {
|
||||||
|
return {
|
||||||
|
include: [
|
||||||
|
...primevuecomponents,
|
||||||
|
'@primeuix/themes',
|
||||||
|
'@primeuix/themes/lara',
|
||||||
|
'class-variance-authority',
|
||||||
|
'clsx',
|
||||||
|
'tailwind-merge',
|
||||||
|
'reka-ui',
|
||||||
|
'axios',
|
||||||
|
'@ant-design/icons-vue',
|
||||||
|
'ant-design-vue/es',
|
||||||
|
'p5',
|
||||||
|
'@splinetool/runtime',
|
||||||
|
'satellite.js',
|
||||||
|
'ts-enum-util',
|
||||||
|
'unplugin-vue-router',
|
||||||
|
'unplugin-vue-router/runtime',
|
||||||
|
'unplugin-vue-router/data-loaders/basic',
|
||||||
|
'unplugin-vue-router/data-loaders/pinia-colada',
|
||||||
|
'eruda',
|
||||||
|
'simplebar-vue',
|
||||||
|
],
|
||||||
|
exclude: ['quill', 'chart.js/auto'],
|
||||||
|
};
|
||||||
|
}
|
||||||
26
vite.config.plugin.index-html-plugin.ts
Normal file
26
vite.config.plugin.index-html-plugin.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { minify as minifyHtml } from 'html-minifier-terser';
|
||||||
|
import { type PluginOption } from 'vite';
|
||||||
|
|
||||||
|
export function IndexHtmlPlugin(): PluginOption {
|
||||||
|
return {
|
||||||
|
name: 'index-html-plugin',
|
||||||
|
apply: 'build',
|
||||||
|
async transformIndexHtml(html) {
|
||||||
|
console.time('minifyHtml');
|
||||||
|
// 压缩 HTML
|
||||||
|
const minifiedHtml = await minifyHtml(html, {
|
||||||
|
collapseWhitespace: true,
|
||||||
|
removeComments: true,
|
||||||
|
removeRedundantAttributes: true,
|
||||||
|
removeScriptTypeAttributes: true,
|
||||||
|
removeStyleLinkTypeAttributes: true,
|
||||||
|
useShortDoctype: true,
|
||||||
|
minifyCSS: true,
|
||||||
|
minifyJS: true,
|
||||||
|
});
|
||||||
|
console.log();
|
||||||
|
console.timeEnd('minifyHtml');
|
||||||
|
return minifiedHtml;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
237
vite.config.plugins.ts
Normal file
237
vite.config.plugins.ts
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import { cloudflare } from '@cloudflare/vite-plugin';
|
||||||
|
import VueI18n from '@intlify/unplugin-vue-i18n/vite';
|
||||||
|
import { PrimeVueResolver } from '@primevue/auto-import-resolver';
|
||||||
|
import { VantResolver } from '@vant/auto-import-resolver';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
|
import path from 'node:path';
|
||||||
|
import UnoCSS from 'unocss/vite';
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
|
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||||
|
import IconsResolver from 'unplugin-icons/resolver';
|
||||||
|
import Icons from 'unplugin-icons/vite';
|
||||||
|
import {
|
||||||
|
AntDesignVueResolver,
|
||||||
|
NaiveUiResolver,
|
||||||
|
TDesignResolver,
|
||||||
|
} from 'unplugin-vue-components/resolvers';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import Markdown from 'unplugin-vue-markdown/vite';
|
||||||
|
import { getPascalCaseRouteName, VueRouterAutoImports } from 'unplugin-vue-router';
|
||||||
|
import vueRouter from 'unplugin-vue-router/vite';
|
||||||
|
import { createUtils4uAutoImports } from 'utils4u/auto-imports';
|
||||||
|
import { type PluginOption } from 'vite';
|
||||||
|
import { checker } from 'vite-plugin-checker';
|
||||||
|
import { vitePluginFakeServer } from 'vite-plugin-fake-server';
|
||||||
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
|
||||||
|
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||||
|
import MetaLayouts from 'vite-plugin-vue-meta-layouts';
|
||||||
|
import { ViteWebfontDownload } from 'vite-plugin-webfont-dl';
|
||||||
|
import VueMacros from 'vue-macros/vite';
|
||||||
|
import { IndexHtmlPlugin } from './vite.config.plugin.index-html-plugin';
|
||||||
|
|
||||||
|
export function Plugins({ mode }: { mode: string }): PluginOption[] {
|
||||||
|
const plugins: PluginOption[] = [
|
||||||
|
VueMacros({
|
||||||
|
plugins: {
|
||||||
|
vue: vue({ include: [/\.vue$/, /\.md$/] }),
|
||||||
|
vueJsx: vueJsx(),
|
||||||
|
|
||||||
|
// https://uvr.esm.is/guide/configuration.html
|
||||||
|
// https://github.com/posva/unplugin-vue-router
|
||||||
|
// ⚠️ Vue must be placed after VueRouter()
|
||||||
|
vueRouter: vueRouter({
|
||||||
|
exclude: ['**/__*', '**/__*/**/*'],
|
||||||
|
extensions: ['.page.vue', '.page.md'],
|
||||||
|
getRouteName: (routeNode) => getPascalCaseRouteName(routeNode),
|
||||||
|
logs: false,
|
||||||
|
routesFolder: 'src/pages',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts?tab=readme-ov-file#configuration
|
||||||
|
// Layouts({ defaultLayout: 'sakai-vue/AppLayout', pagesDirs: [] }),
|
||||||
|
|
||||||
|
// https://github.com/dishait/vite-plugin-vue-meta-layouts
|
||||||
|
MetaLayouts({
|
||||||
|
// defaultLayout: 'sakai-vue/AppLayout',
|
||||||
|
// defaultLayout: 'naive-ui/AppLayout',
|
||||||
|
defaultLayout: 'base-layout/base-layout',
|
||||||
|
skipTopLevelRouteLayout: true, // 打开修复 https://github.com/JohnCampionJr/vite-plugin-vue-layouts/issues/134,默认为 false 关闭
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
// https://github.com/unplugin/unplugin-vue-markdown
|
||||||
|
Markdown({
|
||||||
|
headEnabled: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
cloudflare(),
|
||||||
|
);
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
// https://github.com/antfu/unocss
|
||||||
|
// see uno.config.ts for config
|
||||||
|
UnoCSS(),
|
||||||
|
);
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
// https://github.com/antfu/unplugin-auto-import
|
||||||
|
AutoImport({
|
||||||
|
dirs: [
|
||||||
|
// 'src/composables',
|
||||||
|
// 'src/utils',
|
||||||
|
'src/stores',
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
'vue',
|
||||||
|
'vue-i18n',
|
||||||
|
'pinia',
|
||||||
|
'@vueuse/core',
|
||||||
|
VueRouterAutoImports,
|
||||||
|
createUtils4uAutoImports([]),
|
||||||
|
{
|
||||||
|
'consola/browser': ['consola'],
|
||||||
|
'vue-router/auto': ['useLink'],
|
||||||
|
'naive-ui': ['useModal', 'useDialog', 'useMessage', 'useNotification', 'useLoadingBar'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vueTemplate: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/antfu/unplugin-vue-components
|
||||||
|
Components({
|
||||||
|
// __开头的
|
||||||
|
excludeNames: [/^__/],
|
||||||
|
// allow auto load markdown components under `./src/components/`
|
||||||
|
extensions: ['vue', 'md'],
|
||||||
|
// allow auto import and register components used in markdown
|
||||||
|
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||||
|
resolvers: [
|
||||||
|
AntDesignVueResolver({
|
||||||
|
importStyle: false, // css in js
|
||||||
|
resolveIcons: true,
|
||||||
|
}),
|
||||||
|
IconsResolver({
|
||||||
|
customCollections: ['svg'],
|
||||||
|
prefix: 'icon' /* <icon-svg:demo /> or <icon-svg-demo /> */,
|
||||||
|
}), // https://github.com/unplugin/unplugin-icons?tab=readme-ov-file#auto-importing
|
||||||
|
TDesignResolver({ esm: true, library: 'mobile-vue' }),
|
||||||
|
VantResolver({ importStyle: true }),
|
||||||
|
PrimeVueResolver(/* { components: { prefix: 'P' } } */),
|
||||||
|
NaiveUiResolver(),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
Icons({
|
||||||
|
autoInstall: true,
|
||||||
|
customCollections: {
|
||||||
|
svg: FileSystemIconLoader('src/assets/icons/svgs', (svg) => {
|
||||||
|
return svg.replace(/^<svg /, '<svg fill="currentColor" ');
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
iconCustomizer(collection, icon, properties) {
|
||||||
|
properties.class = 'unplugin-icons';
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
|
||||||
|
VueI18n({
|
||||||
|
/* options */
|
||||||
|
// locale messages resource pre-compile option
|
||||||
|
include: [path.resolve(import.meta.dirname, './src/locales/**')],
|
||||||
|
|
||||||
|
// https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n#transformi18nblock
|
||||||
|
// transformI18nBlock(src) {
|
||||||
|
// console.debug(`src :>> `, src);
|
||||||
|
// console.debug(`typeof src :>> `, typeof src);
|
||||||
|
// return src as string;
|
||||||
|
// },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mode !== 'test') {
|
||||||
|
plugins.push(
|
||||||
|
// https://github.com/condorheroblog/vite-plugin-fake-server?tab=readme-ov-file#usage
|
||||||
|
vitePluginFakeServer({
|
||||||
|
basename: 'fake-api',
|
||||||
|
enableProd: !true,
|
||||||
|
include: 'fake',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
vueDevTools(),
|
||||||
|
// https://vite-plugin-checker.netlify.app/introduction/introduction.html
|
||||||
|
checker({
|
||||||
|
eslint: {
|
||||||
|
lintCommand: 'eslint "./src/**/*.{js,jsx,ts,tsx,vue}"',
|
||||||
|
useFlatConfig: true,
|
||||||
|
},
|
||||||
|
vueTsc: true,
|
||||||
|
overlay: {
|
||||||
|
initialIsOpen: false,
|
||||||
|
},
|
||||||
|
terminal: true,
|
||||||
|
enableBuild: true,
|
||||||
|
// XXX: pnpm add vls vti -D
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
plugins.push(
|
||||||
|
// https://github.com/FatehAK/vite-plugin-image-optimizer?tab=readme-ov-file#default-configuration
|
||||||
|
ViteImageOptimizer({
|
||||||
|
/* pass your config */
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查是否在VS Code终端中运行
|
||||||
|
if (process.env.TERM_PROGRAM === 'vscode' || process.env.VSCODE_PID) {
|
||||||
|
// plugins.push(
|
||||||
|
// // 构建后自动将dist目录打包成zip文件
|
||||||
|
// viteArchiverPlugin({
|
||||||
|
// addTimestamp: false, // 是否添加时间戳到输出文件名
|
||||||
|
// format: 'zip', // 输出的压缩文件格式
|
||||||
|
// outputDir: '', // 输出目录,默认为项目根目录
|
||||||
|
// outputFileName: 'dist', // 输出的zip文件名(不含扩展名)
|
||||||
|
// sourceDir: 'dist', // 要打包的源目录
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
const _unused = () => {
|
||||||
|
// plugins.push(
|
||||||
|
// // https://github.com/rsnakdmx/vite-plugin-purgecss-v5?tab=readme-ov-file#-usage
|
||||||
|
// pluginPurgeCss({
|
||||||
|
// variables: true,
|
||||||
|
// }),
|
||||||
|
// viteSingleFile(),
|
||||||
|
// viteStaticCopy({
|
||||||
|
// targets: [
|
||||||
|
// // globalThis.CESIUM_BASE_URL = 'https://digitalarsenal.io/';
|
||||||
|
// { dest: cesiumBaseUrl, src: `${cesiumSource}/ThirdParty` },
|
||||||
|
// { dest: cesiumBaseUrl, src: `${cesiumSource}/Workers` },
|
||||||
|
// { dest: cesiumBaseUrl, src: `${cesiumSource}/Assets` },
|
||||||
|
// { dest: cesiumBaseUrl, src: `${cesiumSource}/Widgets` },
|
||||||
|
// ],
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
plugins.push(
|
||||||
|
// https://github.com/feat-agency/vite-plugin-webfont-dl?tab=readme-ov-file#-usage-simple-config-method-b-
|
||||||
|
ViteWebfontDownload([
|
||||||
|
'https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap',
|
||||||
|
'https://fonts.googleapis.com/css2?family=Fira+Code&display=swap',
|
||||||
|
'https://fonts.googleapis.com/css?family=Montserrat:300,400,500,600,700,900',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
plugins.push(IndexHtmlPlugin());
|
||||||
|
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
101
vite.config.ts
Normal file
101
vite.config.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
|
import { createViteProxy } from 'utils4u/vite';
|
||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import { optimizeDeps } from './vite.config.optimizeDeps';
|
||||||
|
import { Plugins } from './vite.config.plugins';
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig(({ command, mode }) => {
|
||||||
|
const isBuild = command === 'build';
|
||||||
|
const env = loadEnv(mode, process.cwd());
|
||||||
|
|
||||||
|
return {
|
||||||
|
base: env.VITE_APP_BASE,
|
||||||
|
build: {
|
||||||
|
sourcemap: env.VITE_APP_BUILD_SOURCE_MAP === 'true',
|
||||||
|
rollupOptions: {
|
||||||
|
/* onwarn: (warning, warn) => {
|
||||||
|
if (warning.code === 'EMPTY_BUNDLE') return;
|
||||||
|
if (warning.code === 'EVAL' && warning.id?.includes('node_modules/eruda')) return;
|
||||||
|
if (warning.code === 'EVAL' && warning.id?.includes('node_modules/mockjs')) return;
|
||||||
|
if (warning.code === 'EVAL' && warning.id?.includes('node_modules/protobufjs')) return;
|
||||||
|
warn(warning);
|
||||||
|
}, */
|
||||||
|
|
||||||
|
output: {
|
||||||
|
// Keep hashed file names predictable across entry, chunk, and asset outputs.
|
||||||
|
entryFileNames: 'assets/[name].[hash].js',
|
||||||
|
chunkFileNames: 'assets/[name].[hash].js',
|
||||||
|
// https://cn.rollupjs.org/configuration-options/#output-assetfilenames
|
||||||
|
assetFileNames: (assetInfo) => {
|
||||||
|
if (assetInfo.names.length > 1) {
|
||||||
|
console.warn('Multiple names for asset:', assetInfo);
|
||||||
|
}
|
||||||
|
const assetName =
|
||||||
|
assetInfo.names.find(Boolean) ?? assetInfo.originalFileNames.find(Boolean) ?? '';
|
||||||
|
const ext = assetName.split('.').pop()?.toLowerCase();
|
||||||
|
if (ext && /png|jpe?g|gif|svg|webp|avif/.test(ext)) {
|
||||||
|
return 'assets/images/[name].[hash][extname]';
|
||||||
|
}
|
||||||
|
if (ext && /woff2?|ttf|otf/.test(ext)) {
|
||||||
|
return 'assets/fonts/[name].[hash][extname]';
|
||||||
|
}
|
||||||
|
if (ext === 'css') {
|
||||||
|
return 'assets/css/[name].[hash][extname]';
|
||||||
|
}
|
||||||
|
return 'assets/[name].[hash][extname]';
|
||||||
|
},
|
||||||
|
// // Split key dependency groups to improve long-term caching.
|
||||||
|
// manualChunks: (id) => {
|
||||||
|
// if (!id.includes('node_modules')) return;
|
||||||
|
// if (
|
||||||
|
// id.includes('node_modules/vue') ||
|
||||||
|
// id.includes('node_modules/@vue/') ||
|
||||||
|
// id.includes('node_modules/vue-router')
|
||||||
|
// ) {
|
||||||
|
// return 'vue-vendor';
|
||||||
|
// }
|
||||||
|
// if (id.includes('pinia') || id.includes('vue-i18n')) {
|
||||||
|
// return 'state-i18n';
|
||||||
|
// }
|
||||||
|
// if (id.includes('naive-ui')) {
|
||||||
|
// return 'naive-ui';
|
||||||
|
// }
|
||||||
|
// if (id.includes('primevue')) {
|
||||||
|
// return 'primevue';
|
||||||
|
// }
|
||||||
|
// if (id.includes('@vueuse')) {
|
||||||
|
// return 'vueuse';
|
||||||
|
// }
|
||||||
|
// return 'vendor';
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
// 使用 Sass 的现代编译器 API,提供更好的性能和新功能支持
|
||||||
|
api: 'modern-compiler',
|
||||||
|
additionalData: `@use "@/styles/scss/global.scss" as *;`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: Plugins({ mode }),
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
__DEV__: JSON.stringify(!isBuild),
|
||||||
|
// https://github.com/fi3ework/vite-plugin-checker/issues/569#issuecomment-3254311799
|
||||||
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
allowedHosts: ['.NWCT.DEV'],
|
||||||
|
proxy: createViteProxy(),
|
||||||
|
},
|
||||||
|
optimizeDeps: optimizeDeps(),
|
||||||
|
};
|
||||||
|
});
|
||||||
19
vitest.config.ts
Normal file
19
vitest.config.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config';
|
||||||
|
import viteConfig from './vite.config';
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig({
|
||||||
|
command: 'build',
|
||||||
|
mode: 'test',
|
||||||
|
}),
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
||||||
|
environment: 'happy-dom',
|
||||||
|
exclude: [...configDefaults.exclude, 'e2e/**'],
|
||||||
|
root: fileURLToPath(new URL('./', import.meta.url)),
|
||||||
|
teardownTimeout: 5000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
8374
worker-configuration.d.ts
vendored
Normal file
8374
worker-configuration.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
59
wrangler.jsonc
Normal file
59
wrangler.jsonc
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* For more details on how to configure Wrangler, refer to:
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "vue-ts-example-2025",
|
||||||
|
"compatibility_date": "2025-09-09",
|
||||||
|
"main": "server/index.ts",
|
||||||
|
"workers_dev": true,
|
||||||
|
"preview_urls": true,
|
||||||
|
"assets": {
|
||||||
|
"not_found_handling": "single-page-application",
|
||||||
|
},
|
||||||
|
"observability": {
|
||||||
|
"enabled": true,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Smart Placement
|
||||||
|
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||||
|
*/
|
||||||
|
// "placement": { "mode": "smart" }
|
||||||
|
/**
|
||||||
|
* Bindings
|
||||||
|
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
|
||||||
|
* databases, object storage, AI inference, real-time communication and more.
|
||||||
|
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Environment Variables
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||||
|
*/
|
||||||
|
// "vars": { "MY_VARIABLE": "production_value" }
|
||||||
|
/**
|
||||||
|
* Note: Use secrets to store sensitive data.
|
||||||
|
* https://developers.cloudflare.com/workers/configuration/secrets/
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Static Assets
|
||||||
|
* https://developers.cloudflare.com/workers/static-assets/binding/
|
||||||
|
*/
|
||||||
|
// "assets": { "directory": "./public/", "binding": "ASSETS" }
|
||||||
|
/**
|
||||||
|
* Service Bindings (communicate between multiple Workers)
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||||
|
*/
|
||||||
|
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KV Namespaces
|
||||||
|
* https://developers.cloudflare.com/kv/
|
||||||
|
*/
|
||||||
|
"kv_namespaces": [
|
||||||
|
{
|
||||||
|
"binding": "KV",
|
||||||
|
"id": "cf60206f0d994aa5ac7d4a4b853ced18",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user