kintone-vue-template
使用 Vue 、ts 和 Vite 创建 kintone plugin 的初始化模板,先由 create-plugin 生成之后再手动引入 Vue。
并且提供了 License 检查 的功能。
プラグイン開発手順:https://cybozu.dev/ja/kintone/tips/development/plugins/development-plugin/
包括了以下 kintone 库:
- kintone-ui-component (v1.22.0)(文档)
- @cybozu/eslint-config
- @kintone/rest-api-client(文档)
- kintone/plugin-packer
- @kintone/plugin-uploader
- @kintone/dts-gen
使用步骤
首先进行安装
npm install # 或 yarn
随后需要修改项目信息:
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 "
},
...
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/"
- },
...
}
- 如果需要使用 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
开发说明
当前模板中已经有一个案例,可以参考并且修改。
- 对于 Config 页面,请修改
components/Config.vue - 对于 PC 的 App 页面,请修改
js/desktop.ts - 对于 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 中自动 import,build 的时候生成
├── 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 开发没有区别,但是有以下一些限制:
- CSS 样式只能使用
css文件 kintone-ui-component库有一些限制
关于 desktop/mobile 的 app 页面开发
desktop/mobile 的 js 会被 vite 使用 lib 模式打包,从而将所有 import 合并到同一个文件里面。
关于 CSS 开发
暂时不支持在 Vue Template 中创建 css,只能在
css文件夹下新建 css 文件
/css 目录下分别对应了不同环境的 css 样式
config.css- config 页面引用的 cssdesktop.css- desktop app 页面引用的 cssmobile.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-*> 组件时:
- 不需要写 kuc 文档中所说的版本号
- 不需要在
<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>