425 lines
12 KiB
Markdown
425 lines
12 KiB
Markdown
|
||
# kintone-vue-ts-template
|
||
|
||
使用 Vue 、ts 和 Vite 创建 kintone plugin 的初始化模板,先由 [create-plugin](https://cybozu.dev/ja/kintone/sdk/development-environment/create-plugin/) 生成之后再手动引入 Vue。
|
||
|
||
> プラグイン開発手順:https://cybozu.dev/ja/kintone/tips/development/plugins/development-plugin/
|
||
|
||
包括了以下 kintone 库:
|
||
|
||
- [kintone-ui-component (v1.22.0)](https://cybozu.dev/ja/kintone/sdk/library/kintone-ui-component-v1/)([文档](https://ui-component.kintone.dev/ja/))
|
||
- [@cybozu/eslint-config](https://cybozu.dev/ja/kintone/sdk/development-environment/eslint-config/)
|
||
- [@kintone/rest-api-client](https://cybozu.dev/ja/kintone/sdk/rest-api-client/kintone-javascript-client/)([文档](https://github.com/kintone/js-sdk/tree/main/packages/rest-api-client))
|
||
- [kintone/plugin-packer](https://cybozu.dev/ja/kintone/sdk/development-environment/plugin-packer/)
|
||
- [@kintone/plugin-uploader](https://cybozu.dev/ja/kintone/sdk/development-environment/plugin-uploader/)
|
||
- [@kintone/dts-gen](https://cybozu.dev/ja/kintone/sdk/library/dts-gen/)
|
||
|
||
|
||
# 使用步骤
|
||
|
||
首先进行安装
|
||
|
||
```bash
|
||
npm install # 或 yarn
|
||
```
|
||
|
||
随后需要修改项目信息:
|
||
|
||
1. `package.json`
|
||
```diff
|
||
{
|
||
+ "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 "
|
||
},
|
||
...
|
||
```
|
||
|
||
2. `src/manifest.json`
|
||
|
||
```diff
|
||
{
|
||
...
|
||
+ "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/"
|
||
- },
|
||
...
|
||
}
|
||
```
|
||
|
||
|
||
# 编译流程
|
||
|
||
- **build**
|
||
- `build` 会生成 `dist` 文件夹,以及 `plugin.zip` 文件
|
||
|
||
> 如果只需要 `dist` 文件夹,可以执行 `vite:build`
|
||
|
||
```bash
|
||
npm run build # 或 yarn build
|
||
|
||
# 仅生成 `dist` 文件夹
|
||
npm run vite:build # 或 yarn vite:build
|
||
```
|
||
|
||
- **build:prod**
|
||
- `build:prod` 也是生成 `plugin.zip` 文件,但是会进行压缩和混淆,并且没有 sourceMap,在发布的时候使用
|
||
|
||
```bash
|
||
npm run build:prod # 或 yarn build:prod
|
||
```
|
||
|
||
- **upload**
|
||
- `upload` 会将 `plugin.zip` 上传到 Kintone plugin
|
||
|
||
```bash
|
||
npm run upload # 或 yarn upload
|
||
```
|
||
|
||
- **build-upload**
|
||
- `build-upload` 会顺序执行上面两步
|
||
|
||
```bash
|
||
npm run build-upload # 或 yarn build-upload
|
||
```
|
||
|
||
|
||
# 开发说明
|
||
|
||
当前模板中已经有一个案例,可以参考并且修改。
|
||
|
||
1. 对于 Config 页面,请修改 `components/Config.vue`
|
||
2. 对于 PC 的 App 页面,请修改 `js/desktop.ts`
|
||
3. 对于 Mobile 的 App 页面,请修改 `js/mobile.ts`
|
||
|
||
这三个文件是强制指定的,请不要修改。
|
||
|
||
|
||
## 项目结构
|
||
|
||
```shell
|
||
├── 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
|
||
├── 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` 库有一些限制](#对于-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` 中删除
|
||
```diff
|
||
"config": {
|
||
"html": "html/config.html",
|
||
"js": [
|
||
"js/config.js"
|
||
],
|
||
"css": [
|
||
- "css/51-modern-default.css",
|
||
"css/config.css"
|
||
],
|
||
"required_params": [
|
||
"buttonName"
|
||
]
|
||
},
|
||
```
|
||
|
||
|
||
## 关于 KintoneRestAPIClient
|
||
|
||
在 desktop/mobile,直接使用:
|
||
|
||
```ts
|
||
import client from '@/plugins/kintoneClient.ts'
|
||
|
||
client.app...
|
||
```
|
||
|
||
在 vue 中使用 useKintoneClient():
|
||
|
||
```vue
|
||
import { useKintoneClient } from '@/composables/useKintoneClient';
|
||
|
||
const client = useKintoneClient();// KintoneRestAPIClient
|
||
client.app...
|
||
```
|
||
|
||
|
||
|
||
## 关于 i18n
|
||
|
||
在 desktop/mobile:
|
||
|
||
```ts
|
||
import i18n from '@/i18n';
|
||
|
||
const { t } = i18n.global;
|
||
console.log(t('config.button.label'));
|
||
```
|
||
|
||
在 vue:
|
||
|
||
```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 下的路径,按需导入
|
||
|
||
```js
|
||
// 建议
|
||
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` 中已经自动处理了。
|
||
|
||
```ts
|
||
<kuc-text :value="modelValue" @change="updateValue" />
|
||
```
|
||
|
||
而对于在 `<script>` 中使用到的 ts type,和在 desktop/mobile 一样,请使用 `lib` 下的组件
|
||
|
||
```ts
|
||
import type { TextInputEventDetail } from 'kintone-ui-component/lib/text';
|
||
```
|
||
|
||
|
||
### v-model 语法糖无法使用
|
||
|
||
毕竟不是专门为 vue 开发的,所以默认不能使用 v-model 语法糖
|
||
|
||
需要自行创建 emit 事件(参考 `/src/components/basic/PluginInput.vue` )
|
||
|
||
```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` )
|
||
|
||
```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`
|
||
|
||
```ts
|
||
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` 文件……
|
||
|
||
```vue
|
||
<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>
|
||
``` |