关于 capacitor-vue
为使用 Vue 3 和 Capacitor 的开发者提供按项目框架调整的开发指导,涵盖组合式 API、插件、生命周期和路由等 Vue 模式。
主要功能
- 01检测 Vue、Capacitor 版本及框架
- 02组合式函数封装 Capacitor 插件
- 03管理插件监听器生命周期
- 04集成 Vue Router 深度链接
- 05处理 Android 返回键与平台检测
适用场景
- 01将 Vue 项目接入 Capacitor
- 02为插件创建可复用的组合式函数
- 03排查监听器清理与平台配置问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ capawesome-team/ skills --skill capacitor-vue安装后在 Claude Code 中输入/capacitor-vue确认已加载。
附带文件references/nuxt.mdreferences/quasar.md
详细介绍
使用 Vue 的 Capacitor
Capacitor 应用开发的 Vue 专属模式和最佳实践——Composition API、组合式函数、响应性、生命周期钩子、Vue Router 集成,以及针对 Quasar 和 Nuxt 的框架专属指南。
前提条件
- 使用 Vue 3 的 Capacitor 6、7 或 8 应用。
- 已安装 Node.js 和 npm。
- iOS 开发:已安装 Xcode。
- Android 开发:已安装 Android Studio。
Agent 行为
- 先自动检测,再提问。 检查项目中的
vite.config.ts、vite.config.js、quasar.config.js、quasar.config.ts、nuxt.config.ts、package.json、capacitor.config.ts或capacitor.config.json,以及现有目录结构。仅在无法检测某项信息时询问用户。 - 逐步引导。 一次引导用户完成一个步骤。
- 检测元框架。 判断项目使用的是纯 Vue(Vite)、Quasar 还是 Nuxt,并相应调整说明。
操作步骤
步骤 1:分析项目
通过读取项目文件自动检测以下信息:
- Vue 版本:从
package.json读取vue版本。 - Capacitor 版本:从
package.json读取@capacitor/core版本。如果不存在,说明尚未添加 Capacitor——继续执行步骤 2。 - 元框架:按以下顺序检查文件来检测框架:
quasar.config.js或quasar.config.ts——Quasar 项目。转到references/quasar.md。nuxt.config.ts或nuxt.config.js——Nuxt 项目。转到references/nuxt.md。vite.config.ts或vite.config.js——纯 Vue(Vite) 项目。继续执行以下步骤。
- 平台:检查哪些目录存在(
android/、ios/)。 - Capacitor 配置格式:检查是否存在
capacitor.config.ts(TypeScript)或capacitor.config.json(JSON)。 - 构建输出目录:从
vite.config.ts或vite.config.js读取build.outDir。默认值为dist。
步骤 2:将 Capacitor 添加到 Vue 项目
如果 package.json 中已包含 @capacitor/core,则跳过。如果项目使用 Quasar,则跳过(Quasar 有自己的 Capacitor 集成——参见 references/quasar.md)。
-
安装 Capacitor 核心包和 CLI:
npm install @capacitor/core npm install -D @capacitor/cli -
初始化 Capacitor:
npx cap init出现提示时,将 web 目录设置为步骤 1 中检测到的 Vue 构建输出路径。对于基于 Vite 的 Vue 项目,该路径通常为
dist。 -
确认生成的
capacitor.config.ts或capacitor.config.json中的webDir值与 Vue 构建输出路径一致。如果不一致,请更新:capacitor.config.ts:import type { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { appId: 'com.example.app', appName: 'my-app', webDir: 'dist', }; export default config;capacitor.config.json:{ "appId": "com.example.app", "appName": "my-app", "webDir": "dist" } -
构建 Vue 应用并添加平台:
npm run build npm install @capacitor/android @capacitor/ios npx cap add android npx cap add ios npx cap sync
步骤 3:项目结构
基于 Vite 的 Capacitor Vue 项目具有以下结构:
my-app/
├── android/ # Android native project
├── ios/ # iOS native project
├── public/
├── src/
│ ├── assets/
│ ├── components/
│ ├── composables/ # Custom composables for Capacitor plugins
│ ├── router/
│ │ └── index.ts # Vue Router configuration
│ ├── views/
│ ├── App.vue
│ └── main.ts
├── capacitor.config.ts # or capacitor.config.json
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
要点:
android/和ios/目录包含原生项目,应提交到版本控制中。src/目录包含 Vue 应用,即 Capacitor 应用的 Web 层。- 将封装 Capacitor 插件的自定义组合式函数放在
src/composables/中。 - 在
src/中的 Vue 组件或组合式函数里调用 Capacitor 插件。
步骤 4:在 Vue 中使用 Capacitor 插件
Capacitor 插件是纯 TypeScript API。使用 Composition API 在 Vue 组件中直接导入并调用它们。
在组件中直接使用
<script setup lang="ts">
import { ref } from 'vue';
import { Geolocation } from '@capacitor/geolocation';
const latitude = ref<number | null>(null);
const longitude = ref<number | null>(null);
async function getCurrentPosition() {
const position = await Geolocation.getCurrentPosition();
latitude.value = position.coords.latitude;
longitude.value = position.coords.longitude;
}
</script>
<template>
<div>
<p>Latitude: {{ latitude }}</p>
<p>Longitude: {{ longitude }}</p>
<button @click="getCurrentPosition">Get Location</button>
</div>
</template>
将插件封装为组合式函数(推荐)
将 Capacitor 插件封装为组合式函数,可实现代码复用、封装响应式状态和自动清理:
// src/composables/useCamera.ts
import { ref } from 'vue';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import type { Photo } from '@capacitor/camera';
export function useCamera() {
const photo = ref<Photo | null>(null);
const error = ref<string | null>(null);
async function takePhoto(): Promise<void> {
try {
error.value = null;
photo.value = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
});
} catch (e) {
error.value = e instanceof Error ? e.message : String(e);
}
}
async function pickFromGallery(): Promise<void> {
try {
error.value = null;
photo.value = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Photos,
});
} catch (e) {
error.value = e instanceof Error ? e.message : String(e);
}
}
return {
photo,
error,
takePhoto,
pickFromGallery,
};
}
在组件中使用组合式函数:
<script setup lang="ts">
import { useCamera } from '@/composables/useCamera';
const { photo, error, takePhoto } = useCamera();
</script>
<template>
<div>
<button @click="takePhoto">Take Photo</button>
<p v-if="error">Error: {{ error }}</p>
<img v-if="photo?.webPath" :src="photo.webPath" alt="Captured photo" />
</div>
</template>
步骤 5:使用生命周期钩子管理插件事件监听器
必须在 onMounted 中注册 Capacitor 插件事件监听器,并在 onUnmounted 中移除,以防止内存泄漏。Vue 的响应式系统会自动检测 ref 的变化,因此不存在与 NgZone 等效的问题——但清理仍然至关重要。
自动清理的组合式函数
// src/composables/useNetwork.ts
import { ref, onMounted, onUnmounted } from 'vue';
import { Network } from '@capacitor/network';
import type { ConnectionStatus } from '@capacitor/network';
import type { PluginListenerHandle } from '@capacitor/core';
export function useNetwork() {
const status = ref<ConnectionStatus | null>(null);
let listenerHandle: PluginListenerHandle | null = null;
onMounted(async () => {
status.value = await Network.getStatus();
listenerHandle = await Network.addListener('networkStatusChange', (newStatus) => {
status.value = newStatus;
});
});
onUnmounted(async () => {
await listenerHandle?.remove();
});
return {
status,
};
}
在组件中使用:
<script setup lang="ts">
import { useNetwork } from '@/composables/useNetwork';
const { status } = useNetwork();
</script>
<template>
<p v-if="status">Network: {{ status.connected ? 'Online' : 'Offline' }}</p>
</template>
通过 App.vue 添加应用级监听器
对于应在整个应用生命周期内持续运行的监听器(例如应用状态变化),请在 App.vue 中注册:
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import { App } from '@capacitor/app';
import type { PluginListenerHandle } from '@capacitor/core';
import { RouterView } from 'vue-router';
let appStateListener: PluginListenerHandle | null = null;
onMounted(async () => {
appStateListener = await App.addListener('appStateChange', (state) => {
console.log('App state changed. Is active:', state.isActive);
});
});
onUnmounted(async () => {
await appStateListener?.remove();
});
</script>
<template>
<RouterView />
</template>
第 6 步:平台检测
使用 Capacitor.isNativePlatform() 和 Capacitor.getPlatform() 有条件地运行仅限原生平台的代码。将其封装为可复用的组合式函数:
// src/composables/usePlatform.ts
import { Capacitor } from '@capacitor/core';
export function usePlatform() {
const platform = Capacitor.getPlatform() as 'web' | 'ios' | 'android';
const isNative = Capacitor.isNativePlatform();
const isIos = platform === 'ios';
const isAndroid = platform === 'android';
const isWeb = platform === 'web';
return {
platform,
isNative,
isIos,
isAndroid,
isWeb,
};
}
在组件中使用它来显示或隐藏仅限原生平台的功能:
<script setup lang="ts">
import { usePlatform } from '@/composables/usePlatform';
const { isNative } = usePlatform();
</script>
<template>
<button v-if="isNative" @click="openNativeSettings()">Open Device Settings</button>
</template>
第 7 步:使用 Vue Router 处理深层链接路由
通过将 Capacitor 的 App.addListener('appUrlOpen', ...) 事件映射到 Vue Router 导航来处理深层链接。在 App.vue 或专用组合式函数中进行设置:
// src/composables/useDeepLinks.ts
import { onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { App } from '@capacitor/app';
import type { PluginListenerHandle } from '@capacitor/core';
export function useDeepLinks() {
const router = useRouter();
let listenerHandle: PluginListenerHandle | null = null;
onMounted(async () => {
listenerHandle = await App.addListener('appUrlOpen', (event) => {
const url = new URL(event.url);
const path = url.pathname;
// Navigate to the route matching the deep link path.
// Adjust the path parsing logic to match the app's URL scheme.
if (path) {
router.push(path);
}
});
});
onUnmounted(async () => {
await listenerHandle?.remove();
});
}
在 App.vue 中使用该组合式函数:
<script setup lang="ts">
import { useDeepLinks } from '@/composables/useDeepLinks';
useDeepLinks();
</script>
<template>
<RouterView />
</template>
第 8 步:处理返回按钮(Android)
结合 Vue Router,使用 App.addListener('backButton', ...) 处理 Android 硬件返回按钮:
// src/composables/useBackButton.ts
import { onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
import { App } from '@capacitor/app';
import { Capacitor } from '@capacitor/core';
import type { PluginListenerHandle } from '@capacitor/core';
export function useBackButton() {
const router = useRouter();
let listenerHandle: PluginListenerHandle | null = null;
onMounted(async () => {
if (Capacitor.getPlatform() !== 'android') {
return;
}
listenerHandle = await App.addListener('backButton', ({ canGoBack }) => {
if (canGoBack) {
router.back();
} else {
App.exitApp();
}
});
});
onUnmounted(async () => {
await listenerHandle?.remove();
});
}
在 App.vue 中使用该组合式函数:
<script setup lang="ts">
import { useBackButton } from '@/composables/useBackButton';
useBackButton();
</script>
<template>
<RouterView />
</template>
第 9 步:设置 PWA Elements
某些 Capacitor 插件(例如 Camera、Toast)需要 @ionic/pwa-elements 才能提供 Web 回退界面。如果项目使用了这些插件中的任意一个,且目标平台包含 Web:
-
安装 PWA Elements:
npm install @ionic/pwa-elements -
在
src/main.ts中、调用createApp()之前注册自定义元素:import { createApp } from 'vue'; import { defineCustomElements } from '@ionic/pwa-elements/loader'; import App from './App.vue'; import router from './router'; defineCustomElements(window); const app = createApp(App); app.use(router); app.mount('#app');
第 10 步:构建与同步工作流
修改 Vue 应用后,构建并同步到原生平台:
npm run build
npx cap sync
要在设备或模拟器上运行:
npx cap run android
npx cap run ios
要打开原生 IDE 进行高级配置或调试:
npx cap open android
npx cap open ios
要在开发期间启用实时重新加载:
npx cap run android --livereload --external
npx cap run ios --livereload --external
这会在内部启动 Vite 开发服务器,并配置原生应用从开发服务器加载。
错误处理
webDir不匹配:如果npx cap sync复制了错误的文件,请确认capacitor.config.ts或capacitor.config.json中的webDir与 Vue 的构建输出路径一致。对于基于 Vite 的 Vue 项目,默认输出目录为dist。- 运行时找不到插件:安装任何新插件后运行
npx cap sync。确认该插件已列在package.json依赖项中。 - 监听器导致内存泄漏:始终在
onUnmounted中移除插件监听器。保存addListener返回的PluginListenerHandle,并在组件卸载时调用handle.remove()。 - 深层链接无法使用:确认已在原生项目中配置应用 URL 方案或通用链接(Android 对应
android/app/src/main/AndroidManifest.xml,iOS 对应ios/App/App/Info.plist和关联域名权利文件)。确认已在App.vue中调用useDeepLinks()。 - 返回按钮意外关闭应用:确保返回按钮监听器在调用
App.exitApp()前检查canGoBack。仅在没有导航历史记录时退出。 - PWA Elements 未在 Web 上渲染:确认在
src/main.ts中、调用createApp()之前调用了defineCustomElements(window)。确认已安装@ionic/pwa-elements。 - Quasar 项目 — 未检测到 Capacitor:请使用
quasar mode add capacitor,而非手动安装 Capacitor。参见references/quasar.md。 - Nuxt 项目 — 构建输出不兼容:Nuxt 的默认 SSR 输出与 Capacitor 不兼容。项目必须在
nuxt.config.ts中使用ssr: false来生成静态 SPA。参见references/nuxt.md。
相关技能
capacitor-app-creation— 从零创建新的 Capacitor 应用。capacitor-app-development— 通用的 Capacitor 开发指南,不限于 Vue。capacitor-plugins— 从官方和社区来源安装并配置 Capacitor 插件。ionic-vue— 使用 Vue 的 Ionic Framework(基于 Capacitor 的 UI 组件、导航和主题)。capacitor-app-upgrades— 将 Capacitor 应用升级到较新的主版本。