Files
kintone-vue-template/README.md
2025-11-03 10:28:03 +08:00

15 KiB
Raw Permalink Blame History

kintone-vue-template

使用 Vue 、ts 和 Vite 创建 kintone plugin 的初始化模板,先由 create-plugin 生成之后再手动引入 Vue。

并且提供了 License 检查 的功能。

プラグイン開発手順:https://cybozu.dev/ja/kintone/tips/development/plugins/development-plugin/

包括了以下 kintone 库:

使用步骤

首先进行安装

npm install # 或 yarn

随后需要修改项目信息:

  1. package.json
{
+  "name": "项目名",
-  "name": "kintone-vue-template",
  "private": true,
+  "version": "版本号",
-  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "vite:build": "vite build",
    "build": "vite build && npm run pkg",
    "build-upload": "npm run build && npm run upload",
    "pkg": "kintone-plugin-packer --ppk private.ppk --out dist/plugin.zip dist/src",
+    "upload": "修改 kintone 域名、用户名和密码"
-    "upload": "kintone-plugin-uploader --base-url https://alicorn.cybozu.com --username maxz --password 7ld7i8vd dist/plugin.zip "
  },
  ...
  1. src/manifest.json
{
  ...
+  "icon": "icon 路径",
-  "icon": "image/icon.png",
  "config": {
    ...
+    "required_params": [ "/config 页面必填的参数" ]
-    "required_params": [ "buttonName" ]
  },
+  "name": "项目名",
-  "name": {
-    "en": "kintone Vue template",
-    "ja": "kintone Vue テンプレート"
-  },
+  "description": "项目描述",
-  "description": {
-    "en": "kintone Vue template for creating plugin",
-    "ja": "kintoneプラグイン作成用 Vue テンプレート"
-  },
+  "description": "项目网站",
-  "homepage_url": {
-    "en": "https://www.alicorns.co.jp/",
-    "ja": "https://www.alicorns.co.jp/"
-  },
  ...
}
  1. 如果需要使用 License 检查,请参考: License 检查

需要修改 src/config.json

{
  "license": {
    "enabled": true,                 // 是否开启 License 检查
    "api": {
      "checkUrl": "https://kintone.alicorns.co.jp/api/license/check",   // server 路径,一般不需要修改
+      "pluginKey": 插件的 id需要和后端数据库中保持一致
-      "pluginKey": "kintone-vue-template"
    },
    "purchase": {
+      "url": 购买页面打开的链接
-      "url": "https://alisurvey.alicorns.co.jp/s/Iuc5RxZv",
      "formIds": {
+        "name": 表单中存储申请人的名字的 field id, 会设置为 kintone 的登陆用户名,为空就跳过
-        "name": "input1761283314263",
+        "email": 表单中存储申请人 email 的 field id, 会设置为 kintone 的登陆邮箱,为空就跳过
-        "email": "input1761283275767",
+        "domain": 表单中存储用户 url domain 的 field id, 会设置为 url
-        "domain": "input1761283180784",
+        "pluginId": 表单中存储用户 plugin id 的 field id, 会设置为 plugin id
-        "pluginId": "input1761283200616"
      }
    },
    "warningDaysBeforeExpiry": 7    // 进行提醒的剩余天数
  }
}

编译流程

  • build
    • build 会生成 dist 文件夹,以及 plugin.zip 文件

如果只需要 dist 文件夹,可以执行 vite:build

npm run build # 或 yarn build

# 仅生成 `dist` 文件夹
npm run vite:build # 或 yarn vite:build
  • build:prod
    • build:prod 也是生成 plugin.zip 文件,但是会进行压缩和混淆,并且没有 sourceMap在发布的时候使用
npm run build:prod # 或 yarn build:prod
  • upload
    • upload 会将 plugin.zip 上传到 Kintone plugin
npm run upload # 或 yarn upload
  • build-upload
    • build-upload 会顺序执行上面两步
npm run build-upload # 或 yarn build-upload

开发说明

当前模板中已经有一个案例,可以参考并且修改。

  1. 对于 Config 页面,请修改 components/Config.vue
  2. 对于 PC 的 App 页面,请修改 js/desktop.ts
  3. 对于 Mobile 的 App 页面,请修改 js/mobile.ts

这三个文件是强制指定的,请不要修改。

项目结构

├── src
│   ├── components                # config 页面
│   │   ├── basic
│   │   │   ├── PluginInput.vue
│   │   │   └── PluginLabel.vue
│   │   └── Config.vue            # config 页面入口
│   ├── css
│   │   ├── 51-modern-default.css # config 页面额外引用的样式 css
│   │   ├── config.css            # config 页面引用的 css
│   │   ├── desktop.css           # desktop app 页面引用的 css
│   │   └── mobile.css            # mobile app 页面引用的 css
│   ├── i18n                      # i18n 语言包相关
│   │   ├── lang
│   │   │   ├── en.ts
│   │   │   └── ja.ts
│   │   └── index.ts
│   ├── js
│   │   ├── desktop.ts            # desktop app 页面入口
│   │   └── mobile.ts             # mobile app 页面入口
│   ├── types
│   │   ├── index.d.ts
│   │   ├── model.d.ts
│   │   └── my-kintone.d.ts       # kintone 相关的 ts 类型
│   ├── main.ts
│   └── manifest.json             # kintone plugin 需要的配置
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── components.d.ts          # vue 中自动 importbuild 的时候生成
├── env.d.ts                 # vue 类型定义
├── index.html
├── package.json
├── private.ppk              # 当前 plugin 密钥,首次 build 自动生成
├── README.md
├── rsa_private.pem          # license 验证密钥
├── rsa_public.pem           # license 公钥,需要被添加到密钥检查的后端系统中
├── tsconfig.json
└── vite.config.ts           # 主要的 vite 配置
└── vite.iife.config.ts      # 用于打包 desktop/mobile 文件的配置

关于 Config 页面开发

只有 Config 页面 的开发可以使用 Vue Template其他页面请使用 js 文件。

基本上和 Vue 开发没有区别,但是有以下一些限制:

  1. CSS 样式只能使用 css 文件
  2. kintone-ui-component 库有一些限制

关于 desktop/mobile 的 app 页面开发

desktop/mobile 的 js 会被 vite 使用 lib 模式打包,从而将所有 import 合并到同一个文件里面。

关于 CSS 开发

暂时不支持在 Vue Template 中创建 css只能在 css 文件夹下新建 css 文件

/css 目录下分别对应了不同环境的 css 样式

  • config.css - config 页面引用的 css
  • desktop.css - desktop app 页面引用的 css
  • mobile.css - mobile app 页面引用的 css

另外,在 config 页面引入了 51-modern-default.css,里面存放了 kintone 的一些样式

  • 如果不需要使用就在 manifest.json 中删除
    "config": {
       "html": "html/config.html",
       "js": [
          "js/config.js"
       ],
       "css": [
    -      "css/51-modern-default.css",
          "css/config.css"
       ],
       "required_params": [
          "buttonName"
       ]
    },
    

关于 KintoneRestAPIClient

在 desktop/mobile直接使用

import client from '@/plugins/kintoneClient.ts'

client.app...

在 vue 中使用 useKintoneClient()

import { useKintoneClient } from '@/composables/useKintoneClient';

const client = useKintoneClient();// KintoneRestAPIClient
client.app...

关于 i18n

在 desktop/mobile

import i18n from '@/i18n';

const { t } = i18n.global;
console.log(t('config.button.label'));

在 vue

<script setup lang="ts">
import { useI18n } from 'vue-i18n';

const { t } = useI18n();
console.log(t('config.button.label'));

对于 kintone-ui-component 库的限制

desktop/mobile 页面

在 desktop/mobile 的 app 页面中import 的时候建议使用 lib 下的路径,按需导入

// 建议
import { Button } from 'kintone-ui-component/lib/button';

// 下面的方式不太建议,都会引入所有的组件
import { Button } from 'kintone-ui-component';
import kuc from 'kintone-ui-component';

config 页面

由于 kuc 只提供了 umd 文件,所以对于 vue 页面,有比较多的限制

基本使用方式

<template> 中使用 <kuc-*> 组件时:

  1. 不需要写 kuc 文档中所说的版本号
  2. 不需要在 <script> 中手动 import

这些已经在 vite.config.ts 中已经自动处理了。

<kuc-text :value="modelValue" @change="updateValue" />

而对于在 <script> 中使用到的 ts type和在 desktop/mobile 一样,请使用 lib 下的组件

import type { TextInputEventDetail } from 'kintone-ui-component/lib/text';

v-model 语法糖无法使用

毕竟不是专门为 vue 开发的,所以默认不能使用 v-model 语法糖

需要自行创建 emit 事件(参考 /src/components/basic/PluginInput.vue

<template>
    <kuc-text :value="modelValue" @change="updateValue" />
</template>

<script setup lang="ts">
import type { KucEvent } from '@/types/my-kintone';
import type { TextInputEventDetail } from 'kintone-ui-component/lib/text';
import { defineProps, defineEmits } from 'vue';

// KUC Text 组件不支持 v-model 语法,因此需要进行包装以支持双向绑定
const props = defineProps<{
  modelValue: string;
}>();

const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void;
}>();


/**
 * 处理 v-model 事件
 */
const updateValue = ({ detail }: KucEvent<TextInputEventDetail>) => {
  emit('update:modelValue', detail.value || '');
};
</script>

封装组件的问题

当为了解决 v-model 的问题,可能会需要封装组件。我们会使用 v-bind 来避免手动来加 kuc 组件的所有属性。(参考 /src/components/basic/PluginInput.vue

<template>
  <kuc-text v-bind="textProps" />
</template>
<script setup lang="ts">
import type { TextProps } from 'kintone-ui-component/lib/text';
import { defineProps, computed } from 'vue';

const props = defineProps<TextProps & {
  modelValue: string; // 用于解决 v-model
}>();

/**
 * 生成传递给 KUC Text 组件的属性对象
 */
const textProps = computed<TextProps>(() => {
  return {
    ...props,
    className: "kuc-text-input " + ${props.className}, // 添加基础样式类
  };
</script>

但是有的 boolean 类型的属性,会被默认设置为 false,导致问题

  • 比如 visible: false 就看不到组件了

所以需要手动将这些 boolean 属性设置为 undefined

const props = withDefaults(defineProps<TextProps & {
  modelValue: string;
}>(), {
  // 布尔值的默认值处理,设置为 undefined 表示未定义,否则默认会是 false
  disabled: undefined,
  requiredIcon: undefined,
  visible: undefined,
});

const textProps = computed<TextProps>(() => {
  const baseProps = {
    ...props,
    className: "kuc-text-input " + ${props.className}, // 添加基础样式类
  };

  // 移除值为 undefined 的属性,确保只传递有效属性
  return Object.fromEntries(Object.entries(baseProps).filter(([_, value]) => value !== undefined)) as TextProps;
});

关于 spinner

使用 spinner 的时候,如果想要限制在某一个组件上(:container 属性),直接使用是不行的,它只会加在 document.body

不知道为啥,当它创建的时候不会用到 :container 参数,即使 HTMLNode 是可见的

所以可以使用 v-show 先让它隐藏

理论上来说spinner 使用 new kucs.spinner() 创建更符合习惯,但是我不太想引入 kuc.min.js 文件……

<template>
  <div id="main-area" ref="mainArea">
  </div>
   <!-- 必须使用 v-show  spinner dom 创建的时候不显示 -->
  <kuc-spinner :container="mainArea" v-show="loading" ref="spinner"></kuc-spinner>
</template>

<script setup lang="ts">
const loading = ref(false);

const mainArea = shallowRef<HTMLElement | null>(null);
const spinner = shallowRef<Spinner | null>(null);

onMounted(async () => {
  // 等待页面完全渲染后再显示加载状态,实现更平滑的用户体验
  nextTick(async () => {
    loading.value = true;
    // ...
    // 模拟加载时间,展示 spinner 效果
    await new Promise((resolve) => setTimeout(resolve, 1000));
    loading.value = false;
  });
});

// 监听加载状态变化,控制 spinner 显示/隐藏
watch(loading, (load) => {
  load ? spinner.value?.open() : spinner.value?.close();
});
</script>

关于 tooltip

kuc 没有实现插槽,所以应该当成一个普通组件使用:

<template>
    <button ref="buyButton">购买</button>
    <kuc-tooltip title="点击购买" :container="buyButton"></kuc-tooltip>
</template>

<script setup lang="ts">
const buyButton = shallowRef<HTMLButtonElement | null>(null);
</script>

License 检查

目前 License 的检查使用的是后端传一个 RSA 密钥加密的 JWT前端使用公钥验证是否被修改过并且保存在 Localstorage 中。

每隔一天都会从后端重新获取新的 License 信息。

默认试用是 30 天,还剩 7 天的时候会进行提醒。(在 license-service.ts 中修改)

当点击购买的时候,会跳转到一个外部网站,这里可以使用 AliSurvey 进行表单填写。

创建密钥

创建密钥可以使用 openssl 命令行工具:

openssl genrsa -out ./rsa_private.pem 2048

然后公钥可以放在同一个文件夹中:

openssl rsa -in ./rsa_private.pem -pubout -out ./rsa_public.pem

公钥需要被放到后端系统中

使用

在 desktop/mobile 中只要在所有的逻辑外部多包裹一层 LicenseService.loadPluginIfAuthorized()

kintone.events.on('app.record.index.show', async () => {
  LicenseService.loadPluginIfAuthorized(PLUGIN_ID,    // <--- 内部会进行 license 判断
    async () => {
      // 已有的逻辑代码
    },
  );
});

在 vue 中只要在顶部引入 LicenseStatus.vue

<template>
  <!-- 许可证状态信息 -->
  <LicenseStatus />

  <h2 class="settings-heading">{{ $t('config.title') }}</h2>