插件

插件

InsightSite 的插件分两类,都在「后台 → 插件」统一管理:

运行时插件代码包插件
形态一个 JSON 清单 nuxtcms.plugin.json一个 Nuxt Layer(npm 包 / Git 仓库)
能力设置项、注入代码、CSP 白名单、事件 Webhook、定时任务以上全部 + 页面、组件、API、数据库表
安装后台在线安装,无需重新构建,所有部署形态通用plugin.mjs add 后重新构建部署
典型统计代码、在线客服、群机器人通知、搜索推送会员、商城、表单设计器

官方插件中心

官网(https://insightme.top,NUXT_PLUGIN_HUB_URL 可改)就是插件中心,提供一条龙服务:

  1. 注册账号:官网 /account/register。
  2. 绑定站点:官网「账号 → API 令牌」创建令牌,粘贴到站点「后台 → 插件 → 账号与授权」。令牌只保存在服务端。
  3. 在线下载安装:插件市场里点「安装」,免费插件直接装好;有新版本时显示「更新到 vX」。
  4. 积分购买:付费插件标价为积分,点「N 积分购买」即扣积分并为当前域名签发授权。积分暂不支持在线支付,联系管理员充值。
  5. 联系授权:需要多站点授权、试用或报价,点「联系授权」,留言连同域名提交到官网后台,由管理员处理并发放授权码。
  6. 授权关联:授权绑定域名(NUXT_PUBLIC_SITE_URL 的 host)。也可以在插件设置里手动填写授权码。授权每天由 cron 复核一次;插件中心暂时连不上时保留上次结果,不会让正常运行的站点突然失效。

未授权的付费插件可以先安装和配置,但不会生效(不注入代码、不触发 Webhook)。

默认授权:插件中心管理员在「插件中心 → 概览 → 自有站点」登记的域名(例如自己公司的站点),安装付费插件时自动获得授权码,无需购买;已安装但没有授权码的付费插件会在校验或每日复核时自动领取。

插件中心不可达时(内网、离线),市场自动切换为框架内置的官方插件(plugins/official/*.json),免费插件照常安装。

使用量统计:站点每天最多向插件中心报告一次"正在运行"(后台检查更新、cron、upgrade.mjs 部署后),用于官网首页的"站点在用"数字。只发送 NUXT_PUBLIC_SITE_URL 的域名和框架版本号;插件中心只保存域名的 SHA-256 哈希,并在首次收到时访问该站的 /api/health 核实。本地地址(localhost)从不上报;设置 NUXT_HUB_TELEMETRY=false 即可关闭。

运行时插件清单

{
  "id": "my-plugin",
  "name": { "zh": "我的插件", "us": "My plugin" },
  "version": "1.0.0",
  "description": { "zh": "做什么用的", "us": "What it does" },
  "requires": ">=1.3.0",
  "category": "analytics",
  "icon": "i-lucide-bar-chart-3",
  "license": "free",
  "settings": [
    { "key": "siteId", "label": "站点 ID", "type": "text", "required": true }
  ],
  "inject": [
    { "position": "head", "when": "siteId", "html": "<script src=\"https://cdn.example.com/a.js?id={{settings.siteId}}\" async></script>" }
  ],
  "csp": { "script-src": ["https://cdn.example.com"] },
  "webhooks": [
    { "event": "contact.created", "url": "https://hooks.example.com/x", "body": { "text": "{{event.name}}:{{event.message}}" } }
  ],
  "tasks": [
    { "name": "每日同步", "cron": "0 3 * * *", "url": "{{site.url}}/api/cron/xxx", "headers": { "Authorization": "Bearer {{cronSecret}}" } }
  ]
}
字段说明
id小写字母、数字、短横线,全局唯一
name / description / label / help字符串或 { "zh": "...", "us": "..." }
versionsemver;市场据此提示更新
requires需要的最低 InsightSite 版本
license / priceCreditsfree 或 paid(付费插件只能从插件中心安装)
settings[].typetext textarea number boolean select secret url;secret 不回显给浏览器
inject[]position: head / body;scope: public(默认,只在前台)/ all;when: 某个设置为真时才注入;raw: true 表示设置值本身就是 HTML(不转义)
csp注入代码需要的外部域名。只有真正注入了代码的页面才会放开,没装插件的站点保持严格策略
webhooks[]event 见下表;url / headers / body 都可以用模板
tasks[]安装时写入「定时任务」,由 cron 调度器执行;插件停用时任务一起停用,卸载时删除

模板变量

{{settings.xxx}}、{{site.url}}、{{site.name}}、{{event.xxx}}(Webhook)、{{cronSecret}}(任务)。注入 HTML 时变量会做 HTML 转义,防止设置值破坏页面。

事件

事件时机载荷
contact.created前台提交联系表单name email phone message createdAt
form.submitted访客提交后台「表单」里建的表单form(标识)name id locale data(字段标识 → 答案)
content.published文章 / 页面 / 产品 / 内容类型条目发布(含定时发布到点)kind id title slug locale status url(条目另有 collection)
content.updated内容被修改同上
content.deleted内容被删除同上
plugin.installed插件安装或更新id version updated
order.created商城新订单number status total totalFormatted currency email name phone paymentMethod shippingMethod items url
order.paid付款确认(在线支付或后台标记已付款)同 order.created
order.status_changed订单状态变化同上 + previousStatus
comment.created博客新评论(非垃圾)id postId postTitle status name email content url
subscriber.created邮件订阅email name status locale source
saas.payment.paidSaaS 套件收到一笔付款(套餐购买、订阅续费)number plan period amount amountFormatted currency provider email name url
system.error服务器 5xx 错误(同一路由和错误 10 分钟内只发一次,每小时最多 20 条)status method path message at

Webhook 在响应之后执行(Cloudflare 上用 waitUntil),失败只记日志,不影响用户操作。

本地调试

「后台 → 插件 → 开发者」可粘贴或上传清单直接安装,修改后再次导入即覆盖(设置保留)。

代码包插件

一个普通的 Nuxt Layer,根目录放 nuxtcms.plugin.json("kind": "package"),在 Nitro 插件里注册:

// server/plugins/my-plugin.ts
import manifest from '../../nuxtcms.plugin.json'

export default defineNitroPlugin(() => {
  defineCmsPlugin(manifest, ({ on }) => {
    on('content.published', async (payload) => {
      // 插件停用或未授权时不会被调用
    })
  })
})

代码包插件给后台加菜单(app.config.ts):

export default defineAppConfig({
  nuxtcms: { adminNav: [{ label: '我的插件', icon: 'i-lucide-puzzle', to: '/admin/my-plugin', adminOnly: true, after: 'plugins' }] },
})

后台菜单分两级:主菜单(内容、产品、用户与会员、SEO 与营销、扩展功能、系统)下是子菜单。菜单项默认归入 after 指向的那一项所在的主菜单;用 group 指定主菜单(content、products、users、growth、extensions、system),或用 adminNavGroups 新增一个主菜单(官方商城插件即如此):

nuxtcms: {
  adminNavGroups: [{ key: 'crm', labelKey: 'crm.admin.menu', icon: 'i-lucide-contact', order: 35 }],
  adminNav: [{ labelKey: 'crm.admin.leads', icon: 'i-lucide-inbox', to: '/admin/crm/leads', group: 'crm' }],
}

站点不用的内置菜单可以用 adminNavHide 隐藏(填菜单键名,如 posts、media、products);外部页面(例如站点自己的旧后台)用 external: true 加入菜单,to 原样使用、不加语言前缀,target: '_blank' 在新标签打开;labels 按界面语言命名菜单项或主菜单:

nuxtcms: {
  adminNavHide: ['posts', 'pages', 'media', 'products'],
  adminNavGroups: [{ key: 'store', label: 'Store', labels: { cn: '商城后台', us: 'Store' }, icon: 'i-lucide-store', order: 5 }],
  adminNav: [{ to: '/admin/orders', labels: { cn: '订单', us: 'Orders' }, external: true, target: '_blank', group: 'store' }],
}

后台左上角的名称和 logo 用 adminBrand: { name: 'JIHE-TCM', logo: '/assets/logo.png' } 设置(默认 InsightSite)。后台配色可在站点 main.css 里改 cms-admin-sidebar、cms-admin-brand、cms-admin-header、cms-admin-main 以及菜单的 cms-nav-link / cms-nav-group(is-active)。

只有一个可见子项的主菜单直接显示为该菜单项。站点自定义了后台布局时,把菜单链接列表交给 <AdminSidebarNav :links="visibleLinks" /> 即可得到同样的两级菜单(可用 link-class、active-class、inactive-class 套用站点自己的样式)。

在 API 里判断是否可用、读取设置:

export default defineEventHandler(async (event) => {
  if (!(await isPluginActive(event, 'my-plugin'))) throw createError({ statusCode: 404 })
  const settings = await getPluginSettings(event, 'my-plugin')
  // ...
})

安装 / 卸载(在站点项目目录执行,然后重新构建部署):

node node_modules/nuxtcms/scripts/plugin.mjs list
node node_modules/nuxtcms/scripts/plugin.mjs add my-plugin --license NCMS-XXXX   # 从插件中心按 id 安装
node node_modules/nuxtcms/scripts/plugin.mjs add github:you/nuxtcms-plugin-x#v1.0.0
node node_modules/nuxtcms/scripts/plugin.mjs remove my-plugin

CLI 会用项目的包管理器安装依赖,并记录到 nuxtcms.plugins.json;基座的 nuxt.config.ts 自动 extends 其中的包。带 --license 时授权码随构建下发,首次访问后台或 cron 运行时自动绑定并校验。

官方 package 插件

shop(付费)、saas(付费)、ai(付费)、builder(免费)、marketing(免费)和 blog(免费)随框架一起发布(plugins/shop、plugins/saas、plugins/ai、plugins/builder、plugins/marketing、plugins/blog),添加时无需下载: node node_modules/nuxtcms/scripts/plugin.mjs add shop,重新构建并部署即可。详见 商城、SaaS 套件、AI 助手、区块页面搭建、营销工具 与 博客增强。

扩展点

package 插件和站点无需复制基座文件即可扩展:

扩展点用法
界面插槽基座页面里的 <CmsSlot name> 渲染 app.config 中 nuxtcms.slots 注册的组件,如 { 'product:aside': { ShopBuyBox: 10 } }(数字为顺序,false 为隐藏)。可用插槽:header:actions、app:end、post:meta、post:before-content、post:after-content、post:footer、product:aside、product:after-media、product:after-content、product-card:footer、editor:sidebar、account:main、account:aside(SaaS 用户中心,ctx.user)、account:nav(用户中心标签栏)、pricing:after(价格页底部)、page:blocks(页面模板中替代正文显示,ctx.page)。插槽组件须为全局组件(app/components/global/),ctx 以 props 传入(post、product,编辑器里为 type 与 contentId)。
积分收费的插件调用 cmsCredits(event, 'check' / 'spend', 数量, 原因),管理余额的插件(SaaS 套件)监听 Nitro 钩子 cms:credits 作答;没有提供者时返回 503 credits.unavailable。
区块中的插件组件在 app.config 的 nuxtcms.builderWidgets 中登记全局组件({ MyWidget: 'i18n 名称键' }),区块编辑器的「插件组件」区块即可选用;未登记的名称不会渲染。
编辑器表单editor:sidebar 中的组件 inject('cmsEditor') 得到 state(标题、正文、SEO 字段等),可直接填入建议。
编辑器保存钩子editor:sidebar 中的组件注入 cmsEditor 并调用 onSave(({ id, slug, type }) => ...),在文章/产品本身保存成功后执行。
公开数据服务端 onPublicContent((items, { event, kind, detail }) => ...) 在缓存前装饰 /api/public/products、/api/public/posts 与 /api/public/pages/:slug(kind 为 page)的响应。产品页会把 offers / sku 写入 JSON-LD,有 hideInquiry 时隐藏询盘卡片;文章页有 related 时优先使用。响应会被缓存,只能放目录数据。
自有数据表每种方言一份 Drizzle schema,配合 ensurePluginSchema(event, id, ddl):每个实例只执行一次幂等 DDL({prefix} 为表前缀)。官方插件用 node scripts/plugin-ddl.mjs <id> 生成 DDL。
事件新增 order.created、order.paid、order.status_changed、comment.created、subscriber.created、saas.payment.paid。

在框架仓库里开发插件:NUXT_PACKAGE_PLUGINS=./plugins/shop,./plugins/blog,./plugins/saas nuxt dev,nuxt dev 不校验授权。 插件页面位于 node_modules/nuxtcms/plugins,站点 main.css 需要 @source "../../../node_modules/nuxtcms/plugins";(upgrade.mjs 会自动补上)。

发布到官方插件中心

把清单(运行时插件)或包地址(代码包插件)提交给管理员,在官网后台「插件中心 → 插件」上架、定价(积分)。站点下次打开插件市场即可看到。