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