Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2023ca601f | |||
| dec0e73dbb | |||
| c88affc53a | |||
| 41ba930473 | |||
| cc0e34a651 | |||
| 03a43789ac | |||
| 78a269b503 | |||
|
|
86669d3a28 | ||
|
|
b277fb23aa | ||
|
|
f33887df8a | ||
|
|
0aab989b32 | ||
|
|
ebfd49b130 | ||
|
|
f13781ebd1 | ||
|
|
4761f416f4 | ||
| 10d0c2ef74 | |||
|
|
a3595b1368 | ||
| c585013611 | |||
| 4b8d8506aa | |||
| 4a229e5e59 | |||
| 8d4ab48b89 | |||
| dfcb522951 | |||
|
|
6a0a28418f | ||
|
|
88a83878d2 | ||
| bf4a55dd26 | |||
|
|
defc671ad3 | ||
|
|
e927b9d5af | ||
|
|
18c6433b2f | ||
| 85f8bd9526 | |||
| 53da9bebb3 | |||
|
|
4f4482e20d | ||
| 0abea3628a | |||
| d57aac4613 | |||
| 62ec4b84fc | |||
|
|
54d384e7da | ||
|
|
e58a4c7293 | ||
| 662b18548f | |||
|
|
f3bb622fd5 | ||
| 7fb31b5c53 | |||
| ccbcbf5259 | |||
| ba897b00b9 | |||
| 46f2f5268c | |||
| 31b8f8a344 | |||
| eaa9ec2fea | |||
|
|
36b90ed45b | ||
|
|
529d656d1b | ||
|
|
acd3498832 |
15
data-fetch-pluging/.vscode/launch.json
vendored
15
data-fetch-pluging/.vscode/launch.json
vendored
@@ -1,15 +0,0 @@
|
||||
{
|
||||
// IntelliSense を使用して利用可能な属性を学べます。
|
||||
// 既存の属性の説明をホバーして表示します。
|
||||
// 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "localhost に対して Chrome を起動する",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
data-fetch-pluging/dist/plugin.zip
vendored
BIN
data-fetch-pluging/dist/plugin.zip
vendored
Binary file not shown.
BIN
data-fetch-pluging/dist/plugin/PUBKEY
vendored
BIN
data-fetch-pluging/dist/plugin/PUBKEY
vendored
Binary file not shown.
2
data-fetch-pluging/dist/plugin/SIGNATURE
vendored
2
data-fetch-pluging/dist/plugin/SIGNATURE
vendored
@@ -1,2 +0,0 @@
|
||||
n<EFBFBD>mR<10><>ӹE<D3B9><45><EFBFBD>H<EFBFBD>ޥu<DEA5><75><EFBFBD><02><><EFBFBD><EFBFBD>?u<>OL<4F>luG<75><47><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>$v
|
||||
/9R{t<><74>M<>v#>T<>D<EFBFBD>F<EFBFBD>0<EFBFBD><><7F>L<EFBFBD>kC<6B>i!<05>J<EFBFBD><10>o
|
||||
BIN
data-fetch-pluging/dist/plugin/contents.zip
vendored
BIN
data-fetch-pluging/dist/plugin/contents.zip
vendored
Binary file not shown.
@@ -1,7 +0,0 @@
|
||||
.settings-heading {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.kintoneplugin-input-text {
|
||||
width: 20em;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<section class="settings">
|
||||
<h2 class="settings-heading">Settings for data fetch pluging</h2>
|
||||
<p class="kintoneplugin-desc">This message is displayed on the app page after the app has been updated.</p>
|
||||
<form class="js-submit-settings">
|
||||
<p class="kintoneplugin-row">
|
||||
<label for="message">
|
||||
Message:
|
||||
<input type="text" class="js-text-message kintoneplugin-input-text">
|
||||
</label>
|
||||
</p>
|
||||
<p class="kintoneplugin-row">
|
||||
<button type="button" class="js-cancel-button kintoneplugin-button-dialog-cancel">Cancel</button>
|
||||
<button class="kintoneplugin-button-dialog-ok">Save</button>
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
@@ -1,25 +0,0 @@
|
||||
(function (PLUGIN_ID) {
|
||||
const formEl = document.querySelector('.js-submit-settings');
|
||||
const cancelButtonEl = document.querySelector('.js-cancel-button');
|
||||
const messageEl = document.querySelector('.js-text-message');
|
||||
if (!(formEl && cancelButtonEl && messageEl)) {
|
||||
throw new Error('Required elements do not exist.');
|
||||
}
|
||||
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
if (config.message) {
|
||||
messageEl.value = config.message;
|
||||
}
|
||||
|
||||
formEl.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
kintone.plugin.app.setConfig({ message: messageEl.value }, () => {
|
||||
alert('The plug-in settings have been saved. Please update the app!');
|
||||
window.location.href = '../../flow?app=' + kintone.app.getId();
|
||||
});
|
||||
});
|
||||
|
||||
cancelButtonEl.addEventListener('click', () => {
|
||||
window.location.href = '../../' + kintone.app.getId() + '/plugin/';
|
||||
});
|
||||
})(kintone.$PLUGIN_ID);
|
||||
@@ -1,23 +0,0 @@
|
||||
(function (PLUGIN_ID) {
|
||||
kintone.events.on('app.record.index.show', () => {
|
||||
alert('app.record.index.show run by pluging');
|
||||
const spaceEl = kintone.app.getHeaderSpaceElement();
|
||||
if (spaceEl === null) {
|
||||
throw new Error('The header element is unavailable on this page.');
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
const headingEl = document.createElement('h3');
|
||||
const messageEl = document.createElement('p');
|
||||
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
messageEl.textContent = config.message;
|
||||
messageEl.classList.add('plugin-space-message');
|
||||
headingEl.textContent = 'Hello kintone plugin!';
|
||||
headingEl.classList.add('plugin-space-heading');
|
||||
|
||||
fragment.appendChild(headingEl);
|
||||
fragment.appendChild(messageEl);
|
||||
spaceEl.appendChild(fragment);
|
||||
});
|
||||
})(kintone.$PLUGIN_ID);
|
||||
@@ -1,22 +0,0 @@
|
||||
(function (PLUGIN_ID) {
|
||||
kintone.events.on('mobile.app.record.index.show', () => {
|
||||
const spaceEl = kintone.mobile.app.getHeaderSpaceElement();
|
||||
if (spaceEl === null) {
|
||||
throw new Error('The header element is unavailable on this page.');
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
const headingEl = document.createElement('h3');
|
||||
const messageEl = document.createElement('p');
|
||||
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
messageEl.textContent = config.message;
|
||||
messageEl.classList.add('plugin-space-message');
|
||||
headingEl.textContent = 'Hello kintone plugin!';
|
||||
headingEl.classList.add('plugin-space-heading');
|
||||
|
||||
fragment.appendChild(headingEl);
|
||||
fragment.appendChild(messageEl);
|
||||
spaceEl.appendChild(fragment);
|
||||
});
|
||||
})(kintone.$PLUGIN_ID);
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/kintone/js-sdk/%40kintone/plugin-manifest-validator%4010.2.0/packages/plugin-manifest-validator/manifest-schema.json",
|
||||
"manifest_version": 1,
|
||||
"version": 2,
|
||||
"type": "APP",
|
||||
"desktop": {
|
||||
"js": [
|
||||
"js/desktop.js"
|
||||
],
|
||||
"css": [
|
||||
"css/51-modern-default.css",
|
||||
"css/desktop.css"
|
||||
]
|
||||
},
|
||||
"icon": "image/icon.png",
|
||||
"config": {
|
||||
"html": "html/config.html",
|
||||
"js": [
|
||||
"js/config.js"
|
||||
],
|
||||
"css": [
|
||||
"css/51-modern-default.css",
|
||||
"css/config.css"
|
||||
],
|
||||
"required_params": [
|
||||
"message"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"en": "data fetch pluging",
|
||||
"ja": "データ取得プラグイン"
|
||||
},
|
||||
"description": {
|
||||
"en": "create search data pluging",
|
||||
"ja": "検索結果のデータを生成するプラグインです"
|
||||
},
|
||||
"mobile": {
|
||||
"js": [
|
||||
"js/mobile.js"
|
||||
],
|
||||
"css": [
|
||||
"css/mobile.css"
|
||||
]
|
||||
}
|
||||
}
|
||||
4803
data-fetch-pluging/package-lock.json
generated
4803
data-fetch-pluging/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "data-fetch-pluging",
|
||||
"version": "0.1.1",
|
||||
"scripts": {
|
||||
"build": "kintone-plugin-packer --ppk private.ppk --out dist/plugin.zip src",
|
||||
"develop": "npm run build -- --watch",
|
||||
"lint": "eslint src",
|
||||
"start": "npm run develop",
|
||||
"test":"kintone-plugin-packer --help"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cybozu/eslint-config": "^23.0.0",
|
||||
"@kintone/plugin-packer": "^8.1.3",
|
||||
"eslint": "^8.57.0"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
"use strict";
|
||||
const runAll = require("npm-run-all");
|
||||
|
||||
runAll(["develop", "upload"], {
|
||||
parallel: true,
|
||||
stdout: process.stdout,
|
||||
stdin: process.stdin
|
||||
}).catch(({results}) => {
|
||||
results
|
||||
.filter(({code}) => code)
|
||||
.forEach(({name}) => {
|
||||
console.log(`"npm run ${name}" was failed`);
|
||||
})
|
||||
;
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
.settings-heading {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.kintoneplugin-input-text {
|
||||
width: 20em;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.plugin-space-heading {
|
||||
font-size: 1.5rem;
|
||||
margin: 0.8rem;
|
||||
}
|
||||
.plugin-space-message {
|
||||
display: inline-block;
|
||||
font-size: 1.2em;
|
||||
margin: 0.8rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<section class="settings">
|
||||
<h2 class="settings-heading">Settings for data fetch pluging</h2>
|
||||
<p class="kintoneplugin-desc">This message is displayed on the app page after the app has been updated.</p>
|
||||
<form class="js-submit-settings">
|
||||
<p class="kintoneplugin-row">
|
||||
<label for="message">
|
||||
Message:
|
||||
<input type="text" class="js-text-message kintoneplugin-input-text">
|
||||
</label>
|
||||
</p>
|
||||
<p class="kintoneplugin-row">
|
||||
<button type="button" class="js-cancel-button kintoneplugin-button-dialog-cancel">Cancel</button>
|
||||
<button class="kintoneplugin-button-dialog-ok">Save</button>
|
||||
</p>
|
||||
</form>
|
||||
</section>
|
||||
@@ -1,25 +0,0 @@
|
||||
(function (PLUGIN_ID) {
|
||||
const formEl = document.querySelector('.js-submit-settings');
|
||||
const cancelButtonEl = document.querySelector('.js-cancel-button');
|
||||
const messageEl = document.querySelector('.js-text-message');
|
||||
if (!(formEl && cancelButtonEl && messageEl)) {
|
||||
throw new Error('Required elements do not exist.');
|
||||
}
|
||||
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
if (config.message) {
|
||||
messageEl.value = config.message;
|
||||
}
|
||||
|
||||
formEl.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
kintone.plugin.app.setConfig({ message: messageEl.value }, () => {
|
||||
alert('The plug-in settings have been saved. Please update the app!');
|
||||
window.location.href = '../../flow?app=' + kintone.app.getId();
|
||||
});
|
||||
});
|
||||
|
||||
cancelButtonEl.addEventListener('click', () => {
|
||||
window.location.href = '../../' + kintone.app.getId() + '/plugin/';
|
||||
});
|
||||
})(kintone.$PLUGIN_ID);
|
||||
@@ -1,22 +0,0 @@
|
||||
(function (PLUGIN_ID) {
|
||||
kintone.events.on('app.record.index.show', () => {
|
||||
const spaceEl = kintone.app.getHeaderSpaceElement();
|
||||
if (spaceEl === null) {
|
||||
throw new Error('The header element is unavailable on this page.');
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
const headingEl = document.createElement('h3');
|
||||
const messageEl = document.createElement('p');
|
||||
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
messageEl.textContent = config.message;
|
||||
messageEl.classList.add('plugin-space-message');
|
||||
headingEl.textContent = 'Hello kintone plugin!';
|
||||
headingEl.classList.add('plugin-space-heading');
|
||||
|
||||
fragment.appendChild(headingEl);
|
||||
fragment.appendChild(messageEl);
|
||||
spaceEl.appendChild(fragment);
|
||||
});
|
||||
})(kintone.$PLUGIN_ID);
|
||||
@@ -1,22 +0,0 @@
|
||||
(function (PLUGIN_ID) {
|
||||
kintone.events.on('mobile.app.record.index.show', () => {
|
||||
const spaceEl = kintone.mobile.app.getHeaderSpaceElement();
|
||||
if (spaceEl === null) {
|
||||
throw new Error('The header element is unavailable on this page.');
|
||||
}
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
const headingEl = document.createElement('h3');
|
||||
const messageEl = document.createElement('p');
|
||||
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
messageEl.textContent = config.message;
|
||||
messageEl.classList.add('plugin-space-message');
|
||||
headingEl.textContent = 'Hello kintone plugin!';
|
||||
headingEl.classList.add('plugin-space-heading');
|
||||
|
||||
fragment.appendChild(headingEl);
|
||||
fragment.appendChild(messageEl);
|
||||
spaceEl.appendChild(fragment);
|
||||
});
|
||||
})(kintone.$PLUGIN_ID);
|
||||
BIN
document/テスト仕様書/検索取得プラグイン開発_コンフィグ設定_結合テスト仕様書.xlsx
Normal file
BIN
document/テスト仕様書/検索取得プラグイン開発_コンフィグ設定_結合テスト仕様書.xlsx
Normal file
Binary file not shown.
BIN
document/テスト仕様書/検索取得プラグイン開発_集約実行_結合テスト仕様書.xlsx
Normal file
BIN
document/テスト仕様書/検索取得プラグイン開発_集約実行_結合テスト仕様書.xlsx
Normal file
Binary file not shown.
Binary file not shown.
BIN
document/条件式整理.xlsx
Normal file
BIN
document/条件式整理.xlsx
Normal file
Binary file not shown.
@@ -22,3 +22,4 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
data
|
||||
41
vue-project/data-fetch-plugin/README.md
Normal file
41
vue-project/data-fetch-plugin/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
# data-fetch-plugin
|
||||
|
||||
1. コマンド:
|
||||
- `package.json`ファイルを開き、`scripts`内の`upload`コマンドのパラメータを接続する必要があるkintoneドメインに変更してください。
|
||||
package.json:
|
||||
```json
|
||||
"upload": "kintone-plugin-uploader --base-url https://{YOUR-KINTONE-DOMAIN}.cybozu.com --username {YOUR-USERID} --password {YOUR-PASSWORD} dist/plugin.zip ",
|
||||
```
|
||||
- `npm run build` を実行すると、`dist` ディレクトリにパッケージファイルを生成し、`plugin.zip` が作成されます。
|
||||
- `npm run upload` を実行すると、`plugin.zip` がkintoneにアップロードされます。
|
||||
- `npm run build-upload` を実行すると、上記両方同時実行されます。
|
||||
|
||||
2. Vue3.0を使用した開発:
|
||||
- 設定ページは `components/Config.vue` にて開発します。
|
||||
- Desktopページは `js/desktop.ts` にて開発します。
|
||||
- Mobileページは `js/desktop.ts` にて開発します。
|
||||
|
||||
3. 依存環境作成:
|
||||
- 最新のNode.js と npm のインストール
|
||||
- Yarnのインストール
|
||||
```bash
|
||||
npm install -g yarn
|
||||
```
|
||||
- 依存環境をインストール
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
- ビルド
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
- pluginアップロード
|
||||
```bash
|
||||
yarn upload
|
||||
```
|
||||
- ビルド&アップロード
|
||||
```
|
||||
yarn build-upload
|
||||
```
|
||||
---
|
||||
@@ -7,7 +7,9 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
CellInput: typeof import('./src/components/basic/conditions/CellInput.vue')['default']
|
||||
Config: typeof import('./src/components/Config.vue')['default']
|
||||
ErrorDialog: typeof import('./src/components/basic/ErrorDialog.vue')['default']
|
||||
PluginDropdown: typeof import('./src/components/basic/PluginDropdown.vue')['default']
|
||||
PluginInput: typeof import('./src/components/basic/PluginInput.vue')['default']
|
||||
PluginLabel: typeof import('./src/components/basic/PluginLabel.vue')['default']
|
||||
@@ -20,5 +22,7 @@ declare module 'vue' {
|
||||
TableCombobox: typeof import('./src/components/basic/TableCombobox.vue')['default']
|
||||
TableCondition: typeof import('./src/components/basic/conditions/TableCondition.vue')['default']
|
||||
TableConditionValue: typeof import('./src/components/basic/conditions/TableConditionValue.vue')['default']
|
||||
TableConditionValueDateTime: typeof import('./src/components/basic/conditions/TableConditionValueDateTime.vue')['default']
|
||||
TableConditionValueMultiInput: typeof import('./src/components/basic/conditions/TableConditionValueMultiInput.vue')['default']
|
||||
}
|
||||
}
|
||||
4
vue-project/data-fetch-plugin/dist/index.html
vendored
Normal file
4
vue-project/data-fetch-plugin/dist/index.html
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<script type="module" crossorigin src="/src/js/config.js"></script>
|
||||
<section class="settings">
|
||||
<div id="app"></div>
|
||||
</section>
|
||||
BIN
vue-project/data-fetch-plugin/dist/plugin.zip
vendored
Normal file
BIN
vue-project/data-fetch-plugin/dist/plugin.zip
vendored
Normal file
Binary file not shown.
@@ -11,7 +11,7 @@
|
||||
/* config 页面 */
|
||||
#app {
|
||||
width: 60vw;
|
||||
min-width: 920px;
|
||||
min-width: 940px;
|
||||
}
|
||||
|
||||
/* 最上面的说明 */
|
||||
@@ -27,7 +27,7 @@
|
||||
/* laebl input 单行的情况 */
|
||||
.flex-row .kintoneplugin-label {
|
||||
margin: 0;
|
||||
width: 10em;
|
||||
width: 8.5em;
|
||||
}
|
||||
|
||||
/* 遮罩 */
|
||||
4
vue-project/data-fetch-plugin/dist/src/html/config.html
vendored
Normal file
4
vue-project/data-fetch-plugin/dist/src/html/config.html
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<script type="module" crossorigin src="/src/js/config.js"></script>
|
||||
<section class="settings">
|
||||
<div id="app"></div>
|
||||
</section>
|
||||
|
Before Width: | Height: | Size: 110 B After Width: | Height: | Size: 110 B |
1
vue-project/data-fetch-plugin/dist/src/js/KintoneRestAPIClient.min.js
vendored
Normal file
1
vue-project/data-fetch-plugin/dist/src/js/KintoneRestAPIClient.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2230
vue-project/data-fetch-plugin/dist/src/js/config.js
vendored
Normal file
2230
vue-project/data-fetch-plugin/dist/src/js/config.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3
vue-project/data-fetch-plugin/dist/src/js/desktop.js
vendored
Normal file
3
vue-project/data-fetch-plugin/dist/src/js/desktop.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
var f=Object.defineProperty;var E=(c,e,t)=>e in c?f(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var l=(c,e,t)=>E(c,typeof e!="symbol"?e+"":e,t);new KintoneRestAPIClient;const h=["RECORD_NUMBER","CREATOR","CREATED_TIME","MODIFIER","UPDATED_TIME","CATEGORY","STATUS","STATUS_ASSIGNEE","SINGLE_LINE_TEXT","NUMBER","CALC","MULTI_LINE_TEXT","RICH_TEXT","LINK","CHECK_BOX","RADIO_BUTTON","DROP_DOWN","MULTI_SELECT","FILE","DATE","TIME","DATETIME","USER_SELECT","ORGANIZATION_SELECT","GROUP_SELECT","GROUP","REFERENCE_TABLE","SUBTABLE"];h.reduce((c,e)=>(c[e]=e,c),{});function C(c){return e=>(e==null?void 0:e.type)===c}const u=Object.fromEntries(h.map(c=>[c,C(c)]));class T{constructor(e,t){l(this,"config");l(this,"currentApp");l(this,"handleButtonClick",async()=>{const e=this.showSpinner();try{console.log("データ収集開始..."),await this.execDataFectch(),e.close(),location.reload()}catch(t){e.close();const r=`データ収集中処理中例外発生しました。${t instanceof Error?`
|
||||
詳細:`+t.message:""}`;console.error(r,t),window.alert(r)}});l(this,"execDataFectch",async()=>{const e=this.config.joinTables[0];let n=(await this.fetchDataFromApp(e)).map(i=>{const o={};return e.fieldsMapping.forEach(s=>{o[this.fieldCode(s.rightField)]=i[this.fieldCode(s.leftField)]}),o});const r=this.config.joinTables.filter((i,o)=>o>0);for(const i of r){const o=await this.fetchDataFromApp(i);n=this.leftJoin(n,o,i)}await this.deleteCurrentRecords(),await this.saveDataToCurrentApp(n)});l(this,"fetchDataFromApp",async e=>{const t=this.getWhereCondition(e.whereConditions),n=e.fieldsMapping.map(s=>this.fieldCode(s.leftField));e.table&&n.push(e.table),e.onConditions.map(s=>this.fieldCode(s.leftField)).forEach(s=>{n.includes(s)||n.push(s)});const o=await new KintoneRestAPIClient().record.getAllRecords({app:e.app,fields:n,condition:t});return this.convertToFlatDatas(o,e.table)});this.config=e,this.currentApp=t}init(){this.addButtonToView()}addButtonToView(){const e=kintone.app.getHeaderMenuSpaceElement();if(!e)throw new Error("このページではヘッダー要素が利用できません。");if(document.getElementById("btn-data-fetch"))return;const t=Kucs["1.18.0"],n=new t.Button({text:this.config.buttonName,type:"submit",id:"btn-data-fetch"});n.addEventListener("click",()=>this.handleButtonClick()),e.appendChild(n)}showSpinner(){const e=Kucs["1.18.0"],t=new e.Spinner({text:"データ収集中",container:document.body});return t.open(),t}getWhereCondition(e){return e.filter(r=>this.fieldCode(r.field)!=="").map(r=>{let i=r.condition;"subField"in r.field&&r.field.subField&&(i=this.mapConditionForSubField(r.condition));const o=this.getConditionValue(r.field,i,r.data);return`${this.fieldCode(r.field)} ${i} ${o}`}).join(" and ")}mapConditionForSubField(e){switch(e){case"=":return"in";case"!=":return"not in";default:return e}}getConditionValue(e,t,n){return n?u.NUMBER(e)||u.RECORD_NUMBER(e)?n:u.DATE(e)?/^\d{4}-\d{2}-\d{2}$/.test(n)||n.match(/^\w+\(.*\)$/)?`"${n}"`:`"${new Date(n).toISOString().split("T")[0]}"`:u.DATETIME(e)||u.CREATED_TIME(e)||u.UPDATED_TIME(e)?n.match(/^\w+\(.*\)$/)?`"${n}"`:`"${new Date(n).toISOString()}"`:t==="in"||t==="not in"?n.includes(",")?`(${n.split(",").map(i=>`"${i.trim()}"`).join(",")})`:`("${n}")`:`"${n}"`:""}fieldCode(e){return e?typeof e=="string"&&e?e:typeof e=="object"&&"code"in e?e.code:"":""}convertToFlatDatas(e,t){if(!t)return e;const n=[];return e.forEach(r=>{var i;if(((i=r[t])==null?void 0:i.type)==="SUBTABLE"&&r[t].value.length>0)r[t].value.forEach(o=>{const s={...r};Object.entries(o.value).forEach(([d,a])=>{s[d]={value:a.value,type:a.type}}),delete s[t],n.push(s)});else{const o={...r};delete o[t],n.push(o)}}),n}leftJoin(e,t,n){const r=[];return e.forEach(i=>{const o=t.filter(s=>n.onConditions.every(d=>{var a,p;return((a=i[this.fieldCode(d.rightField)])==null?void 0:a.value)===((p=s[this.fieldCode(d.leftField)])==null?void 0:p.value)}));o?o.forEach(s=>{const d={...i};n.fieldsMapping.forEach(a=>{d[this.fieldCode(a.rightField)]=s[this.fieldCode(a.leftField)]}),r.push(d)}):r.push(i)}),r}async deleteCurrentRecords(){const e=new KintoneRestAPIClient,n=(await e.record.getAllRecords({app:this.currentApp,fields:["$id"]})).map(r=>({id:r.$id.value}));await e.record.deleteAllRecords({app:this.currentApp,records:n}),e.record.addAllRecords}async saveDataToCurrentApp(e){try{const n=await new KintoneRestAPIClient().record.addAllRecords({app:this.currentApp,records:this.convertForUpdate(e)})}catch(t){throw console.error("データ作成時エラーが発生しました:",t),t}}convertForUpdate(e){return e.map(t=>Object.fromEntries(Object.entries(t).map(([n,{value:r}])=>[n,{value:r}])))}}(function(c){kintone.events.on("app.record.index.show",t=>{var n;try{const r=kintone.plugin.app.getConfig(c),i=e(r),o=(n=kintone.app.getId())==null?void 0:n.toString();if(!o)return;new T(i,o).init()}catch(r){const o=`データ収集中処理中例外発生しました。${r instanceof Error?`
|
||||
詳細:`+r.message:""}`;t.error=o}return t});function e(t){return{buttonName:t.buttonName,joinTables:JSON.parse(t.joinTables)}}})(kintone.$PLUGIN_ID);
|
||||
3
vue-project/data-fetch-plugin/dist/src/js/mobile.js
vendored
Normal file
3
vue-project/data-fetch-plugin/dist/src/js/mobile.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
var f=Object.defineProperty;var E=(c,e,t)=>e in c?f(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var l=(c,e,t)=>E(c,typeof e!="symbol"?e+"":e,t);new KintoneRestAPIClient;const h=["RECORD_NUMBER","CREATOR","CREATED_TIME","MODIFIER","UPDATED_TIME","CATEGORY","STATUS","STATUS_ASSIGNEE","SINGLE_LINE_TEXT","NUMBER","CALC","MULTI_LINE_TEXT","RICH_TEXT","LINK","CHECK_BOX","RADIO_BUTTON","DROP_DOWN","MULTI_SELECT","FILE","DATE","TIME","DATETIME","USER_SELECT","ORGANIZATION_SELECT","GROUP_SELECT","GROUP","REFERENCE_TABLE","SUBTABLE"];h.reduce((c,e)=>(c[e]=e,c),{});function C(c){return e=>(e==null?void 0:e.type)===c}const p=Object.fromEntries(h.map(c=>[c,C(c)]));class T{constructor(e,t){l(this,"config");l(this,"currentApp");l(this,"handleButtonClick",async()=>{const e=this.showSpinner();try{console.log("データ収集開始..."),await this.execDataFectch(),e.close(),location.reload()}catch(t){e.close();const r=`データ収集中処理中例外発生しました。${t instanceof Error?`
|
||||
詳細:`+t.message:""}`;console.error(r,t),window.alert(r)}});l(this,"execDataFectch",async()=>{const e=this.config.joinTables[0];let n=(await this.fetchDataFromApp(e)).map(i=>{const o={};return e.fieldsMapping.forEach(s=>{o[this.fieldCode(s.rightField)]=i[this.fieldCode(s.leftField)]}),o});const r=this.config.joinTables.filter((i,o)=>o>0);for(const i of r){const o=await this.fetchDataFromApp(i);n=this.leftJoin(n,o,i)}await this.deleteCurrentRecords(),await this.saveDataToCurrentApp(n)});l(this,"fetchDataFromApp",async e=>{const t=this.getWhereCondition(e.whereConditions),n=e.fieldsMapping.map(s=>this.fieldCode(s.leftField));e.table&&n.push(e.table),e.onConditions.map(s=>this.fieldCode(s.leftField)).forEach(s=>{n.includes(s)||n.push(s)});const o=await new KintoneRestAPIClient().record.getAllRecords({app:e.app,fields:n,condition:t});return this.convertToFlatDatas(o,e.table)});this.config=e,this.currentApp=t}init(){this.addButtonToView()}addButtonToView(){const e=kintone.mobile.app.getHeaderSpaceElement();if(!e)throw new Error("このページではヘッダー要素が利用できません。");if(document.getElementById("btn-data-fetch"))return;const t=Kucs["1.18.0"],n=new t.Button({text:this.config.buttonName,type:"submit",id:"btn-data-fetch"});n.addEventListener("click",()=>this.handleButtonClick()),e.appendChild(n)}showSpinner(){const e=Kucs["1.18.0"],t=new e.Spinner({text:"データ収集中",container:document.body});return t.open(),t}getWhereCondition(e){return e.filter(r=>this.fieldCode(r.field)!=="").map(r=>{let i=r.condition;"subField"in r.field&&r.field.subField&&(i=this.mapConditionForSubField(r.condition));const o=this.getConditionValue(r.field,i,r.data);return`${this.fieldCode(r.field)} ${i} ${o}`}).join(" and ")}mapConditionForSubField(e){switch(e){case"=":return"in";case"!=":return"not in";default:return e}}getConditionValue(e,t,n){return n?p.NUMBER(e)||p.RECORD_NUMBER(e)?n:p.DATE(e)?/^\d{4}-\d{2}-\d{2}$/.test(n)||n.match(/^\w+\(.*\)$/)?`"${n}"`:`"${new Date(n).toISOString().split("T")[0]}"`:p.DATETIME(e)||p.CREATED_TIME(e)||p.UPDATED_TIME(e)?n.match(/^\w+\(.*\)$/)?`"${n}"`:`"${new Date(n).toISOString()}"`:t==="in"||t==="not in"?n.includes(",")?`(${n.split(",").map(i=>`"${i.trim()}"`).join(",")})`:`("${n}")`:`"${n}"`:""}fieldCode(e){return e?typeof e=="string"&&e?e:typeof e=="object"&&"code"in e?e.code:"":""}convertToFlatDatas(e,t){if(!t)return e;const n=[];return e.forEach(r=>{var i;if(((i=r[t])==null?void 0:i.type)==="SUBTABLE"&&r[t].value.length>0)r[t].value.forEach(o=>{const s={...r};Object.entries(o.value).forEach(([d,a])=>{s[d]={value:a.value,type:a.type}}),delete s[t],n.push(s)});else{const o={...r};delete o[t],n.push(o)}}),n}leftJoin(e,t,n){const r=[];return e.forEach(i=>{const o=t.filter(s=>n.onConditions.every(d=>{var a,u;return((a=i[this.fieldCode(d.rightField)])==null?void 0:a.value)===((u=s[this.fieldCode(d.leftField)])==null?void 0:u.value)}));o?o.forEach(s=>{const d={...i};n.fieldsMapping.forEach(a=>{d[this.fieldCode(a.rightField)]=s[this.fieldCode(a.leftField)]}),r.push(d)}):r.push(i)}),r}async deleteCurrentRecords(){const e=new KintoneRestAPIClient,n=(await e.record.getAllRecords({app:this.currentApp,fields:["$id"]})).map(r=>({id:r.$id.value}));await e.record.deleteAllRecords({app:this.currentApp,records:n}),e.record.addAllRecords}async saveDataToCurrentApp(e){try{const n=await new KintoneRestAPIClient().record.addAllRecords({app:this.currentApp,records:this.convertForUpdate(e)})}catch(t){throw console.error("データ作成時エラーが発生しました:",t),t}}convertForUpdate(e){return e.map(t=>Object.fromEntries(Object.entries(t).map(([n,{value:r}])=>[n,{value:r}])))}}(function(c){kintone.events.on("mobile.app.record.index.show",t=>{var n;try{const r=kintone.plugin.app.getConfig(c),i=e(r),o=(n=kintone.mobile.app.getId())==null?void 0:n.toString();if(!o)return;new T(i,o).init()}catch(r){const o=`データ収集中処理中例外発生しました。${r instanceof Error?`
|
||||
詳細:`+r.message:""}`;t.error=o}return t});function e(t){return{buttonName:t.buttonName,joinTables:JSON.parse(t.joinTables)}}})(kintone.$PLUGIN_ID);
|
||||
@@ -5,6 +5,7 @@
|
||||
"type": "APP",
|
||||
"desktop": {
|
||||
"js": [
|
||||
"js/KintoneRestAPIClient.min.js",
|
||||
"js/kuc.min.js",
|
||||
"js/desktop.js"
|
||||
],
|
||||
@@ -37,6 +38,7 @@
|
||||
},
|
||||
"mobile": {
|
||||
"js": [
|
||||
"js/KintoneRestAPIClient.min.js",
|
||||
"js/kuc.min.js",
|
||||
"js/mobile.js"
|
||||
],
|
||||
1
vue-project/data-fetch-plugin/src/assets/KintoneRestAPIClient.min.js
vendored
Normal file
1
vue-project/data-fetch-plugin/src/assets/KintoneRestAPIClient.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 110 B After Width: | Height: | Size: 110 B |
2377
vue-project/data-fetch-plugin/src/assets/kuc.min.js
vendored
Normal file
2377
vue-project/data-fetch-plugin/src/assets/kuc.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
166
vue-project/data-fetch-plugin/src/components/Config.vue
Normal file
166
vue-project/data-fetch-plugin/src/components/Config.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<h2 class="settings-heading">{{ $t('config.title') }}</h2>
|
||||
<p class="kintoneplugin-desc">{{ $t('config.desc') }}</p>
|
||||
|
||||
<plugin-row class="header-row border">
|
||||
<plugin-input v-model="data.buttonName" placeholder="ボタン名を入力してください" label="集約ボタン名" />
|
||||
</plugin-row>
|
||||
|
||||
<div id="main-area" ref="mainArea">
|
||||
<plugin-table-area v-for="joinTable in data.joinTables" :table="joinTable" :key="joinTable.id" />
|
||||
</div>
|
||||
|
||||
<plugin-row class="footer-row border">
|
||||
<kuc-button text="キャンセル" type="normal" @click="cancel" />
|
||||
<kuc-button :disabled="!canSave" text="保存する" class="save-btn" type="submit" @click="save" />
|
||||
</plugin-row>
|
||||
|
||||
<kuc-spinner :container="mainArea" ref="spinner"></kuc-spinner>
|
||||
<error-dialog :message="errMessage" :show="showError" @update:show="(value) => showError = value"></error-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
createEmptyJoinTable,
|
||||
loadApps,
|
||||
loadAppFieldsAndLayout,
|
||||
EMPTY_OPTION,
|
||||
getEmptyOnCondition,
|
||||
getMeta,
|
||||
} from '@/js/helper';
|
||||
import { isType, type OneOf, type Properties } from '@/js/kintone-rest-api-client';
|
||||
import type { CachedData, FieldsInfo, JoinTable, SavedData } from '@/types/model';
|
||||
import type { Spinner } from 'kintone-ui-component';
|
||||
|
||||
import { onMounted, watch, provide, reactive, ref, shallowRef, nextTick } from 'vue';
|
||||
|
||||
const props = defineProps<{ pluginId: string }>();
|
||||
const loading = ref(false);
|
||||
const canSave = ref(true);
|
||||
const data: SavedData = reactive({
|
||||
buttonName: '',
|
||||
joinTables: [createEmptyJoinTable()],
|
||||
});
|
||||
const showError = ref(false);
|
||||
const errMessage = ref("");
|
||||
|
||||
const cachedData: CachedData = reactive({
|
||||
apps: [EMPTY_OPTION],
|
||||
currentAppFields: { fields: {}, layout: [] } as FieldsInfo,
|
||||
});
|
||||
|
||||
provide('savedData', data);
|
||||
provide('canSave', (data: boolean) => {
|
||||
canSave.value = data;
|
||||
});
|
||||
provide('cachedData', cachedData);
|
||||
|
||||
const mainArea = shallowRef<HTMLElement | null>(null);
|
||||
const spinner = shallowRef<Spinner | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
nextTick(async () => {
|
||||
spinner.value?.close(); // fix bug: kuc-spinner will not auto amount to target HTML element when init loading
|
||||
const savedData = kintone.plugin.app.getConfig(props.pluginId);
|
||||
loading.value = true;
|
||||
cachedData.apps = await loadApps();
|
||||
cachedData.currentAppFields = await loadAppFieldsAndLayout();
|
||||
if (savedData?.joinTablesForConfig) {
|
||||
const newJoinTables = JSON.parse(savedData.joinTablesForConfig);
|
||||
data.joinTables.length = 0;
|
||||
data.joinTables.push(...newJoinTables);
|
||||
}
|
||||
data.buttonName = savedData?.buttonName || '集約';
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
watch(loading, (load) => {
|
||||
load ? spinner.value?.open() : spinner.value?.close();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => data.joinTables.length,
|
||||
(newLength) => {
|
||||
console.log(data.joinTables);
|
||||
if (newLength === 1) {
|
||||
data.joinTables[0].onConditions = [getEmptyOnCondition()];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 保存データのバリデーション関数
|
||||
*/
|
||||
function validate(data: SavedData<string>): boolean {
|
||||
// ボタン名が空の場合、エラーを表示
|
||||
if (!data.buttonName.trim()) {
|
||||
errMessage.value = 'ボタン名を入力してください。';
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const joinTable of data.joinTables) {
|
||||
// 取得元アプリが空の場合、エラーを表示
|
||||
if (!joinTable.app.trim()) {
|
||||
errMessage.value = '取得元アプリを指定してください。';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 取得フィールドのマッピングが1つ未満の場合、エラーを表示
|
||||
if (
|
||||
joinTable.fieldsMapping.length < 1 ||
|
||||
!joinTable.fieldsMapping[0].leftField?.trim() ||
|
||||
!joinTable.fieldsMapping[0].rightField?.trim()
|
||||
) {
|
||||
errMessage.value = '取得フィールドを1つ以上設定してください。';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function save() {
|
||||
if(!validate(data)){
|
||||
showError.value=true;
|
||||
return;
|
||||
}
|
||||
const currentAppMeta = cachedData.currentAppFields.fields;
|
||||
const convertJoinTables = JSON.parse(JSON.stringify(data.joinTables)) as JoinTable<OneOf | string>[];
|
||||
convertJoinTables.forEach((item) => {
|
||||
const meta = getMeta(item.meta as Properties, item.table);
|
||||
if (!meta) return;
|
||||
|
||||
// Process onConditions
|
||||
item.onConditions.forEach((condition) => {
|
||||
condition.leftField = meta[condition.leftField as string] || condition.leftField;
|
||||
condition.rightField = currentAppMeta[condition.rightField as string] || condition.rightField;
|
||||
});
|
||||
|
||||
// Process fieldsMapping
|
||||
item.fieldsMapping.forEach((mapping) => {
|
||||
mapping.leftField = meta[mapping.leftField as string] || mapping.leftField;
|
||||
mapping.rightField = currentAppMeta[mapping.rightField as string] || mapping.rightField;
|
||||
});
|
||||
|
||||
// Process whereConditions
|
||||
item.whereConditions.forEach((condition) => {
|
||||
condition.field = meta[condition.field as string] || condition.field;
|
||||
});
|
||||
delete item.meta;
|
||||
});
|
||||
|
||||
data.joinTables.forEach((item) => {
|
||||
delete item.meta;
|
||||
});
|
||||
|
||||
kintone.plugin.app.setConfig({
|
||||
buttonName: data.buttonName,
|
||||
joinTables: JSON.stringify(convertJoinTables),
|
||||
joinTablesForConfig: JSON.stringify(data.joinTables || []),
|
||||
});
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
window.location.href = `../../${kintone.app.getId()}/plugin/`;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
<template>
|
||||
<div ref="dialogContainer">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Button, Dialog } from "kintone-ui-component";
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
message: string;
|
||||
show: boolean;
|
||||
}>();
|
||||
|
||||
const isDialogVisible = ref(props.show);
|
||||
|
||||
watch(
|
||||
() => props.message,
|
||||
(newMessage) => {
|
||||
if (dialog.value) {
|
||||
dialog.value.content=newMessage;
|
||||
}
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.show,
|
||||
(newValue) => {
|
||||
if (dialog.value) {
|
||||
if (newValue) {
|
||||
dialog.value.open();
|
||||
} else {
|
||||
dialog.value.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(["update:show"]);
|
||||
|
||||
const dialog = ref<Dialog | null>(null);
|
||||
const dialogContainer = ref<HTMLDivElement | null>(null);
|
||||
|
||||
const closeDialog = () => {
|
||||
if (dialog.value) {
|
||||
dialog.value.close();
|
||||
}
|
||||
emit("update:show", false);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!dialogContainer.value) return;
|
||||
|
||||
const okButton = new Button({ text: "OK", type: "normal" });
|
||||
okButton.addEventListener("click", closeDialog);
|
||||
|
||||
const footerDiv = document.createElement("div");
|
||||
footerDiv.className="dialog-action-bar";
|
||||
footerDiv.appendChild(okButton);
|
||||
|
||||
// 创建 Dialog 实例
|
||||
dialog.value = new Dialog({
|
||||
header: "エラー情報",
|
||||
content: props.message,
|
||||
icon: "error",
|
||||
container: dialogContainer.value,
|
||||
footer: footerDiv,
|
||||
footerVisible:true
|
||||
});
|
||||
|
||||
if (props.show) {
|
||||
dialog.value.open();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import type { DropdownItem } from 'kintone-ui-component';
|
||||
import type { ComboboxChangeEventDetail, DropdownItem } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -21,7 +21,7 @@ const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void;
|
||||
}>();
|
||||
|
||||
const updateValue = (event: KucEvent) => {
|
||||
emit('update:modelValue', event.detail.value);
|
||||
const updateValue = ({ detail }: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
emit('update:modelValue', detail.value || '');
|
||||
};
|
||||
</script>
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import type { TextInputEventDetail } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -19,7 +20,7 @@ const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void;
|
||||
}>();
|
||||
|
||||
const updateValue = (event: KucEvent) => {
|
||||
emit('update:modelValue', event.detail.value);
|
||||
const updateValue = ({ detail }: KucEvent<TextInputEventDetail>) => {
|
||||
emit('update:modelValue', detail.value || '');
|
||||
};
|
||||
</script>
|
||||
@@ -16,15 +16,16 @@
|
||||
</plugin-row>
|
||||
<plugin-row class="flex-row" v-if="isJoinConditionShown(table)">
|
||||
<plugin-label label="連結条件" />
|
||||
<plugin-table-connect-row connector="=" :modelValue="table.onConditions" />
|
||||
<plugin-table-connect-row connector="=" type="connect" :modelValue="table.onConditions" />
|
||||
</plugin-row>
|
||||
<plugin-row class="flex-row">
|
||||
<plugin-label label="取得フィールド" />
|
||||
<plugin-table-connect-row connector="→" :modelValue="table.fieldsMapping" />
|
||||
<plugin-table-connect-row connector="→" type="mapping" :modelValue="table.fieldsMapping" />
|
||||
</plugin-row>
|
||||
<plugin-row class="flex-row">
|
||||
<plugin-label label="絞込条件" />
|
||||
<plugin-table-condition-row :modelValue="table.whereConditions"/>
|
||||
<plugin-table-condition-row :modelValue="table.whereConditions" :table="table"
|
||||
@update:modelValue="(newData:any) => table.whereConditions = newData"/>
|
||||
</plugin-row>
|
||||
</div>
|
||||
<div class="table-action-area">
|
||||
@@ -43,7 +44,6 @@ import {
|
||||
} from '@/js/helper';
|
||||
import { types } from '@/js/kintone-rest-api-client';
|
||||
import type { CachedData, CachedSelectedAppData, FieldsInfo, JoinTable, SavedData } from '@/types/model';
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import { computed, inject, provide, reactive, ref, watch } from 'vue';
|
||||
|
||||
const savedData = inject<SavedData>('savedData') as SavedData;
|
||||
@@ -86,13 +86,14 @@ watch(
|
||||
const fields = await loadAppFieldsAndLayout(newVal);
|
||||
tableOptions.value = getTableFieldsDropdownItems(fields, types.SUBTABLE);
|
||||
selectedAppData.appFields = fields;
|
||||
props.table.meta = fields.fields;
|
||||
!!oldVal && resetTable(props.table);
|
||||
loading.value = false;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const selectTable = (e: KucEvent) => {
|
||||
const selectTable = () => {
|
||||
resetConditions(props.table);
|
||||
};
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
<template>
|
||||
<kuc-table :class-name.camel="['plugin-kuc-table']" :columns="columns" :data="modelValue" />
|
||||
<kuc-table className='plugin-kuc-table condition-table'
|
||||
:columns="columns"
|
||||
:data="modelValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CachedData, CachedSelectedAppData, SavedData, WhereCondition } from '@/types/model';
|
||||
import { defineProps, inject, computed, render, h, reactive } from 'vue';
|
||||
import type { CachedData, CachedSelectedAppData, JoinTable, SavedData, WhereCondition } from '@/types/model';
|
||||
import { defineProps, inject, computed, render, h, reactive, watch } from 'vue';
|
||||
import TableCombobox from './TableCombobox.vue';
|
||||
import { generateId, getFieldsDropdownItems, search } from '@/js/helper';
|
||||
import type { ConditionValue } from '@/js/conditions';
|
||||
import TableCondition from './conditions/TableCondition.vue';
|
||||
import TableConditionValue from './conditions/TableConditionValue.vue';
|
||||
import type { KucTableEvent } from '@/types/my-kintone';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: WhereCondition[];
|
||||
table:JoinTable;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: WhereCondition[]): void;
|
||||
}>();
|
||||
|
||||
const savedData = inject<SavedData>('savedData') as SavedData;
|
||||
const cachedData = inject<CachedData>('cachedData') as CachedData;
|
||||
const selectedAppData = inject<CachedSelectedAppData>('selectedAppData') as CachedSelectedAppData;
|
||||
const table = computed(() => selectedAppData.table.table);
|
||||
// const table = computed(() => selectedAppData.table.table);
|
||||
|
||||
const canSave = inject<(canSave: boolean) => void>('canSave') as (canSave: boolean) => void;
|
||||
|
||||
watch(
|
||||
()=>props.modelValue,
|
||||
(newValue,oldValue)=>{
|
||||
console.log(newValue);
|
||||
console.log(oldValue);
|
||||
},{
|
||||
deep:true,
|
||||
immediate:true
|
||||
}
|
||||
)
|
||||
|
||||
const columns = reactive([
|
||||
{
|
||||
@@ -33,11 +52,13 @@ const columns = reactive([
|
||||
const vnode = h(TableCombobox, {
|
||||
items: computed(() =>
|
||||
getFieldsDropdownItems(selectedAppData.appFields, {
|
||||
subTableCode: table.value,
|
||||
subTableCode: '', //table.value,
|
||||
baseFilter: undefined,
|
||||
defaultLabel: 'すべてのレコード',
|
||||
needAllSubTableField: true,
|
||||
}),
|
||||
),
|
||||
modelValue: (search(props.modelValue, rowData.id) as WhereCondition)?.field || '',
|
||||
modelValue: computed(() => (search(props.modelValue, rowData.id) as WhereCondition)?.field || ''),
|
||||
selectedAppData,
|
||||
dataList: props.modelValue,
|
||||
id: rowData.id,
|
||||
@@ -60,15 +81,12 @@ const columns = reactive([
|
||||
render: (cellData: string, rowData: WhereCondition) => {
|
||||
const container = document.createElement('div');
|
||||
const vnode = h(TableCondition, {
|
||||
modelValue: (search(props.modelValue, rowData.id) as WhereCondition)?.condition || '',
|
||||
modelValue: computed(() => (search(props.modelValue, rowData.id) as WhereCondition)?.condition || ''),
|
||||
selectedAppData,
|
||||
id: rowData.id,
|
||||
whereConditions: props.modelValue,
|
||||
'onUpdate:modelValue': ({obj, value}) => {
|
||||
if (obj) {
|
||||
obj.condition = value;
|
||||
obj.data = '';
|
||||
}
|
||||
obj && (obj.condition = value);
|
||||
},
|
||||
});
|
||||
render(vnode, container);
|
||||
@@ -81,12 +99,19 @@ const columns = reactive([
|
||||
render: (cellData: string, rowData: WhereCondition) => {
|
||||
const container = document.createElement('div');
|
||||
const vnode = h(TableConditionValue, {
|
||||
modelValue: (search(props.modelValue, rowData.id) as WhereCondition)?.data || '',
|
||||
modelValue: computed(() => (search(props.modelValue, rowData.id) as WhereCondition)?.data || ''),
|
||||
selectedAppData,
|
||||
canSave,
|
||||
id: rowData.id,
|
||||
whereConditions: props.modelValue,
|
||||
'onUpdate:modelValue': ({obj, value}) => {
|
||||
obj && (obj.data = value);
|
||||
if(obj){
|
||||
obj.data = value;
|
||||
const newData = props.modelValue.map((item) =>
|
||||
item.id === obj.id ? { ...item, data: value } : item
|
||||
);
|
||||
emit('update:modelValue', newData);
|
||||
}
|
||||
},
|
||||
});
|
||||
render(vnode, container);
|
||||
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<kuc-table className='plugin-kuc-table' :columns="columns" :data="modelValue"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CachedData, CachedSelectedAppData, FieldsJoinMapping, WhereCondition } from '@/types/model';
|
||||
import { defineProps, inject, computed, reactive, render, h } from 'vue';
|
||||
import { generateId, getFieldObj, getFieldsDropdownItems, search } from '@/js/helper';
|
||||
import { getLeftAvailableJoinType, getRightAvailableJoinType, isLeftJoinForceDisable, isRightJoinForceDisable, } from '@/js/join';
|
||||
import { getLeftAvailableMappingType, getRightAvailableMappingType } from '@/js/mapping';
|
||||
import TableCombobox from './TableCombobox.vue';
|
||||
import { type FieldType, type OneOf } from '@/js/kintone-rest-api-client';
|
||||
|
||||
const props = defineProps<{
|
||||
connector: string;
|
||||
modelValue: FieldsJoinMapping[];
|
||||
type: 'connect' | 'mapping';
|
||||
}>();
|
||||
|
||||
const cachedData = inject<CachedData>('cachedData') as CachedData;
|
||||
const selectedAppData = inject<CachedSelectedAppData>('selectedAppData') as CachedSelectedAppData;
|
||||
const table = computed(() => selectedAppData.table.table);
|
||||
|
||||
const filterFunc = {
|
||||
connect: {
|
||||
left: (right?: OneOf | '') => getLeftAvailableJoinType(right),
|
||||
right: (left?: OneOf | '') => getRightAvailableJoinType(left),
|
||||
},
|
||||
mapping: {
|
||||
left: (right?: OneOf | '') => getLeftAvailableMappingType(right),
|
||||
right: (left?: OneOf | '') => getRightAvailableMappingType(left),
|
||||
},
|
||||
};
|
||||
|
||||
const columns = reactive([
|
||||
{
|
||||
title: '取得元アプリのフィールド',
|
||||
field: 'leftField',
|
||||
render: (cellData: string, rowData: WhereCondition) => {
|
||||
if (!rowData.id) {
|
||||
rowData.id = generateId();
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
const vnode = h(TableCombobox, {
|
||||
items: computed(() => {
|
||||
const dependFilterField = getField('rightField', rowData.id);
|
||||
return getFieldsDropdownItems(selectedAppData.appFields, {
|
||||
subTableCode: table.value,
|
||||
baseFilter: filterFunc[props.type].left() as FieldType[],
|
||||
filterType: filterFunc[props.type].left(dependFilterField),
|
||||
dependFilterField,
|
||||
defaultDisableCallback: isLeftJoinForceDisable,
|
||||
});
|
||||
}),
|
||||
modelValue: computed(() => (search(props.modelValue, rowData.id) as FieldsJoinMapping)?.leftField || ''),
|
||||
selectedAppData,
|
||||
dataList: props.modelValue,
|
||||
id: rowData.id,
|
||||
'onUpdate:modelValue': (data) => {
|
||||
if (data.obj) {
|
||||
(data.obj as FieldsJoinMapping).leftField = data.value;
|
||||
}
|
||||
},
|
||||
});
|
||||
render(vnode, container);
|
||||
return container;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
field: 'connector',
|
||||
render: () => {
|
||||
return props.connector;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'このアプリのフィールド',
|
||||
field: 'rightField',
|
||||
render: (cellData: string, rowData: WhereCondition) => {
|
||||
if (!rowData.id) {
|
||||
rowData.id = generateId();
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
const vnode = h(TableCombobox, {
|
||||
items: computed(() => {
|
||||
const dependFilterField = getField('leftField', rowData.id);
|
||||
return getFieldsDropdownItems(cachedData.currentAppFields, {
|
||||
subTableCode: '', // subtable not allowed for current app
|
||||
baseFilter: filterFunc[props.type].right() as FieldType[],
|
||||
filterType: filterFunc[props.type].right(dependFilterField),
|
||||
dependFilterField,
|
||||
defaultDisableCallback: props.type === 'connect' ? isRightJoinForceDisable : undefined,
|
||||
});
|
||||
}),
|
||||
modelValue: computed(() => (search(props.modelValue, rowData.id) as FieldsJoinMapping)?.rightField || ''),
|
||||
selectedAppData,
|
||||
dataList: props.modelValue,
|
||||
id: rowData.id,
|
||||
'onUpdate:modelValue': (data) => {
|
||||
if (data.obj) {
|
||||
(data.obj as FieldsJoinMapping).rightField = data.value;
|
||||
}
|
||||
},
|
||||
});
|
||||
render(vnode, container);
|
||||
return container;
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
function getField(key: 'leftField' | 'rightField', id: string) {
|
||||
const dataRow = search(props.modelValue, id) as FieldsJoinMapping | undefined;
|
||||
const fieldCode = dataRow ? dataRow[key] || '' : '';
|
||||
const targetFieldMap = key === 'leftField' ? selectedAppData.appFields : cachedData.currentAppFields;
|
||||
const targetTable = key === 'leftField' ? table.value : '';
|
||||
return getFieldObj(fieldCode, targetFieldMap, targetTable);
|
||||
}
|
||||
</script>
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<kuc-combobox
|
||||
className="kuc-text-input"
|
||||
:items="items.value"
|
||||
:value="modelValue"
|
||||
:value="modelValue.value"
|
||||
@change="updateValue"
|
||||
:key="componentKey"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
@@ -12,12 +13,12 @@
|
||||
import { search } from '@/js/helper';
|
||||
import type { CachedSelectedAppData } from '@/types/model';
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import type { DropdownItem } from 'kintone-ui-component';
|
||||
import type { ComboboxChangeEventDetail, DropdownItem } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits, type Ref, watch, ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
items: Ref<DropdownItem[]>;
|
||||
modelValue: string;
|
||||
modelValue: Ref<string>;
|
||||
dataList: any[];
|
||||
id: string;
|
||||
selectedAppData: CachedSelectedAppData;
|
||||
@@ -28,7 +29,7 @@ const componentKey = ref(0);
|
||||
watch(
|
||||
() => props.items.value,
|
||||
() => {
|
||||
if (!props.modelValue) return;
|
||||
if (!props.modelValue.value) return;
|
||||
componentKey.value += 1;
|
||||
},
|
||||
{
|
||||
@@ -45,7 +46,7 @@ const emit = defineEmits<{
|
||||
(e: 'update:modelValue', data: EmitData): void;
|
||||
}>();
|
||||
|
||||
const updateValue = (event: KucEvent) => {
|
||||
emit('update:modelValue', { obj: search(props.dataList, props.id), value: event.detail.value });
|
||||
const updateValue = ({ detail }: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
emit('update:modelValue', { obj: search(props.dataList, props.id), value: detail.value || '' });
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<kuc-text className="kuc-text-input" :value="modelValue" @change="updateValue" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import type { TextInputEventDetail } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits, type Ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void;
|
||||
}>();
|
||||
|
||||
const updateValue = ({ detail }: KucEvent<TextInputEventDetail>) => {
|
||||
emit('update:modelValue', detail.value || '');
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<kuc-combobox
|
||||
v-if="items?.length"
|
||||
:items="items"
|
||||
:value="value"
|
||||
@change.stop="updateValue"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
className="condition-combobox-short"
|
||||
:data-val="value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getAvailableCondition, type ConditionValue } from '@/js/conditions';
|
||||
import { search } from '@/js/helper';
|
||||
import type { CachedSelectedAppData, WhereCondition } from '@/types/model';
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import type { ComboboxChangeEventDetail } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits, computed, watch, ref, type Ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Ref<string>;
|
||||
selectedAppData: CachedSelectedAppData;
|
||||
whereConditions: WhereCondition[];
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const whereCondition = computed(() => search(props.whereConditions, props.id) as WhereCondition | undefined);
|
||||
|
||||
const items = computed(() =>
|
||||
getAvailableCondition(
|
||||
whereCondition.value?.field || '',
|
||||
props.selectedAppData.appFields,
|
||||
props.selectedAppData.table.table,
|
||||
),
|
||||
);
|
||||
|
||||
const value = ref(props.modelValue.value);
|
||||
|
||||
watch(
|
||||
() => items,
|
||||
() => {
|
||||
if (whereCondition.value?.condition === '') {
|
||||
// select first option
|
||||
const option = items.value?.[0] || { value: '' };
|
||||
value.value = option.value;
|
||||
updateValue({ detail: option });
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
type EmitData = {
|
||||
obj?: WhereCondition;
|
||||
value: ConditionValue;
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', data: EmitData): void;
|
||||
}>();
|
||||
|
||||
const updateValue = ({ detail }: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
value.value = detail.value || '';
|
||||
emit('update:modelValue', { obj: whereCondition.value, value: detail.value as ConditionValue });
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<kuc-text
|
||||
v-if="valueType === 'kuc-text'"
|
||||
:value="modelValue.value"
|
||||
@change="updateValue"
|
||||
:className="needPlaceholderWidthClass"
|
||||
:placeholder="placeholder"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
/>
|
||||
<kuc-combobox
|
||||
v-else-if="valueType === 'kuc-combobox'"
|
||||
:value="modelValue.value"
|
||||
@change="updateValue"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
/>
|
||||
<kuc-time-picker
|
||||
v-else-if="valueType === 'kuc-time'"
|
||||
:value="modelValue.value"
|
||||
@change="updateValue"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
/>
|
||||
<table-condition-value-date-time
|
||||
v-else-if="valueType === 'datetime' || valueType === 'date'"
|
||||
:time="valueType === 'datetime'"
|
||||
:value="modelValue.value as string"
|
||||
@change="updateValue"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
/>
|
||||
<kuc-multi-choice
|
||||
v-else-if="valueType === 'kuc-multichoice'"
|
||||
:value="multiValue"
|
||||
:items="multiChoiceItems"
|
||||
:requiredIcon="true"
|
||||
@change="updateMultiValue"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
/>
|
||||
<table-condition-value-multi-input
|
||||
v-else-if="valueType === 'multi-input'"
|
||||
:value="multiInput"
|
||||
@change="updateTableValue"
|
||||
:disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getComponent } from '@/js/conditions';
|
||||
import { getFieldObj, isStringArray, search } from '@/js/helper';
|
||||
import { isType } from '@/js/kintone-rest-api-client';
|
||||
import { isSelectType } from '@/js/mapping';
|
||||
import type { CachedSelectedAppData, StringValue, WhereCondition } from '@/types/model';
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import type { ComboboxChangeEventDetail, TextInputEventDetail, MultiChoiceChangeEventDetail } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits, computed, type Ref, inject, provide, ref, watch, watchEffect } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Ref<StringValue>;
|
||||
selectedAppData: CachedSelectedAppData;
|
||||
whereConditions: WhereCondition[];
|
||||
id: string;
|
||||
canSave: (canSave: boolean) => void;
|
||||
}>();
|
||||
|
||||
provide('canSave', props.canSave);
|
||||
|
||||
const whereCondition = computed(() => search(props.whereConditions, props.id) as WhereCondition | undefined);
|
||||
|
||||
const needPlaceholderWidthClass = computed(() => (placeholder.value ? 'kuc-text-input-placeholder-width' : ''));
|
||||
|
||||
const placeholder = computed(() => {
|
||||
const field = getFieldObj(whereCondition.value?.field || '', props.selectedAppData.appFields, '');
|
||||
if (isType.GROUP_SELECT(field)) {
|
||||
return 'グループコードをカンマ区切りで指定';
|
||||
} else if (isType.ORGANIZATION_SELECT(field)) {
|
||||
return '組織コードをカンマ区切りで指定';
|
||||
} else if (isType.USER_SELECT(field) || isType.CREATOR(field) || isType.MODIFIER(field)) {
|
||||
return 'ログイン名をカンマ区切りで指定';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const multiChoiceItems = computed(() => {
|
||||
const field = getFieldObj(whereCondition.value?.field || '', props.selectedAppData.appFields, '');
|
||||
const items = [{
|
||||
label: '--',
|
||||
value: '',
|
||||
}];
|
||||
if (field && isSelectType(field)) {
|
||||
const opts = field.options;
|
||||
const multiOpts = Object.values(opts).map((opt) => {
|
||||
return {
|
||||
label: opt.label,
|
||||
value: opt.label,
|
||||
};
|
||||
});
|
||||
items.push(...multiOpts);
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
const valueType = computed(() => {
|
||||
const field = getFieldObj(whereCondition.value?.field || '', props.selectedAppData.appFields, '');
|
||||
return getComponent(whereCondition.value?.condition || '', field);
|
||||
});
|
||||
|
||||
type EmitData = {
|
||||
obj?: WhereCondition;
|
||||
value: StringValue;
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', data: EmitData): void;
|
||||
}>();
|
||||
|
||||
const updateValue = (event: KucEvent<ComboboxChangeEventDetail | TextInputEventDetail>) => {
|
||||
emit('update:modelValue', { obj: whereCondition.value, value: event.detail.value || '' });
|
||||
};
|
||||
|
||||
const multiValue = ref(isStringArray(props.modelValue.value) ? props.modelValue.value : []);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
const field = getFieldObj(whereCondition.value?.field || '', props.selectedAppData.appFields, '');
|
||||
const vType = valueType.value;
|
||||
const moduleValue = props.modelValue.value;
|
||||
if (field && isSelectType(field) && vType === 'kuc-multichoice') {
|
||||
multiValue.value = isStringArray(moduleValue) ? moduleValue : [];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const multiInput = ref(isStringArray(props.modelValue.value) ? (props.modelValue.value as string[]) : ['', '']);
|
||||
watchEffect(() => {
|
||||
const vType = valueType.value;
|
||||
const moduleValue = props.modelValue.value;
|
||||
if (vType === 'multi-input') {
|
||||
multiInput.value = isStringArray(moduleValue) ? (moduleValue as string[]) : ['', ''];
|
||||
}
|
||||
});
|
||||
|
||||
const updateMultiValue = (event: KucEvent<MultiChoiceChangeEventDetail>) => {
|
||||
emit('update:modelValue', { obj: whereCondition.value, value: event.detail.value || [] });
|
||||
};
|
||||
|
||||
const updateTableValue = (event: KucEvent<string[]>) => {
|
||||
let value = event.detail || ['', ''];
|
||||
multiInput.value = value;
|
||||
emit('update:modelValue', { obj: whereCondition.value, value: value });
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div style="display: flex; position: relative">
|
||||
<kuc-combobox
|
||||
:value="funcValue"
|
||||
:items="options"
|
||||
@change.stop="updateFuncValue"
|
||||
:disabled="disabled"
|
||||
:className="shortConditionClass"
|
||||
:key="time"
|
||||
/>
|
||||
|
||||
<template v-if="isInput()">
|
||||
<kuc-datetime-picker v-if="time" :value="inputValue" @change.stop="updateValue" :disabled="disabled" />
|
||||
<kuc-date-picker v-else :value="inputValue" @change.stop="updateValue" :disabled="disabled" />
|
||||
</template>
|
||||
<kuc-combobox
|
||||
v-else-if="isWeek()"
|
||||
:items="weekOptions"
|
||||
:value="selectValue"
|
||||
@change.stop="updateValue"
|
||||
:disabled="disabled"
|
||||
:className="weekClassName"
|
||||
/>
|
||||
<kuc-combobox
|
||||
v-else-if="isMonth()"
|
||||
:items="monthOptions"
|
||||
:value="selectValue"
|
||||
@change.stop="updateValue"
|
||||
:disabled="disabled"
|
||||
:className="monthClassName"
|
||||
/>
|
||||
|
||||
<template v-if="isFromToday()">
|
||||
<kuc-text
|
||||
:error="fromTodayError"
|
||||
:value="inputValue"
|
||||
@change.stop="updateFromTodayValue"
|
||||
:disabled="disabled"
|
||||
:className="fromTodayError ? 'from-today-input input error' : 'from-today-input input'"
|
||||
/>
|
||||
<kuc-combobox
|
||||
:items="fromOptions"
|
||||
:value="selectValue"
|
||||
@change.stop="updateFromTodaySelectValue"
|
||||
:disabled="disabled"
|
||||
className="from-today-input"
|
||||
/>
|
||||
<kuc-combobox
|
||||
:items="additionOptions"
|
||||
:value="additionSelectValue"
|
||||
@change.stop="updateFromTodayAdditionValue"
|
||||
:disabled="disabled"
|
||||
className="from-today-input addition"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { dateFuncMap, getDateFuncList, type DateFuncKey } from '@/js/conditions';
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import type { ComboboxChangeEventDetail } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits, computed, ref, watch, inject, type Ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
value: string;
|
||||
time: boolean;
|
||||
disabled: boolean;
|
||||
}>();
|
||||
|
||||
const canSave = inject<(canSave: boolean) => void>('canSave');
|
||||
|
||||
const inputValue = ref('');
|
||||
const selectValue = ref('');
|
||||
const additionSelectValue = ref('');
|
||||
const funcValue = ref('');
|
||||
|
||||
const fromTodayError = ref('');
|
||||
|
||||
watch(
|
||||
() => fromTodayError.value,
|
||||
() => {
|
||||
canSave?.(!fromTodayError.value);
|
||||
},
|
||||
);
|
||||
|
||||
const isInput = (func = funcValue.value) => func === dateFuncMap[''].value;
|
||||
const isWeek = (func = funcValue.value) => func.includes('WEEK');
|
||||
const isMonth = (func = funcValue.value) => func.includes('MONTH');
|
||||
const isFromToday = (func = funcValue.value) => func.includes('FROM_TODAY');
|
||||
|
||||
const weekClassName = computed(() => {
|
||||
if (isWeek()) {
|
||||
return selectValue.value === DEFAULT_WEEK_MONTH ? 'week-all-combobox' : 'week-combobox';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const monthClassName = computed(() => {
|
||||
if (isMonth()) {
|
||||
return selectValue.value === DEFAULT_WEEK_MONTH ? 'month-all-combobox' : 'month-combobox';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const shortConditionClass = computed(() => {
|
||||
const className = 'datetime-condition-combobox';
|
||||
if (isInput()) { return className }
|
||||
if (isFromToday() || funcValue.value === dateFuncMap['NOW'].value) {
|
||||
return className + ' mid';
|
||||
} else {
|
||||
return className + ' short';
|
||||
}
|
||||
});
|
||||
|
||||
const regex = /^(?<func>\w+)\((?<val>.*)\)$/;
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(current, before) => {
|
||||
if (props.value === '') {
|
||||
// select default one when empty
|
||||
funcValue.value = dateFuncMap[''].value;
|
||||
selectValue.value = '';
|
||||
inputValue.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const match = props.value.match(regex);
|
||||
|
||||
funcValue.value = dateFuncMap[(match?.groups?.func || '') as DateFuncKey].value;
|
||||
|
||||
const value = match?.groups?.val || (props.value.includes('%') ? '' : props.value);
|
||||
|
||||
// TODO set values is this method but isFromToday
|
||||
if (isInput()) {
|
||||
inputValue.value = value;
|
||||
selectValue.value = '';
|
||||
} else if (isWeek() || isMonth()) {
|
||||
inputValue.value = '';
|
||||
selectValue.value = value || DEFAULT_WEEK_MONTH;
|
||||
} else if (isFromToday() && !(before && isFromToday(before))) {
|
||||
// only called when first open page
|
||||
const split = value.split(', ');
|
||||
inputValue.value = split[0] === '0' ? '' : split[0].replace('-', '');
|
||||
selectValue.value = split[1] || fromOptions[0].value;
|
||||
additionSelectValue.value = split[0] ? (split[0].startsWith('-') ? '-' : '+') : '+';
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const options = computed(() => {
|
||||
return getDateFuncList(props.time).map((item) => {
|
||||
return { label: typeof item.label === 'function' ? item.label(props.time) : item.label, value: item.value };
|
||||
});
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', data: KucEvent<{ value: string }>): void;
|
||||
}>();
|
||||
|
||||
const updateValue = (event: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
emit('change', { detail: { value: buildResult({ value: event.detail.value }) } });
|
||||
};
|
||||
|
||||
const updateFuncValue = (event: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
selectValue.value = '';
|
||||
inputValue.value = '';
|
||||
emit('change', { detail: { value: buildResult({ func: event.detail.value }) } });
|
||||
};
|
||||
|
||||
const updateFromTodayValue = (event: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
const value = buildFromTodayResult({ value: event.detail.value });
|
||||
if (!value) return;
|
||||
inputValue.value = event.detail.value as string;
|
||||
emit('change', { detail: { value } });
|
||||
};
|
||||
|
||||
const updateFromTodaySelectValue = (event: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
const value = buildFromTodayResult({ select: event.detail.value });
|
||||
if (!value) return;
|
||||
selectValue.value = event.detail.value as string;
|
||||
emit('change', { detail: { value } });
|
||||
};
|
||||
|
||||
const updateFromTodayAdditionValue = (event: KucEvent<ComboboxChangeEventDetail>) => {
|
||||
const value = buildFromTodayResult({ addition: event.detail.value });
|
||||
if (!value) return;
|
||||
additionSelectValue.value = event.detail.value as string;
|
||||
emit('change', { detail: { value } });
|
||||
};
|
||||
|
||||
type FromTodayParam = {
|
||||
value?: string;
|
||||
select?: string;
|
||||
addition?: string;
|
||||
};
|
||||
|
||||
function buildFromTodayResult({
|
||||
value = inputValue.value || '0',
|
||||
select = selectValue.value || 'DAYS',
|
||||
addition = additionSelectValue.value || '-',
|
||||
}: FromTodayParam) {
|
||||
if (value?.match(/^-?[0-9]+$/)) {
|
||||
fromTodayError.value = '';
|
||||
let res = value;
|
||||
if (value && value !== '0' && addition === '-') {
|
||||
if (value.startsWith('-')) {
|
||||
res = res.replace('-', '');
|
||||
} else {
|
||||
res = '-' + res;
|
||||
}
|
||||
}
|
||||
return funcValue.value.replace('%s', select).replace('%d', res);
|
||||
} else if (!fromTodayError.value) {
|
||||
inputValue.value = value || '';
|
||||
fromTodayError.value = '整数値を指定してください。';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
type Param = {
|
||||
func?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
function buildResult({ func = funcValue.value, value }: Param) {
|
||||
let val = value || inputValue.value;
|
||||
if (isWeek(func) || isMonth(func)) {
|
||||
val = value || selectValue.value;
|
||||
val = val === DEFAULT_WEEK_MONTH ? '' : val;
|
||||
} else if (isFromToday(func)) {
|
||||
return func.replace('%d', val || '0').replace('%s', val || 'DAYS');
|
||||
}
|
||||
return func.replace('%s', val || '');
|
||||
}
|
||||
|
||||
const DEFAULT_WEEK_MONTH = '_';
|
||||
|
||||
const fromOptions = [
|
||||
{ label: '日', value: 'DAYS' },
|
||||
{ label: '周', value: 'WEEKS' },
|
||||
{ label: '月', value: 'MONTHS' },
|
||||
{ label: '年', value: 'YEARS' },
|
||||
];
|
||||
|
||||
const additionOptions = [
|
||||
{ label: '前', value: '-' },
|
||||
{ label: '後', value: '+' },
|
||||
];
|
||||
|
||||
const weekOptions = [
|
||||
{ label: 'すべての曜日', value: '_' },
|
||||
{ label: '日', value: 'SUNDAY' },
|
||||
{ label: '月', value: 'MONDAY' },
|
||||
{ label: '火', value: 'TUESDAY' },
|
||||
{ label: '水', value: 'WEDNESDAY' },
|
||||
{ label: '木', value: 'THURSDAY' },
|
||||
{ label: '金', value: 'FRIDAY' },
|
||||
{ label: '土', value: 'SATURDAY' },
|
||||
];
|
||||
|
||||
const monthOptions = [{ label: 'すべて', value: '_' }];
|
||||
for (let i = 1; i <= 31; i++) {
|
||||
monthOptions.push({ label: i.toString() + '日', value: i.toString() });
|
||||
}
|
||||
monthOptions.push({ label: '末日', value: 'LAST' });
|
||||
</script>
|
||||
@@ -0,0 +1,82 @@
|
||||
|
||||
<template>
|
||||
<!-- <kuc-table className='table-option'
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
@change="updateValue"
|
||||
:headerVisible="false"
|
||||
ref="table"/> -->
|
||||
<div ref="tableContainer"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { KucEvent } from '@/types/my-kintone';
|
||||
import { Table, Text, type TableChangeEventDetail } from 'kintone-ui-component';
|
||||
import { defineProps, defineEmits, computed, ref, watch, inject, type Ref, onMounted, onUnmounted } from 'vue';
|
||||
interface MuiltItem{
|
||||
value:string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
value: string[];
|
||||
}>();
|
||||
|
||||
const tableContainer = ref<HTMLDivElement>();
|
||||
const table = ref<Table | null>(null);
|
||||
const data = ref<MuiltItem[]>((props.value || ['', '']).map(x => ({ value: x })));
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue)=>{
|
||||
data.value =(newValue || ['', '']).map((x) => ({ value: x }));
|
||||
if (table.value) {
|
||||
table.value.data = data.value; // 更新 Table 数据
|
||||
}
|
||||
},
|
||||
{
|
||||
deep:true,immediate:true
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', data: KucEvent<string[]>): void;
|
||||
(e: 'update:modelValue', value: string[]): void;
|
||||
}>();
|
||||
|
||||
|
||||
const updateValue=(event:KucEvent<TableChangeEventDetail<MuiltItem>>)=>{
|
||||
data.value = event.detail.data||[{value:''},{value:''}];
|
||||
if (table.value) {
|
||||
table.value.data = data.value;
|
||||
}
|
||||
const muiltData = event.detail.data ? event.detail.data.map(x=>x.value) :[];
|
||||
emit('change', { detail: [...muiltData] });
|
||||
// emit('update:modelValue', [...muiltData] );
|
||||
// emit('change', muiltData);
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
table.value = new Table({
|
||||
className:'table-option',
|
||||
headerVisible:false,
|
||||
actionButton:true,
|
||||
columns:[
|
||||
{
|
||||
field:"value",
|
||||
render:(cellData:any)=>{
|
||||
const text = new Text({value:cellData});
|
||||
return text;
|
||||
}
|
||||
},
|
||||
],
|
||||
data:data.value
|
||||
});
|
||||
table.value.addEventListener('change', updateValue);
|
||||
tableContainer.value?.appendChild(table.value);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (table.value) {
|
||||
table.value.removeEventListener('change', updateValue);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
253
vue-project/data-fetch-plugin/src/css/config.css
Normal file
253
vue-project/data-fetch-plugin/src/css/config.css
Normal file
@@ -0,0 +1,253 @@
|
||||
/* 辅助类 */
|
||||
.flex-row {
|
||||
display: flex;
|
||||
}
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
.border {
|
||||
border: 1px solid #e3e7e8;
|
||||
}
|
||||
/* config 页面 */
|
||||
#app {
|
||||
width: 60vw;
|
||||
min-width: 1030px;
|
||||
}
|
||||
|
||||
/* 最上面的说明 */
|
||||
.settings-heading {
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
/* label 样式 */
|
||||
.kintoneplugin-label {
|
||||
padding-left: 20px;
|
||||
line-height: 40px;
|
||||
}
|
||||
/* laebl input 单行的情况 */
|
||||
.flex-row .kintoneplugin-label {
|
||||
margin: 0;
|
||||
width: 8.5em;
|
||||
}
|
||||
|
||||
/* 遮罩 */
|
||||
#main-area {
|
||||
position: relative;
|
||||
}
|
||||
#main-area .kuc-spinner-1-18-0__mask {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
}
|
||||
#main-area .kuc-spinner-1-18-0__spinner {
|
||||
position: absolute;
|
||||
}
|
||||
#main-area .kuc-spinner-1-18-0__spinner__loader {
|
||||
fill: #3498db;
|
||||
}
|
||||
|
||||
/* 表格内容垂直居中 */
|
||||
.table-area {
|
||||
margin: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 整体边框相关样式 */
|
||||
.header-row {
|
||||
padding: 24px 0;
|
||||
margin: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
.table-main-area {
|
||||
flex: 1;
|
||||
border-right: 1px solid #e3e7e8;
|
||||
padding-top: 24px;
|
||||
}
|
||||
.footer-row {
|
||||
padding: 24px 0;
|
||||
margin-bottom: 32px;
|
||||
text-align: right;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* 底部按钮空间 */
|
||||
.save-btn {
|
||||
margin-left: 16px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
/* 输入框宽度 */
|
||||
.kuc-text-input {
|
||||
--kuc-text-input-width: max(16vw, 200px);
|
||||
--kuc-dropdown-toggle-width: max(16vw, 200px);
|
||||
--kuc-combobox-toggle-width: max(16vw, 200px);
|
||||
}
|
||||
|
||||
.plugin-kuc-table .kuc-text-input {
|
||||
--kuc-text-input-width: max(15vw, 200px);
|
||||
--kuc-dropdown-toggle-width: max(15vw, 200px);
|
||||
--kuc-combobox-toggle-width: max(15vw, 200px);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1840px) {
|
||||
.plugin-kuc-table .kuc-text-input {
|
||||
--kuc-text-input-width: max(13vw, 200px);
|
||||
--kuc-dropdown-toggle-width: max(13vw, 200px);
|
||||
--kuc-combobox-toggle-width: max(13vw, 200px);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1760px) {
|
||||
.plugin-kuc-table .kuc-text-input {
|
||||
--kuc-text-input-width: max(12vw, 200px);
|
||||
--kuc-dropdown-toggle-width: max(12vw, 200px);
|
||||
--kuc-combobox-toggle-width: max(12vw, 200px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 统一 kintone +/- 按钮样式 */
|
||||
.kuc-action-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
margin: 0 4px;
|
||||
}
|
||||
.kuc-action-button.remove {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.kuc-action-button.add {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.kuc-action-button:focus {
|
||||
border: 1px solid #3498db;
|
||||
outline: none;
|
||||
}
|
||||
.kuc-action-button.remove:hover path {
|
||||
fill: #e74c3c;
|
||||
}
|
||||
|
||||
/* 覆盖表格样式 */
|
||||
.plugin-kuc-table > table > tbody > tr > td {
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
border-right-color: rgba(0, 0, 0, 0);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.plugin-kuc-table > table > tbody > tr > td:nth-last-child(2) {
|
||||
border-right-color: #e3e7e8;
|
||||
}
|
||||
.plugin-kuc-table > table > tbody > tr > td:first-child {
|
||||
border-left-color: #e3e7e8;
|
||||
}
|
||||
.plugin-kuc-table > table > tbody > tr > .kuc-table-1-18-0__table__body__row__action {
|
||||
height: 55px;
|
||||
align-items: center;
|
||||
}
|
||||
.plugin-kuc-table:not(.condition-table) > table > tbody > tr > td:nth-child(2) {
|
||||
--kuc-table-header-1-width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.condition-table > table > tbody > tr > td[style]:not(:first-child),
|
||||
.condition-table > table > thead > tr > th[style]:not(:first-child) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* 絞り込み条件选择相关样式 */
|
||||
.row-connector-area {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
.condition-combobox-short {
|
||||
--kuc-combobox-toggle-width: 168px;
|
||||
}
|
||||
/* .condition-combobox-short {
|
||||
--kuc-combobox-toggle-width: 140px
|
||||
}
|
||||
.condition-combobox-short[data-val='!='] {
|
||||
--kuc-combobox-toggle-width: 168px
|
||||
}
|
||||
.condition-combobox-short[data-val='like'] {
|
||||
--kuc-combobox-toggle-width: 200px
|
||||
}
|
||||
.condition-combobox-short[data-val='in'] {
|
||||
--kuc-combobox-toggle-width: 185px
|
||||
}
|
||||
.condition-combobox-short[data-val='not like'] {
|
||||
--kuc-combobox-toggle-width: 225px
|
||||
}
|
||||
.condition-combobox-short[data-val='not in'] {
|
||||
--kuc-combobox-toggle-width: 200px
|
||||
} */
|
||||
|
||||
.kuc-text-input-placeholder-width {
|
||||
--kuc-text-input-width: 258px;
|
||||
}
|
||||
|
||||
.datetime-condition-combobox {
|
||||
--kuc-combobox-toggle-width: 130px;
|
||||
}
|
||||
.datetime-condition-combobox.mid {
|
||||
--kuc-combobox-toggle-width: 112px;
|
||||
}
|
||||
.datetime-condition-combobox.short {
|
||||
--kuc-combobox-toggle-width: 92px;
|
||||
}
|
||||
.datetime-condition-combobox + * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.datetime-condition-combobox li[value^='\-'] {
|
||||
user-select: none;
|
||||
margin: 8px 0;
|
||||
cursor: default;
|
||||
padding: 0;
|
||||
height: 1px;
|
||||
background-color: #eee;
|
||||
list-style: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.week-all-combobox {
|
||||
--kuc-combobox-toggle-width: 140px;
|
||||
}
|
||||
.week-combobox {
|
||||
--kuc-combobox-toggle-width: 72px;
|
||||
}
|
||||
.month-all-combobox {
|
||||
--kuc-combobox-toggle-width: 100px;
|
||||
}
|
||||
.month-combobox {
|
||||
--kuc-combobox-toggle-width: 86px;
|
||||
}
|
||||
.from-today-input {
|
||||
--kuc-text-input-width: 75px;
|
||||
--kuc-combobox-toggle-width: 75px;
|
||||
}
|
||||
.from-today-input.input {
|
||||
--kuc-text-input-width: 50px;
|
||||
--kuc-combobox-toggle-width: 50px;
|
||||
}
|
||||
|
||||
/* .from-today-input error */
|
||||
.condition-table.plugin-kuc-table > table > tbody > tr > td {
|
||||
vertical-align: top;
|
||||
}
|
||||
.from-today-input.input div[class^="kuc-base-error"] {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.from-today-input.input.error {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.table-option td {
|
||||
padding: 1px;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
.dialog-action-bar{
|
||||
text-align: right;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
.plugin-space-heading {
|
||||
font-size: 1.5rem;
|
||||
margin: 0.8rem;
|
||||
}
|
||||
.plugin-space-message {
|
||||
display: inline-block;
|
||||
font-size: 1.2em;
|
||||
margin: 0.8rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.plugin-space-heading {
|
||||
font-size: 1.5rem;
|
||||
margin: 0.8rem;
|
||||
}
|
||||
6
vue-project/data-fetch-plugin/src/i18n/lang/en.ts
Normal file
6
vue-project/data-fetch-plugin/src/i18n/lang/en.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
config: {
|
||||
title: 'Data Fetch Plugin Settings',
|
||||
desc: 'Set the aggregation button name, data source app, fetch fields, filter conditions, and linking conditions, then save.',
|
||||
},
|
||||
};
|
||||
6
vue-project/data-fetch-plugin/src/i18n/lang/ja.ts
Normal file
6
vue-project/data-fetch-plugin/src/i18n/lang/ja.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
config: {
|
||||
title: 'データ取得プラグインの設定',
|
||||
desc: '集約ボタン名とデータ取得元アプリ、取得フィールド、絞込条件や連結条件を設定後、保存してください。',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,349 @@
|
||||
import type { FieldLayout, FieldsJoinMapping, JoinTable, Record, RecordForParameter, SavedData,StringValue, WhereCondition } from "@/types/model";
|
||||
import { type OneOf, isType } from "./field-types-mobile";
|
||||
import type { ConditionValue } from "./conditions";
|
||||
declare var KintoneRestAPIClient: typeof import("@kintone/rest-api-client").KintoneRestAPIClient;
|
||||
export class KintoneIndexEventHandler {
|
||||
private config: SavedData<FieldLayout>;
|
||||
private currentApp: string;
|
||||
constructor(config: SavedData<FieldLayout>, currentApp: string) {
|
||||
this.config = config;
|
||||
this.currentApp = currentApp;
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
this.addButtonToView();
|
||||
}
|
||||
|
||||
// ボタン追加
|
||||
private addButtonToView(): void {
|
||||
const headerSpace =kintone.mobile.app.getHeaderSpaceElement();
|
||||
if (!headerSpace) {
|
||||
throw new Error('このページではヘッダー要素が利用できません。');
|
||||
};
|
||||
|
||||
// ボタン追加
|
||||
if (document.getElementById('btn-data-fetch')) return;
|
||||
const kuc = Kucs['1.18.0'];
|
||||
const button = new kuc.MobileButton({
|
||||
text: this.config.buttonName,
|
||||
type: "submit",
|
||||
id: 'btn-data-fetch',
|
||||
});
|
||||
button.addEventListener('click', () => this.handleButtonClick());
|
||||
headerSpace.appendChild(button);
|
||||
}
|
||||
|
||||
// ボタンクリック
|
||||
private handleButtonClick = async (): Promise<void> => {
|
||||
const spinner = this.showSpinner();
|
||||
try {
|
||||
console.log('データ収集開始...');
|
||||
await this.execDataFectch();
|
||||
spinner.close();
|
||||
location.reload();
|
||||
} catch (error) {
|
||||
spinner.close();
|
||||
const detailError = (error instanceof Error) ? "\n詳細:" + error.message : "";
|
||||
const errorMsg = `データ収集中処理中例外発生しました。${detailError}`;
|
||||
console.error(errorMsg, error);
|
||||
window.alert(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private showSpinner() {
|
||||
const kuc = Kucs['1.18.0'];
|
||||
const spinner = new kuc.Spinner({
|
||||
text: 'データ収集中',
|
||||
container: document.body
|
||||
});
|
||||
spinner.open();
|
||||
return spinner;
|
||||
}
|
||||
|
||||
/**
|
||||
* 検索データ取得&作成処理
|
||||
*/
|
||||
private execDataFectch = async (): Promise<void> => {
|
||||
const mainTable = this.config.joinTables[0];
|
||||
const mainRecords = await this.fetchDataFromApp(mainTable);
|
||||
// フィールド結合
|
||||
let mainData = mainRecords.map((record) => {
|
||||
const rightRecord: Record = {};
|
||||
mainTable.fieldsMapping.forEach((mapping => {
|
||||
rightRecord[this.fieldCode(mapping.rightField)] = record[this.fieldCode(mapping.leftField)]
|
||||
}));
|
||||
return rightRecord;
|
||||
});
|
||||
const joinTables = this.config.joinTables.filter((table, index) => index > 0);
|
||||
for (const table of joinTables) {
|
||||
const subDatas = await this.fetchDataFromApp(table);
|
||||
mainData = this.leftJoin(mainData, subDatas, table);
|
||||
// console.log("LeftJoin", mainData);
|
||||
};
|
||||
//現在のデータをクリアする
|
||||
await this.deleteCurrentRecords();
|
||||
//データを更新する
|
||||
await this.saveDataToCurrentApp(mainData);
|
||||
}
|
||||
/**
|
||||
* Appからデータを取得する
|
||||
* @param joinTable 対象アプリかられレコードを取得する
|
||||
* @returns
|
||||
*/
|
||||
private fetchDataFromApp = async (joinTable: JoinTable<FieldLayout>): Promise<Record[]> => {
|
||||
// Filter 条件作成
|
||||
const filter = this.getWhereCondition(joinTable.whereConditions);
|
||||
//取得列を設定する
|
||||
const fetchFields = joinTable.fieldsMapping.map(map => this.fieldCode(map.leftField));
|
||||
if (joinTable.table) {
|
||||
fetchFields.push(joinTable.table);
|
||||
}
|
||||
const onFields =joinTable.onConditions.map(cond=>this.fieldCode(cond.leftField));
|
||||
onFields.forEach(fld=>{
|
||||
if(!fetchFields.includes(fld)){
|
||||
fetchFields.push(fld);
|
||||
}
|
||||
});
|
||||
// KintoneRESTAPI
|
||||
const client = new KintoneRestAPIClient();
|
||||
const records = await client.record.getAllRecords({
|
||||
app: joinTable.app,
|
||||
fields: fetchFields,
|
||||
condition: filter
|
||||
});
|
||||
//console.log("Data Fetch", records);
|
||||
//SubTableが含まれる場合、フラットなデータに変換する
|
||||
return this.convertToFlatDatas(records, joinTable.table);
|
||||
|
||||
}
|
||||
/**
|
||||
* 絞り込み条件式作成
|
||||
* @param whereCondifions
|
||||
* @returns
|
||||
*/
|
||||
private getWhereCondition(whereCondifions: WhereCondition<FieldLayout>[]): string {
|
||||
const conds = whereCondifions
|
||||
.filter((cond) => this.fieldCode(cond.field) !== '');
|
||||
const condition = conds.map((cond) => {
|
||||
let condition = cond.condition;
|
||||
if ("subField" in cond.field && cond.field.subField) {
|
||||
condition = this.mapConditionForSubField(cond.condition);
|
||||
}
|
||||
const condValue = this.getConditionValue(cond.field as OneOf, condition, cond.data);
|
||||
return `${this.fieldCode(cond.field)} ${condition} ${condValue}`;
|
||||
}).join(' and ');
|
||||
return condition;
|
||||
}
|
||||
/**
|
||||
* サブフィールドの演算子対応
|
||||
* @param condition
|
||||
* @returns
|
||||
*/
|
||||
private mapConditionForSubField(condition: ConditionValue): ConditionValue {
|
||||
switch (condition) {
|
||||
case "=":
|
||||
return "in";
|
||||
case "!=":
|
||||
return "not in";
|
||||
default:
|
||||
return condition; // 既存の条件をそのまま使用
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 条件比較値を変換する
|
||||
* @param field
|
||||
* @param condition
|
||||
* @param value
|
||||
* @returns
|
||||
*/
|
||||
private getConditionValue(field: OneOf, condition: ConditionValue, value: StringValue): string {
|
||||
if (!value) return "\"\"";
|
||||
if(this.isStringArray(value)){
|
||||
//マルチデータの場合
|
||||
const items = (value as string[]).map(item => `"${item.trim()}"`);
|
||||
return `(${items.join(",")})`;
|
||||
}
|
||||
const data = value as string;
|
||||
if (isType.NUMBER(field) || isType.RECORD_NUMBER(field)) {
|
||||
// For numbers, return as is
|
||||
return data;
|
||||
} else if (isType.DATE(field)) {
|
||||
// If field is DATE, format as "yyyy-MM-dd" unless it's a reserved function
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(data)){
|
||||
return `"${data}"`;
|
||||
}else if(data.match(/^\w+\(.*\)$/)){
|
||||
return data;
|
||||
}
|
||||
const date = new Date(data);
|
||||
return `"${date.toISOString().split('T')[0]}"`;
|
||||
} else if (isType.DATETIME(field) || isType.CREATED_TIME(field) || isType.UPDATED_TIME(field)) {
|
||||
// 関数を使用する場合
|
||||
if (data.match(/^\w+\(.*\)$/)) {
|
||||
return data;
|
||||
}
|
||||
const dateTime = new Date(data);
|
||||
return `"${dateTime.toISOString()}"`;
|
||||
} else if ((condition === "in" || condition === "not in")) {
|
||||
if (data.includes(",")) {
|
||||
// Handle "in" and "not in" with comma-separated strings
|
||||
const items = data.split(",").map(item => `"${item.trim()}"`);
|
||||
return `(${items.join(",")})`;
|
||||
} else {
|
||||
return `("${data}")`;
|
||||
}
|
||||
} else {
|
||||
// Default case for other types (treat as text)
|
||||
return `"${data}"`;
|
||||
}
|
||||
}
|
||||
|
||||
private isStringArray=(value:any)=>{
|
||||
if(Array.isArray(value) && value.every(x=>typeof x ==='string')){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* fieldからコードを取得する
|
||||
* @param field
|
||||
* @returns
|
||||
*/
|
||||
private fieldCode(field: any): string {
|
||||
if (!field) {
|
||||
return "";
|
||||
}
|
||||
if (typeof field === 'string' && field) {
|
||||
return field;
|
||||
} else if (typeof field === 'object' && 'code' in field) {
|
||||
return field.code;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* ネストされたサブテーブルデータをフラットなデータに変換し、親レコードを複製します。
|
||||
* @param records レコードの配列
|
||||
* @returns 変換後のフラットなレコード配列
|
||||
*/
|
||||
private convertToFlatDatas(records: Record[], subTable: string): Record[] {
|
||||
if (!subTable) {
|
||||
return records;
|
||||
}
|
||||
const flattenedData: Record[] = [];
|
||||
records.forEach((record) => {
|
||||
// テーブルフィールドが存在するかを確認
|
||||
if (record[subTable]?.type === "SUBTABLE" && record[subTable].value.length > 0) {
|
||||
// サブテーブル内の各レコードを処理
|
||||
record[subTable].value.forEach((nested: Record) => {
|
||||
// 親レコードのコピーを作成
|
||||
const flatRecord = { ...record };
|
||||
|
||||
// サブテーブルフィールドを抽出してフラットな構造に追加
|
||||
Object.entries(nested.value).forEach(([key, field]) => {
|
||||
flatRecord[key] = { value: field.value, type: field.type };
|
||||
});
|
||||
|
||||
// テーブルフィールドを削除
|
||||
delete flatRecord[subTable];
|
||||
|
||||
// 結果の配列に追加
|
||||
flattenedData.push(flatRecord);
|
||||
});
|
||||
} else {
|
||||
// サブテーブルが空の場合、親レコードをそのまま追加
|
||||
const flatRecord = { ...record };
|
||||
delete flatRecord[subTable];
|
||||
flattenedData.push(flatRecord);
|
||||
}
|
||||
});
|
||||
// console.log("FlatDatas=>", flattenedData);
|
||||
return flattenedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* データLeftJoin処理
|
||||
* @param mainData
|
||||
* @param subData
|
||||
* @param onConditions
|
||||
* @param fieldsMapping
|
||||
* @returns
|
||||
*/
|
||||
private leftJoin(
|
||||
mainData: Record[],
|
||||
subData: Record[],
|
||||
joinTable: JoinTable<FieldLayout>
|
||||
): Record[] {
|
||||
const joinedRecords: Record[] = [];
|
||||
mainData.forEach((mainRecord) => {
|
||||
const matchedRecords = subData.filter((subRecord) =>
|
||||
joinTable.onConditions.every(
|
||||
(cond) => mainRecord[this.fieldCode(cond.rightField)]?.value === subRecord[this.fieldCode(cond.leftField)]?.value
|
||||
)
|
||||
);
|
||||
|
||||
// マッチ出来ない場合、LEFTの列のみ返す
|
||||
if (!matchedRecords || matchedRecords.length==0) {
|
||||
joinedRecords.push(mainRecord);
|
||||
} else {
|
||||
matchedRecords.forEach((matchedRecord) => {
|
||||
// フィールド結合
|
||||
const combinedRecord: Record = { ...mainRecord };
|
||||
joinTable.fieldsMapping.forEach((mapping) => {
|
||||
combinedRecord[this.fieldCode(mapping.rightField)] = matchedRecord[this.fieldCode(mapping.leftField)];
|
||||
});
|
||||
joinedRecords.push(combinedRecord);
|
||||
});
|
||||
}
|
||||
});
|
||||
return joinedRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 現在アプリのすべてレコードを削除する
|
||||
*/
|
||||
private async deleteCurrentRecords(): Promise<void> {
|
||||
const client = new KintoneRestAPIClient();
|
||||
const currentRecords = await client.record.getAllRecords({
|
||||
app: this.currentApp,
|
||||
fields: ["$id"],
|
||||
});
|
||||
const deleteRecords = currentRecords.map(record => {
|
||||
return { id: record.$id.value as string }
|
||||
});
|
||||
await client.record.deleteAllRecords({
|
||||
app: this.currentApp,
|
||||
records: deleteRecords
|
||||
});
|
||||
client.record.addAllRecords
|
||||
}
|
||||
/**
|
||||
* 結合後のデータを現在のアプリに挿入する
|
||||
* @param records
|
||||
*/
|
||||
private async saveDataToCurrentApp(records: Record[]): Promise<void> {
|
||||
try {
|
||||
const client = new KintoneRestAPIClient();
|
||||
const result = await client.record.addAllRecords({
|
||||
app: this.currentApp,
|
||||
records: this.convertForUpdate(records)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('データ作成時エラーが発生しました:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recordを更新時の形式を変換する
|
||||
* @param resords
|
||||
* @returns
|
||||
*/
|
||||
private convertForUpdate(resords: Record[]): RecordForParameter[] {
|
||||
return resords.map((record) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(record).map(([fieldCode, { value }]) => [fieldCode, { value }])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
342
vue-project/data-fetch-plugin/src/js/KintoneIndexEventHandler.ts
Normal file
342
vue-project/data-fetch-plugin/src/js/KintoneIndexEventHandler.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
import type { FieldLayout, FieldsJoinMapping, JoinTable, Record, RecordForParameter, SavedData, StringValue,WhereCondition } from "@/types/model";
|
||||
import { type OneOf, isType } from "./field-types";
|
||||
import type { ConditionValue } from "./conditions";
|
||||
declare var KintoneRestAPIClient: typeof import("@kintone/rest-api-client").KintoneRestAPIClient;
|
||||
export class KintoneIndexEventHandler {
|
||||
private config: SavedData<FieldLayout>;
|
||||
private currentApp: string;
|
||||
constructor(config: SavedData<FieldLayout>, currentApp: string) {
|
||||
this.config = config;
|
||||
this.currentApp = currentApp;
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
this.addButtonToView();
|
||||
}
|
||||
|
||||
// ボタン追加
|
||||
private addButtonToView(): void {
|
||||
const headerSpace = kintone.app.getHeaderMenuSpaceElement();
|
||||
if (!headerSpace) {
|
||||
throw new Error('このページではヘッダー要素が利用できません。');
|
||||
};
|
||||
|
||||
// ボタン追加
|
||||
if (document.getElementById('btn-data-fetch')) return;
|
||||
const kuc = Kucs['1.18.0'];
|
||||
const button = new kuc.Button({
|
||||
text: this.config.buttonName,
|
||||
type: "submit",
|
||||
id: 'btn-data-fetch',
|
||||
});
|
||||
button.addEventListener('click', () => this.handleButtonClick());
|
||||
headerSpace.appendChild(button);
|
||||
}
|
||||
|
||||
// ボタンクリック
|
||||
private handleButtonClick = async (): Promise<void> => {
|
||||
const spinner = this.showSpinner();
|
||||
try {
|
||||
console.log('データ収集開始...');
|
||||
await this.execDataFectch();
|
||||
spinner.close();
|
||||
location.reload();
|
||||
} catch (error) {
|
||||
spinner.close();
|
||||
const detailError = (error instanceof Error) ? "\n詳細:" + error.message : "";
|
||||
const errorMsg = `データ収集中処理中例外発生しました。${detailError}`;
|
||||
console.error(errorMsg, error);
|
||||
window.alert(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private showSpinner() {
|
||||
const kuc = Kucs['1.18.0'];
|
||||
const spinner = new kuc.Spinner({
|
||||
text: 'データ収集中',
|
||||
container: document.body
|
||||
});
|
||||
spinner.open();
|
||||
return spinner;
|
||||
}
|
||||
|
||||
/**
|
||||
* 検索データ取得&作成処理
|
||||
*/
|
||||
private execDataFectch = async (): Promise<void> => {
|
||||
const mainTable = this.config.joinTables[0];
|
||||
const mainRecords = await this.fetchDataFromApp(mainTable);
|
||||
// フィールド結合
|
||||
let mainData = mainRecords.map((record) => {
|
||||
const rightRecord: Record = {};
|
||||
mainTable.fieldsMapping.forEach((mapping => {
|
||||
rightRecord[this.fieldCode(mapping.rightField)] = record[this.fieldCode(mapping.leftField)]
|
||||
}));
|
||||
return rightRecord;
|
||||
});
|
||||
const joinTables = this.config.joinTables.filter((table, index) => index > 0);
|
||||
for (const table of joinTables) {
|
||||
const subDatas = await this.fetchDataFromApp(table);
|
||||
mainData = this.leftJoin(mainData, subDatas, table);
|
||||
// console.log("LeftJoin", mainData);
|
||||
};
|
||||
//現在のデータをクリアする
|
||||
await this.deleteCurrentRecords();
|
||||
//データを更新する
|
||||
await this.saveDataToCurrentApp(mainData);
|
||||
}
|
||||
/**
|
||||
* Appからデータを取得する
|
||||
* @param joinTable 対象アプリかられレコードを取得する
|
||||
* @returns
|
||||
*/
|
||||
private fetchDataFromApp = async (joinTable: JoinTable<FieldLayout>): Promise<Record[]> => {
|
||||
// Filter 条件作成
|
||||
const filter = this.getWhereCondition(joinTable.whereConditions);
|
||||
//取得列を設定する
|
||||
const fetchFields = joinTable.fieldsMapping.map(map => this.fieldCode(map.leftField));
|
||||
if (joinTable.table) {
|
||||
fetchFields.push(joinTable.table);
|
||||
}
|
||||
const onFields =joinTable.onConditions.map(cond=>this.fieldCode(cond.leftField));
|
||||
onFields.forEach(fld=>{
|
||||
if(!fetchFields.includes(fld)){
|
||||
fetchFields.push(fld);
|
||||
}
|
||||
});
|
||||
// KintoneRESTAPI
|
||||
const client = new KintoneRestAPIClient();
|
||||
const records = await client.record.getAllRecords({
|
||||
app: joinTable.app,
|
||||
fields: fetchFields,
|
||||
condition: filter
|
||||
});
|
||||
//console.log("Data Fetch", records);
|
||||
//SubTableが含まれる場合、フラットなデータに変換する
|
||||
return this.convertToFlatDatas(records, joinTable.table);
|
||||
|
||||
}
|
||||
/**
|
||||
* 絞り込み条件式作成
|
||||
* @param whereCondifions
|
||||
* @returns
|
||||
*/
|
||||
private getWhereCondition(whereCondifions: WhereCondition<FieldLayout>[]): string {
|
||||
const conds = whereCondifions
|
||||
.filter((cond) => this.fieldCode(cond.field) !== '');
|
||||
const condition = conds.map((cond) => {
|
||||
let condition = cond.condition;
|
||||
if ("subField" in cond.field && cond.field.subField) {
|
||||
condition = this.mapConditionForSubField(cond.condition);
|
||||
}
|
||||
const condValue = this.getConditionValue(cond.field as OneOf, condition, cond.data);
|
||||
return `${this.fieldCode(cond.field)} ${condition} ${condValue}`;
|
||||
}).join(' and ');
|
||||
return condition;
|
||||
}
|
||||
/**
|
||||
* サブフィールドの演算子対応
|
||||
* @param condition
|
||||
* @returns
|
||||
*/
|
||||
private mapConditionForSubField(condition: ConditionValue): ConditionValue {
|
||||
switch (condition) {
|
||||
case "=":
|
||||
return "in";
|
||||
case "!=":
|
||||
return "not in";
|
||||
default:
|
||||
return condition; // 既存の条件をそのまま使用
|
||||
}
|
||||
}
|
||||
private getConditionValue(field: OneOf, condition: ConditionValue, value: StringValue): string {
|
||||
if (!value) return "\"\"";
|
||||
if(this.isStringArray(value)){
|
||||
//マルチデータの場合
|
||||
const items = (value as string[]).map(item => `"${item.trim()}"`);
|
||||
return `(${items.join(",")})`;
|
||||
}
|
||||
const data = value as string;
|
||||
if (isType.NUMBER(field) || isType.RECORD_NUMBER(field)) {
|
||||
// For numbers, return as is
|
||||
return data;
|
||||
} else if (isType.DATE(field)) {
|
||||
// If field is DATE, format as "yyyy-MM-dd" unless it's a reserved function
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(data)){
|
||||
return `"${data}"`;
|
||||
}else if(data.match(/^\w+\(.*\)$/)){
|
||||
return data;
|
||||
}
|
||||
const date = new Date(data);
|
||||
return `"${date.toISOString().split('T')[0]}"`;
|
||||
} else if (isType.DATETIME(field) || isType.CREATED_TIME(field) || isType.UPDATED_TIME(field)) {
|
||||
// If field is DATETIME, format as "yyyy-MM-ddTHH:mm:ssZ"
|
||||
if (data.match(/^\w+\(.*\)$/)) {
|
||||
return data;
|
||||
}
|
||||
const dateTime = new Date(data);
|
||||
return `"${dateTime.toISOString()}"`;
|
||||
} else if ((condition === "in" || condition === "not in")) {
|
||||
if (data.includes(",")) {
|
||||
// Handle "in" and "not in" with comma-separated strings
|
||||
const items = data.split(",").map(item => `"${item.trim()}"`);
|
||||
return `(${items.join(",")})`;
|
||||
} else {
|
||||
return `("${data}")`;
|
||||
}
|
||||
} else {
|
||||
// Default case for other types (treat as text)
|
||||
return `"${data}"`;
|
||||
}
|
||||
}
|
||||
|
||||
private isStringArray=(value:any)=>{
|
||||
if(Array.isArray(value) && value.every(x=>typeof x ==='string')){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* fieldからコードを取得する
|
||||
* @param field
|
||||
* @returns
|
||||
*/
|
||||
private fieldCode(field: any): string {
|
||||
if (!field) {
|
||||
return "";
|
||||
}
|
||||
if (typeof field === 'string' && field) {
|
||||
return field;
|
||||
} else if (typeof field === 'object' && 'code' in field) {
|
||||
return field.code;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* ネストされたサブテーブルデータをフラットなデータに変換し、親レコードを複製します。
|
||||
* @param records レコードの配列
|
||||
* @returns 変換後のフラットなレコード配列
|
||||
*/
|
||||
private convertToFlatDatas(records: Record[], subTable: string): Record[] {
|
||||
if (!subTable) {
|
||||
return records;
|
||||
}
|
||||
const flattenedData: Record[] = [];
|
||||
records.forEach((record) => {
|
||||
// テーブルフィールドが存在するかを確認
|
||||
if (record[subTable]?.type === "SUBTABLE" && record[subTable].value.length > 0) {
|
||||
// サブテーブル内の各レコードを処理
|
||||
record[subTable].value.forEach((nested: Record) => {
|
||||
// 親レコードのコピーを作成
|
||||
const flatRecord = { ...record };
|
||||
|
||||
// サブテーブルフィールドを抽出してフラットな構造に追加
|
||||
Object.entries(nested.value).forEach(([key, field]) => {
|
||||
flatRecord[key] = { value: field.value, type: field.type };
|
||||
});
|
||||
|
||||
// テーブルフィールドを削除
|
||||
delete flatRecord[subTable];
|
||||
|
||||
// 結果の配列に追加
|
||||
flattenedData.push(flatRecord);
|
||||
});
|
||||
} else {
|
||||
// サブテーブルが空の場合、親レコードをそのまま追加
|
||||
const flatRecord = { ...record };
|
||||
delete flatRecord[subTable];
|
||||
flattenedData.push(flatRecord);
|
||||
}
|
||||
});
|
||||
// console.log("FlatDatas=>", flattenedData);
|
||||
return flattenedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* データLeftJoin処理
|
||||
* @param mainData
|
||||
* @param subData
|
||||
* @param onConditions
|
||||
* @param fieldsMapping
|
||||
* @returns
|
||||
*/
|
||||
private leftJoin(
|
||||
mainData: Record[],
|
||||
subData: Record[],
|
||||
joinTable: JoinTable<FieldLayout>
|
||||
): Record[] {
|
||||
const joinedRecords: Record[] = [];
|
||||
mainData.forEach((mainRecord) => {
|
||||
const matchedRecords = subData.filter((subRecord) =>
|
||||
joinTable.onConditions.every(
|
||||
(cond) => mainRecord[this.fieldCode(cond.rightField)]?.value === subRecord[this.fieldCode(cond.leftField)]?.value
|
||||
)
|
||||
);
|
||||
|
||||
// マッチ出来ない場合、LEFTの列のみ返す
|
||||
if (!matchedRecords || matchedRecords.length==0) {
|
||||
joinedRecords.push(mainRecord);
|
||||
} else {
|
||||
matchedRecords.forEach((matchedRecord) => {
|
||||
// フィールド結合
|
||||
const combinedRecord: Record = { ...mainRecord };
|
||||
joinTable.fieldsMapping.forEach((mapping) => {
|
||||
combinedRecord[this.fieldCode(mapping.rightField)] = matchedRecord[this.fieldCode(mapping.leftField)];
|
||||
});
|
||||
joinedRecords.push(combinedRecord);
|
||||
});
|
||||
}
|
||||
});
|
||||
return joinedRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 現在アプリのすべてレコードを削除する
|
||||
*/
|
||||
private async deleteCurrentRecords(): Promise<void> {
|
||||
const client = new KintoneRestAPIClient();
|
||||
const currentRecords = await client.record.getAllRecords({
|
||||
app: this.currentApp,
|
||||
fields: ["$id"],
|
||||
});
|
||||
const deleteRecords = currentRecords.map(record => {
|
||||
return { id: record.$id.value as string }
|
||||
});
|
||||
await client.record.deleteAllRecords({
|
||||
app: this.currentApp,
|
||||
records: deleteRecords
|
||||
});
|
||||
client.record.addAllRecords
|
||||
}
|
||||
/**
|
||||
* 結合後のデータを現在のアプリに挿入する
|
||||
* @param records
|
||||
*/
|
||||
private async saveDataToCurrentApp(records: Record[]): Promise<void> {
|
||||
try {
|
||||
const client = new KintoneRestAPIClient();
|
||||
const result = await client.record.addAllRecords({
|
||||
app: this.currentApp,
|
||||
records: this.convertForUpdate(records)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('データ作成時エラーが発生しました:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recordを更新時の形式を変換する
|
||||
* @param resords
|
||||
* @returns
|
||||
*/
|
||||
private convertForUpdate(resords: Record[]): RecordForParameter[] {
|
||||
return resords.map((record) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(record).map(([fieldCode, { value }]) => [fieldCode, { value }])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
196
vue-project/data-fetch-plugin/src/js/conditions.ts
Normal file
196
vue-project/data-fetch-plugin/src/js/conditions.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import type { FieldsInfo } from '@/types/model';
|
||||
import { isType, type FieldType, type OneOf } from './kintone-rest-api-client';
|
||||
import { getFieldObj } from './helper';
|
||||
|
||||
// conditionValue = '' | 'eq' | 'ne'
|
||||
// conditionItem = { value: 'eq', label: '=(等しい)', type: 'input', func: (a: string, b: string) => a === b }
|
||||
// = conditionMap[conditionValue]
|
||||
export type ConditionValue = '' | '=' | '!=' | '>=' | '<=' | '<' | '>' | 'like' | 'not like' | 'in' | 'not in';
|
||||
|
||||
type ConditionItem = {
|
||||
value: ConditionValue;
|
||||
label: string | ((field: OneOf) => string);
|
||||
type: ComponentType | ((field: OneOf) => ComponentType);
|
||||
};
|
||||
|
||||
export const conditionList: ConditionItem[] = [
|
||||
{ value: '=', label: '=(等しい)', type: (field) => dateTimeComponent[field.type] || 'input' },
|
||||
{ value: '!=', label: '≠ (等しくない)', type: (field) => dateTimeComponent[field.type] || 'input' },
|
||||
{
|
||||
value: '<=',
|
||||
label: (field) => (isDateTimeType(field) ? '≦ (以前)' : '≦ (以下)'),
|
||||
type: (field) => dateTimeComponent[field.type] || 'input',
|
||||
},
|
||||
{ value: '<', label: '< (より前)', type: (field) => dateTimeComponent[field.type] || 'input' },
|
||||
{
|
||||
value: '>=',
|
||||
label: (field) => (isDateTimeType(field) ? '≧ (以降)' : '≧ (以上)'),
|
||||
type: (field) => dateTimeComponent[field.type] || 'input',
|
||||
},
|
||||
{ value: '>', label: '> (より後)', type: (field) => dateTimeComponent[field.type] || 'input' },
|
||||
{ value: 'like', label: '次のキーワードを含む', type: 'input' },
|
||||
{ value: 'not like', label: '次のキーワードを含まない', type: 'input' },
|
||||
{ value: 'in', label: '次のいずれかを含む', type: (field) => MultiChoiceComponent[field.type] || 'input' },
|
||||
{ value: 'not in', label: '次のいずれも含まない', type: (field) => MultiChoiceComponent[field.type] || 'input' },
|
||||
];
|
||||
|
||||
// search from conditionList
|
||||
// conditionItem = conditionMap[conditionValue]
|
||||
export const conditionMap: Record<ConditionValue, ConditionItem> = conditionList.reduce(
|
||||
(map, item) => {
|
||||
map[item.value] = item;
|
||||
return map;
|
||||
},
|
||||
{} as Record<ConditionValue, ConditionItem>,
|
||||
);
|
||||
|
||||
type FieldConditions = Partial<Record<FieldType, ConditionValue[]>>;
|
||||
const textCondition: ConditionValue[] = ['=', '!=', 'in', 'like', 'not like'];
|
||||
const numberCondition: ConditionValue[] = ['=', '!=', '<=', '>='];
|
||||
const timeCondition: ConditionValue[] = ['=', '!=', '<=', '>=', '<', '>'];
|
||||
const containsCondition: ConditionValue[] = ['in', 'not in'];
|
||||
|
||||
// FieldType -> ConditionValue[]
|
||||
const fieldConditions: FieldConditions = {
|
||||
SINGLE_LINE_TEXT: textCondition,
|
||||
MULTI_LINE_TEXT: containsCondition,
|
||||
RICH_TEXT: containsCondition,
|
||||
NUMBER: numberCondition,
|
||||
CHECK_BOX: containsCondition,
|
||||
RADIO_BUTTON: containsCondition,
|
||||
DROP_DOWN: containsCondition,
|
||||
MULTI_SELECT: containsCondition,
|
||||
USER_SELECT: containsCondition,
|
||||
ORGANIZATION_SELECT: containsCondition,
|
||||
GROUP_SELECT: containsCondition,
|
||||
LINK: textCondition,
|
||||
CALC: numberCondition,
|
||||
|
||||
TIME: timeCondition,
|
||||
DATE: timeCondition,
|
||||
DATETIME: timeCondition,
|
||||
CREATED_TIME: timeCondition,
|
||||
CREATOR: containsCondition,
|
||||
UPDATED_TIME: timeCondition,
|
||||
MODIFIER: containsCondition,
|
||||
RECORD_NUMBER: numberCondition,
|
||||
} as const;
|
||||
|
||||
// fieldCode -> conditionList: ConditionItem[]
|
||||
export const getAvailableCondition = (fieldCode: string, fieldsInfo: FieldsInfo, subTableCode: string | '') => {
|
||||
if (!fieldCode || !fieldsInfo.fields) return;
|
||||
const fieldObj = getFieldObj(fieldCode, fieldsInfo, '');
|
||||
if (!fieldObj) return;
|
||||
const conditions = fieldConditions[fieldObj.type] || textCondition; // TODO a fallback here
|
||||
return conditions.map((condition) => {
|
||||
const res = { ...conditionMap[condition] };
|
||||
res.label = typeof res.label === 'function' ? res.label(fieldObj) : res.label;
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
const component = {
|
||||
input: 'kuc-text',
|
||||
select: 'kuc-combobox',
|
||||
time: 'kuc-time',
|
||||
date: 'date',
|
||||
datetime: 'datetime',
|
||||
multiChoice: 'kuc-multichoice',
|
||||
multiInput: 'multi-input',
|
||||
};
|
||||
|
||||
export const isDateTimeType = (field: OneOf) => {
|
||||
return field.type in dateTimeComponent;
|
||||
};
|
||||
|
||||
const dateTimeComponent: Partial<Record<FieldType, ComponentType>> = {
|
||||
TIME: 'time',
|
||||
DATE: 'date',
|
||||
DATETIME: 'datetime',
|
||||
CREATED_TIME: 'datetime',
|
||||
UPDATED_TIME: 'datetime',
|
||||
};
|
||||
|
||||
const MultiChoiceComponent: Partial<Record<FieldType, ComponentType>> = {
|
||||
CHECK_BOX: 'multiChoice',
|
||||
DROP_DOWN: 'multiChoice',
|
||||
RADIO_BUTTON: 'multiChoice',
|
||||
MULTI_SELECT: 'multiChoice',
|
||||
SINGLE_LINE_TEXT: 'multiInput',
|
||||
LINK: 'multiInput',
|
||||
};
|
||||
|
||||
export type ComponentType = keyof typeof component;
|
||||
export const getComponent = (value: ConditionValue, fieldObj: OneOf) => {
|
||||
if (!value || !fieldObj) return;
|
||||
const condition = conditionMap[value].type;
|
||||
return component[typeof condition === 'function' ? condition(fieldObj) : condition];
|
||||
};
|
||||
|
||||
type DateFuncItem = {
|
||||
value: string;
|
||||
label: string | ((isTime: boolean) => string);
|
||||
condition?: 'datetime' | 'date';
|
||||
key: DateFuncKey;
|
||||
};
|
||||
|
||||
export type DateFuncKey =
|
||||
| ''
|
||||
| 'FROM_TODAY'
|
||||
| '---NOW---'
|
||||
| 'NOW'
|
||||
| '---DAY---'
|
||||
| 'YESTERDAY'
|
||||
| 'TODAY'
|
||||
| 'TOMORROW'
|
||||
| '---WEEK---'
|
||||
| 'LAST_WEEK'
|
||||
| 'THIS_WEEK'
|
||||
| 'NEXT_WEEK'
|
||||
| '---MONTH---'
|
||||
| 'LAST_MONTH'
|
||||
| 'THIS_MONTH'
|
||||
| 'NEXT_MONTH'
|
||||
| '---YEAR---'
|
||||
| 'LAST_YEAR'
|
||||
| 'THIS_YEAR'
|
||||
| 'NEXT_YEAR';
|
||||
|
||||
export const dateFuncList: DateFuncItem[] = [
|
||||
{ key: '', value: '%s', label: (isTime) => (isTime ? '日時を指定' : '日付を指定') },
|
||||
{ key: 'FROM_TODAY', value: 'FROM_TODAY(%d, %s)', label: '今日から' },
|
||||
{ key: '---NOW---', value: '\---NOW---', label: '' },
|
||||
{ key: 'NOW', value: 'NOW()', label: '当時刻', condition: 'datetime' },
|
||||
{ key: '---DAY---', value: '\---DAY---', label: '', condition: 'datetime' },
|
||||
{ key: 'YESTERDAY', value: 'YESTERDAY()', label: '昨日' },
|
||||
{ key: 'TODAY', value: 'TODAY()', label: '今日' },
|
||||
{ key: 'TOMORROW', value: 'TOMORROW()', label: '明日' },
|
||||
{ key: '---WEEK---', value: '\---WEEK---', label: '' },
|
||||
{ key: 'LAST_WEEK', value: 'LAST_WEEK(%s)', label: '先週' },
|
||||
{ key: 'THIS_WEEK', value: 'THIS_WEEK(%s)', label: '今週' },
|
||||
{ key: 'NEXT_WEEK', value: 'NEXT_WEEK(%s)', label: '来週' },
|
||||
{ key: '---MONTH---', value: '\---MONTH---', label: '' },
|
||||
{ key: 'LAST_MONTH', value: 'LAST_MONTH(%s)', label: '先月' },
|
||||
{ key: 'THIS_MONTH', value: 'THIS_MONTH(%s)', label: '今月' },
|
||||
{ key: 'NEXT_MONTH', value: 'NEXT_MONTH(%s)', label: '来月' },
|
||||
{ key: '---YEAR---', value: '\---YEAR---', label: '' },
|
||||
{ key: 'LAST_YEAR', value: 'LAST_YEAR()', label: '昨年' },
|
||||
{ key: 'THIS_YEAR', value: 'THIS_YEAR()', label: '今年' },
|
||||
{ key: 'NEXT_YEAR', value: 'NEXT_YEAR()', label: '来年' },
|
||||
];
|
||||
|
||||
// search from dateFuncList
|
||||
// DateFuncItem = dateFuncMap[DateFuncKey]
|
||||
export const dateFuncMap: Record<DateFuncKey, DateFuncItem> = dateFuncList.reduce(
|
||||
(map, item) => {
|
||||
map[item.key] = item;
|
||||
return map;
|
||||
},
|
||||
{} as Record<DateFuncKey, DateFuncItem>,
|
||||
);
|
||||
|
||||
export const getDateFuncList = (hasTime: boolean) => {
|
||||
return dateFuncList.filter((item) => {
|
||||
return item.condition ? item.condition === (hasTime ? 'datetime' : 'date') : true;
|
||||
});
|
||||
};
|
||||
34
vue-project/data-fetch-plugin/src/js/desktop.ts
Normal file
34
vue-project/data-fetch-plugin/src/js/desktop.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Field, FieldLayout, SavedData } from "@/types/model";
|
||||
import { KintoneIndexEventHandler } from "./KintoneIndexEventHandler";
|
||||
|
||||
(function (PLUGIN_ID) {
|
||||
kintone.events.on('app.record.index.show', (event) => {
|
||||
try{
|
||||
const setting = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
const config:SavedData<FieldLayout> = getConfig(setting);
|
||||
const currentApp = kintone.app.getId()?.toString();
|
||||
if(!currentApp) return;
|
||||
const handler = new KintoneIndexEventHandler(config,currentApp);
|
||||
handler.init();
|
||||
}catch(error){
|
||||
const detailError =(error instanceof Error) ? "\n詳細:" + error.message : "";
|
||||
const errorMsg = `データ収集中処理中例外発生しました。${ detailError }`;
|
||||
event.error = errorMsg;
|
||||
}
|
||||
return event;
|
||||
});
|
||||
/**
|
||||
* Config設定値を変換する
|
||||
* @param setting
|
||||
* @returns
|
||||
*/
|
||||
function getConfig(setting:any):SavedData<FieldLayout>{
|
||||
const config:SavedData<FieldLayout>={
|
||||
buttonName:setting.buttonName,
|
||||
joinTables:JSON.parse(setting.joinTables)
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
})(kintone.$PLUGIN_ID);
|
||||
|
||||
55
vue-project/data-fetch-plugin/src/js/field-types-mobile.ts
Normal file
55
vue-project/data-fetch-plugin/src/js/field-types-mobile.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
declare var KintoneRestAPIClient: typeof import("@kintone/rest-api-client").KintoneRestAPIClient;
|
||||
const client = new KintoneRestAPIClient();
|
||||
export type Properties = Awaited<ReturnType<typeof client.app.getFormFields>>['properties'];
|
||||
export type Layout = Awaited<ReturnType<typeof client.app.getFormLayout>>['layout'];
|
||||
|
||||
export type OneOf = Properties[string];
|
||||
export type FieldType = OneOf['type'];
|
||||
|
||||
const typeNames = [
|
||||
'RECORD_NUMBER',
|
||||
'CREATOR',
|
||||
'CREATED_TIME',
|
||||
'MODIFIER',
|
||||
'UPDATED_TIME',
|
||||
'CATEGORY',
|
||||
'STATUS',
|
||||
'STATUS_ASSIGNEE',
|
||||
'SINGLE_LINE_TEXT',
|
||||
'NUMBER',
|
||||
'CALC',
|
||||
'MULTI_LINE_TEXT',
|
||||
'RICH_TEXT',
|
||||
'LINK',
|
||||
'CHECK_BOX',
|
||||
'RADIO_BUTTON',
|
||||
'DROP_DOWN',
|
||||
'MULTI_SELECT',
|
||||
'FILE',
|
||||
'DATE',
|
||||
'TIME',
|
||||
'DATETIME',
|
||||
'USER_SELECT',
|
||||
'ORGANIZATION_SELECT',
|
||||
'GROUP_SELECT',
|
||||
'GROUP',
|
||||
'REFERENCE_TABLE',
|
||||
'SUBTABLE',
|
||||
] as const satisfies readonly FieldType[];
|
||||
|
||||
export const types = typeNames.reduce(
|
||||
(acc, name) => {
|
||||
acc[name] = name;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<(typeof typeNames)[number], (typeof typeNames)[number]>,
|
||||
);
|
||||
|
||||
type ExtractOneOf<T extends FieldType> = Extract<OneOf, { type: T }>;
|
||||
function createTypeGuard<T extends FieldType>(type: T) {
|
||||
return (value: OneOf): value is ExtractOneOf<T> => value?.type === type;
|
||||
}
|
||||
|
||||
export const isType = Object.fromEntries(
|
||||
typeNames.map((typeName) => [typeName, createTypeGuard(typeName as FieldType)]),
|
||||
) as { [K in (typeof typeNames)[number]]: (value: OneOf) => value is ExtractOneOf<K> };
|
||||
55
vue-project/data-fetch-plugin/src/js/field-types.ts
Normal file
55
vue-project/data-fetch-plugin/src/js/field-types.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
declare var KintoneRestAPIClient: typeof import("@kintone/rest-api-client").KintoneRestAPIClient;
|
||||
const client = new KintoneRestAPIClient();
|
||||
export type Properties = Awaited<ReturnType<typeof client.app.getFormFields>>['properties'];
|
||||
export type Layout = Awaited<ReturnType<typeof client.app.getFormLayout>>['layout'];
|
||||
|
||||
export type OneOf = Properties[string];
|
||||
export type FieldType = OneOf['type'];
|
||||
|
||||
const typeNames = [
|
||||
'RECORD_NUMBER',
|
||||
'CREATOR',
|
||||
'CREATED_TIME',
|
||||
'MODIFIER',
|
||||
'UPDATED_TIME',
|
||||
'CATEGORY',
|
||||
'STATUS',
|
||||
'STATUS_ASSIGNEE',
|
||||
'SINGLE_LINE_TEXT',
|
||||
'NUMBER',
|
||||
'CALC',
|
||||
'MULTI_LINE_TEXT',
|
||||
'RICH_TEXT',
|
||||
'LINK',
|
||||
'CHECK_BOX',
|
||||
'RADIO_BUTTON',
|
||||
'DROP_DOWN',
|
||||
'MULTI_SELECT',
|
||||
'FILE',
|
||||
'DATE',
|
||||
'TIME',
|
||||
'DATETIME',
|
||||
'USER_SELECT',
|
||||
'ORGANIZATION_SELECT',
|
||||
'GROUP_SELECT',
|
||||
'GROUP',
|
||||
'REFERENCE_TABLE',
|
||||
'SUBTABLE',
|
||||
] as const satisfies readonly FieldType[];
|
||||
|
||||
export const types = typeNames.reduce(
|
||||
(acc, name) => {
|
||||
acc[name] = name;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<(typeof typeNames)[number], (typeof typeNames)[number]>,
|
||||
);
|
||||
|
||||
type ExtractOneOf<T extends FieldType> = Extract<OneOf, { type: T }>;
|
||||
function createTypeGuard<T extends FieldType>(type: T) {
|
||||
return (value: OneOf): value is ExtractOneOf<T> => value?.type === type;
|
||||
}
|
||||
|
||||
export const isType = Object.fromEntries(
|
||||
typeNames.map((typeName) => [typeName, createTypeGuard(typeName as FieldType)]),
|
||||
) as { [K in (typeof typeNames)[number]]: (value: OneOf) => value is ExtractOneOf<K> };
|
||||
211
vue-project/data-fetch-plugin/src/js/helper.ts
Normal file
211
vue-project/data-fetch-plugin/src/js/helper.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import type { FieldsInfo, FieldsJoinMapping, JoinTable, WhereCondition } from '@/types/model';
|
||||
import {
|
||||
client,
|
||||
isType,
|
||||
type FieldType,
|
||||
type App,
|
||||
type Layout,
|
||||
type OneOf,
|
||||
type Properties,
|
||||
} from './kintone-rest-api-client';
|
||||
import type { ComboboxItem, DropdownItem } from 'kintone-ui-component';
|
||||
import { isSpecialType, type SpecialType } from './join';
|
||||
|
||||
export const EMPTY_OPTION = {
|
||||
value: '',
|
||||
label: '--------',
|
||||
} as DropdownItem;
|
||||
|
||||
export function generateId(): string {
|
||||
const timestamp = new Date().getTime().toString(36);
|
||||
const randomNum = Math.random().toString(36).substring(2, 11);
|
||||
return `${timestamp}-${randomNum}`;
|
||||
}
|
||||
|
||||
export function search(list: Array<WhereCondition | FieldsJoinMapping>, id: string) {
|
||||
if (!list) return;
|
||||
return list.find((item) => item.id === id);
|
||||
}
|
||||
|
||||
export const getEmptyWhereCondition = () =>
|
||||
({ field: '', condition: '', data: '', id: generateId() }) as WhereCondition;
|
||||
export const getEmptyOnCondition = () => ({ leftField: '', rightField: '', id: generateId() }) as FieldsJoinMapping;
|
||||
export const getEmptyFieldsMapping = () => ({ leftField: '', rightField: '', id: generateId() }) as FieldsJoinMapping;
|
||||
|
||||
export function createEmptyJoinTable(id = generateId()) {
|
||||
return resetTable({ id, app: '' } as JoinTable);
|
||||
}
|
||||
|
||||
export function resetTable(table: JoinTable) {
|
||||
table.table = '';
|
||||
return resetConditions(table);
|
||||
}
|
||||
|
||||
export function resetConditions(table: JoinTable) {
|
||||
table.onConditions = [getEmptyOnCondition()];
|
||||
table.fieldsMapping = [getEmptyFieldsMapping()];
|
||||
table.whereConditions = [getEmptyWhereCondition()];
|
||||
return table;
|
||||
}
|
||||
|
||||
const LIMIT = 100; // 毎回請求の最大値
|
||||
export const loadApps = async (offset = 0, _apps: DropdownItem[] = []): Promise<DropdownItem[]> => {
|
||||
const { apps } = await client.app.getApps({ limit: LIMIT, offset });
|
||||
const allApps: DropdownItem[] = [
|
||||
..._apps,
|
||||
...apps.map((app: App) => ({ value: app.appId, label: app.name + '(ID: ' + app.appId + ')' })),
|
||||
];
|
||||
if (apps.length === LIMIT) {
|
||||
return loadApps(offset + LIMIT, allApps);
|
||||
}
|
||||
allApps.sort((a, b) => Number(b.value) - Number(a.value));
|
||||
allApps.unshift(EMPTY_OPTION);
|
||||
return allApps;
|
||||
};
|
||||
|
||||
export const loadAppFieldsAndLayout = async (appId: string | number = kintone.app.getId() as number) => {
|
||||
const fields = (await client.app.getFormFields({ app: appId })).properties;
|
||||
return {
|
||||
fields: flatFields(fields),
|
||||
layout: (await client.app.getFormLayout({ app: appId })).layout,
|
||||
} as FieldsInfo;
|
||||
};
|
||||
|
||||
function flatFields(fields: Properties) {
|
||||
const subtableFields = {} as Properties;
|
||||
Object.values(fields).forEach((field) => {
|
||||
if (isType.SUBTABLE(field)) {
|
||||
Object.values(field.fields).forEach((subField) => {
|
||||
const copy = JSON.parse(JSON.stringify(subField)) as typeof subField & {originLabel:string, tableCode:string};
|
||||
copy.label = '[' + field.label + '].' + subField.label;
|
||||
copy.originLabel = subField.label;
|
||||
copy.tableCode = field.code;
|
||||
subtableFields[subField.code] = copy;
|
||||
});
|
||||
}
|
||||
});
|
||||
return { ...fields, ...subtableFields };
|
||||
}
|
||||
|
||||
type FilterType = Array<FieldType | SpecialType>;
|
||||
type Param = {
|
||||
subTableCode: string | undefined;
|
||||
filterType?: FilterType;
|
||||
baseFilter: FieldType[] | undefined;
|
||||
dependFilterField?: OneOf;
|
||||
defaultLabel?: string;
|
||||
defaultDisableCallback?: (field: OneOf) => boolean;
|
||||
needAllSubTableField?: boolean;
|
||||
};
|
||||
export const getFieldsDropdownItems = (
|
||||
{ fields, layout }: FieldsInfo,
|
||||
{
|
||||
subTableCode, // specified subTable
|
||||
baseFilter, // set not allowed items hidden, undefined means no filter
|
||||
defaultLabel, // label shown (default '--------')
|
||||
filterType, // set not allowed items disabled, undefined and [] means no filter
|
||||
dependFilterField, // used for filterType
|
||||
defaultDisableCallback, // callback to control disabled items, like filterType
|
||||
needAllSubTableField = false, // show all subtable fields
|
||||
}: Param,
|
||||
) => {
|
||||
// get used field codes
|
||||
const fieldOrder = extractFields(layout, baseFilter, !!needAllSubTableField, subTableCode);
|
||||
const fieldMap = fields;
|
||||
|
||||
// create labels
|
||||
const labels: ComboboxItem[] = [
|
||||
{
|
||||
value: EMPTY_OPTION.value,
|
||||
label: defaultLabel || EMPTY_OPTION.label,
|
||||
},
|
||||
];
|
||||
return fieldOrder.reduce((acc, fieldCode) => {
|
||||
const field = fieldMap[fieldCode];
|
||||
if (!fieldCode) return acc;
|
||||
acc.push({
|
||||
value: fieldCode,
|
||||
label: field.label + '(FC: ' + fieldCode + ')',
|
||||
disabled:
|
||||
(defaultDisableCallback && defaultDisableCallback(field)) ||
|
||||
(filterType && !checkFilterType(field, dependFilterField, filterType)),
|
||||
});
|
||||
return acc;
|
||||
}, labels);
|
||||
};
|
||||
|
||||
const checkFilterType = (field: OneOf, dependFilterField: OneOf | undefined, filterType: FilterType) => {
|
||||
if (!filterType.length) return true; // [] means no filter
|
||||
return !!filterType.find((type) => {
|
||||
if (isSpecialType(type)) {
|
||||
return type.check(field, dependFilterField);
|
||||
}
|
||||
return isType[type](field);
|
||||
});
|
||||
};
|
||||
|
||||
export const getTableFieldsDropdownItems = ({ fields }: FieldsInfo, filterType?: FieldType) => {
|
||||
return Object.keys(fields).reduce(
|
||||
(acc, fieldCode) => {
|
||||
const field = fields[fieldCode];
|
||||
if (filterType && !isType[filterType](field)) return acc;
|
||||
acc.push({
|
||||
value: fieldCode,
|
||||
label: field.label + '(FC: ' + fieldCode + ')',
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
[EMPTY_OPTION],
|
||||
);
|
||||
};
|
||||
|
||||
const extractFields = (layout: Layout, baseFilter: FieldType[] | undefined, needAllSubTableField: boolean, subTableCode?: string) => {
|
||||
return layout.reduce((acc, each) => {
|
||||
if (each.type === 'GROUP') {
|
||||
acc.push(...extractFields(each.layout, baseFilter, needAllSubTableField, subTableCode));
|
||||
} else if (each.type === 'ROW' || (!needAllSubTableField && each.code === subTableCode) || (needAllSubTableField && each.type === 'SUBTABLE')) {
|
||||
acc.push(
|
||||
...each.fields.map((field) => {
|
||||
if (!('code' in field)) return '';
|
||||
if (!baseFilter) return field.code;
|
||||
return baseFilter.find((t) => t === field.type) ? field?.code || '' : '';
|
||||
}),
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
};
|
||||
|
||||
export function getFieldObj(fieldCode: string, { fields }: FieldsInfo, subTableCode?: string) {
|
||||
const meta = getMeta(fields, subTableCode);
|
||||
return meta[fieldCode];
|
||||
}
|
||||
|
||||
export function getMeta(fields: Properties, subTableCode?: string, withNoSubTableField = true) {
|
||||
if (!fields || !subTableCode) {
|
||||
return fields;
|
||||
}
|
||||
let meta = fields;
|
||||
const table = meta[subTableCode];
|
||||
if (isType.SUBTABLE(table)) {
|
||||
const subFields = table.fields;
|
||||
Object.values(subFields).forEach((field) => {
|
||||
if (typeof field === 'object' && field !== null) {
|
||||
(field as Record<string, any>).subField = true;
|
||||
}
|
||||
});
|
||||
if (withNoSubTableField) {
|
||||
meta = { ...fields, ...subFields };
|
||||
} else {
|
||||
meta = subFields;
|
||||
}
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
export const isStringArray = (value: any) => {
|
||||
if (Array.isArray(value) && value.every((x) => typeof x === 'string')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
171
vue-project/data-fetch-plugin/src/js/join.ts
Normal file
171
vue-project/data-fetch-plugin/src/js/join.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import type { CalcType, LinkProtocolType } from '@/types/my-kintone';
|
||||
import { isType, type FieldType, type OneOf } from './kintone-rest-api-client';
|
||||
import { KintoneFormFieldProperty } from '@kintone/rest-api-client';
|
||||
|
||||
export function isLeftJoinForceDisable(field: OneOf) {
|
||||
if (isType.CALC(field)) {
|
||||
return field.format === 'DAY_HOUR_MINUTE' || field.format === 'HOUR_MINUTE';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isRightJoinForceDisable(field: OneOf) {
|
||||
if (isLookup(field)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export type SpecialType<T = string, F = any> = {
|
||||
type: T;
|
||||
format: F;
|
||||
check: (checkField: OneOf, selectedField?: OneOf) => boolean;
|
||||
};
|
||||
|
||||
// LEFT
|
||||
// LEFT - lookup
|
||||
export type LookupTypeL2R = SpecialType<'LOOKUP_FROM_LEFT', FieldType[]>;
|
||||
export const forMayLookup = (format: FieldType[]): LookupTypeL2R => {
|
||||
return {
|
||||
type: 'LOOKUP_FROM_LEFT',
|
||||
format,
|
||||
check: function (checkField: OneOf, selectedLeftField?: OneOf) {
|
||||
if (isLookup(checkField) && selectedLeftField) {
|
||||
return isLookup(selectedLeftField) ? checkField.type === selectedLeftField.type : false;
|
||||
}
|
||||
return !!this.format?.find((e) => e === checkField.type);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const mayLookupText = forMayLookup(['SINGLE_LINE_TEXT']);
|
||||
export const mayLookupNumber = forMayLookup(['NUMBER']);
|
||||
export const mayLookupTextNumber = forMayLookup(['SINGLE_LINE_TEXT', 'NUMBER']);
|
||||
|
||||
// LEFT - calc
|
||||
export type CalcTypeL2R = SpecialType<'CALC_FROM_LEFT', Record<CalcType, Array<FieldType | LookupTypeL2R>>>;
|
||||
|
||||
export const leftCalcType: CalcTypeL2R = {
|
||||
type: 'CALC_FROM_LEFT',
|
||||
format: {
|
||||
NUMBER: [mayLookupTextNumber],
|
||||
NUMBER_DIGIT: [mayLookupTextNumber],
|
||||
DATE: ['DATE'],
|
||||
TIME: ['TIME'],
|
||||
DATETIME: ['DATETIME'],
|
||||
HOUR_MINUTE: [],
|
||||
DAY_HOUR_MINUTE: [],
|
||||
},
|
||||
check: function (checkField: OneOf, selectedLeftField?: OneOf) {
|
||||
let allowed: Array<FieldType | LookupTypeL2R> = [];
|
||||
if (selectedLeftField && isType.CALC(selectedLeftField)) {
|
||||
allowed = this.format[selectedLeftField.format];
|
||||
}
|
||||
return !!allowed.find((e) => {
|
||||
if (isSpecialType(e) && isLookupFromLeft(e)) {
|
||||
return e.check(checkField, selectedLeftField);
|
||||
}
|
||||
return e === checkField.type;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// LEFT - link
|
||||
export type LinkType = SpecialType<'LINK', Record<LinkProtocolType, LinkProtocolType[]>>;
|
||||
|
||||
export const linkType: LinkType = {
|
||||
type: 'LINK',
|
||||
format: {
|
||||
// 入力値の種別が同じ場合のみ
|
||||
WEB: ['WEB'],
|
||||
CALL: ['CALL'],
|
||||
MAIL: ['MAIL'],
|
||||
},
|
||||
check: function (checkField: OneOf, selectedField?: OneOf) {
|
||||
let allowed: LinkProtocolType[] = [];
|
||||
if (selectedField && isType.LINK(selectedField)) {
|
||||
allowed = this.format[selectedField.protocol];
|
||||
}
|
||||
if (checkField && isType.LINK(checkField)) {
|
||||
return !!allowed.find((e) => e === checkField.protocol);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
// LEFT - rule
|
||||
export type AvailableRight = FieldType | CalcTypeL2R | LinkType | LookupTypeL2R;
|
||||
const availableLeftJoinType = {
|
||||
SINGLE_LINE_TEXT: [mayLookupText],
|
||||
NUMBER: [mayLookupNumber],
|
||||
CALC: [leftCalcType],
|
||||
DATE: ['DATE'],
|
||||
TIME: ['TIME'],
|
||||
DATETIME: ['DATETIME'],
|
||||
LINK: [linkType],
|
||||
} as Record<FieldType, AvailableRight[]>;
|
||||
|
||||
// RIGHT - calc
|
||||
export type CalcTypeR2L = SpecialType<'CALC_FROM_RIGHT', CalcType[]>;
|
||||
|
||||
export const forCalc = (format: CalcType[]): CalcTypeR2L => {
|
||||
return {
|
||||
type: 'CALC_FROM_RIGHT',
|
||||
format,
|
||||
check: function (checkField: OneOf, selectedRightField?: OneOf) {
|
||||
return isType.CALC(checkField) && !!this.format?.find((e) => e === checkField.format);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// RIGHT - rule
|
||||
export type AvailableLeft = FieldType | CalcTypeR2L | LinkType;
|
||||
const availableRightJoinType = {
|
||||
SINGLE_LINE_TEXT: ['SINGLE_LINE_TEXT', forCalc(['NUMBER', 'NUMBER_DIGIT'])],
|
||||
NUMBER: ['NUMBER', forCalc(['NUMBER', 'NUMBER_DIGIT'])],
|
||||
DATE: ['DATE', forCalc(['DATE'])],
|
||||
TIME: ['TIME', forCalc(['TIME'])],
|
||||
DATETIME: ['DATETIME', forCalc(['DATETIME'])],
|
||||
LINK: [linkType],
|
||||
} as Record<FieldType, AvailableLeft[]>;
|
||||
|
||||
// methods
|
||||
// undefined means all
|
||||
export function getRightAvailableJoinType(left?: OneOf | '') {
|
||||
if (left === undefined) {
|
||||
return Object.keys(availableRightJoinType) as FieldType[];
|
||||
}
|
||||
return left ? availableLeftJoinType[left.type] : [];
|
||||
}
|
||||
|
||||
// undefined means all
|
||||
export function getLeftAvailableJoinType(right?: OneOf | '') {
|
||||
if (right === undefined) {
|
||||
return Object.keys(availableLeftJoinType) as FieldType[];
|
||||
}
|
||||
return right ? availableRightJoinType[right.type] : [];
|
||||
}
|
||||
|
||||
export function isSpecialType(obj: FieldType | SpecialType): obj is SpecialType {
|
||||
return typeof obj === 'object' && !Array.isArray(obj) && 'type' in obj;
|
||||
}
|
||||
|
||||
export function isLookupFromLeft(obj: SpecialType): obj is LookupTypeL2R {
|
||||
return obj.type === 'LOOKUP_FROM_LEFT';
|
||||
}
|
||||
|
||||
export function isLinkType(obj: SpecialType): obj is LinkType {
|
||||
return obj.type === 'LINK';
|
||||
}
|
||||
|
||||
export function isCalcFromLeft(obj: SpecialType): obj is CalcTypeL2R {
|
||||
return obj.type === 'CALC_FROM_LEFT';
|
||||
}
|
||||
|
||||
export function isCalcFromRight(obj: SpecialType): obj is CalcTypeR2L {
|
||||
return obj.type === 'CALC_FROM_RIGHT';
|
||||
}
|
||||
|
||||
export function isLookup(field: OneOf): field is KintoneFormFieldProperty.Lookup {
|
||||
return 'lookup' in field;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export type App = {
|
||||
export type Properties = Awaited<ReturnType<typeof client.app.getFormFields>>['properties'];
|
||||
export type Layout = Awaited<ReturnType<typeof client.app.getFormLayout>>['layout'];
|
||||
|
||||
type OneOf = Properties[string];
|
||||
export type OneOf = Properties[string];
|
||||
export type FieldType = OneOf['type'];
|
||||
|
||||
const typeNames = [
|
||||
@@ -54,7 +54,7 @@ export const types = typeNames.reduce(
|
||||
|
||||
type ExtractOneOf<T extends FieldType> = Extract<OneOf, { type: T }>;
|
||||
function createTypeGuard<T extends FieldType>(type: T) {
|
||||
return (value: OneOf): value is ExtractOneOf<T> => value.type === type;
|
||||
return (value: OneOf): value is ExtractOneOf<T> => value?.type === type;
|
||||
}
|
||||
|
||||
export const isType = Object.fromEntries(
|
||||
103
vue-project/data-fetch-plugin/src/js/mapping.ts
Normal file
103
vue-project/data-fetch-plugin/src/js/mapping.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { type FieldType, type OneOf } from './kintone-rest-api-client';
|
||||
import {
|
||||
leftCalcType,
|
||||
linkType,
|
||||
type SpecialType,
|
||||
mayLookupTextNumber,
|
||||
mayLookupText,
|
||||
type AvailableRight,
|
||||
type AvailableLeft,
|
||||
isLookup,
|
||||
forCalc,
|
||||
type CalcTypeR2L,
|
||||
isSpecialType,
|
||||
} from './join';
|
||||
import type { SelectType } from '@/types/my-kintone';
|
||||
|
||||
// LEFT - rule
|
||||
const availableLeftMappingType = {
|
||||
SINGLE_LINE_TEXT: [mayLookupText],
|
||||
MULTI_LINE_TEXT: ['MULTI_LINE_TEXT'],
|
||||
RICH_TEXT: ['RICH_TEXT'],
|
||||
NUMBER: [mayLookupTextNumber],
|
||||
CALC: [leftCalcType],
|
||||
RADIO_BUTTON: ['RADIO_BUTTON'],
|
||||
CHECK_BOX: ['CHECK_BOX'],
|
||||
MULTI_SELECT: ['MULTI_SELECT'], // TODO 带选项字段报错
|
||||
DROP_DOWN: ['DROP_DOWN'],
|
||||
USER_SELECT: ['USER_SELECT'],
|
||||
ORGANIZATION_SELECT: ['ORGANIZATION_SELECT'],
|
||||
GROUP_SELECT: ['GROUP_SELECT'],
|
||||
DATE: ['DATE', 'DATETIME'],
|
||||
TIME: ['TIME'],
|
||||
DATETIME: ['DATETIME'],
|
||||
LINK: [linkType, mayLookupText],
|
||||
//LOOKUP
|
||||
RECORD_NUMBER: [mayLookupTextNumber],
|
||||
CREATOR: ['USER_SELECT'],
|
||||
CREATED_TIME: ['DATETIME'],
|
||||
MODIFIER: ['USER_SELECT'],
|
||||
UPDATED_TIME: ['DATETIME'],
|
||||
} as Record<FieldType, AvailableRight[]>;
|
||||
|
||||
// RIGHT
|
||||
export type LookupTypeR2L = SpecialType<'LOOKUP_FROM_RIGHT', Array<FieldType | CalcTypeR2L>>;
|
||||
|
||||
export const isSameLookupOr = (format: Array<FieldType | CalcTypeR2L>): LookupTypeR2L => {
|
||||
return {
|
||||
type: 'LOOKUP_FROM_RIGHT',
|
||||
format,
|
||||
check: function (checkField: OneOf, selectedRightField?: OneOf) {
|
||||
if (selectedRightField && isLookup(selectedRightField)) {
|
||||
return isLookup(checkField) ? checkField.type === selectedRightField.type : false;
|
||||
}
|
||||
return !!this.format?.find((e) => {
|
||||
if (isSpecialType(e)) {
|
||||
return e.check(checkField, selectedRightField);
|
||||
}
|
||||
return e === checkField.type;
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const availableRightMappingType = {
|
||||
SINGLE_LINE_TEXT: [
|
||||
isSameLookupOr(['SINGLE_LINE_TEXT', 'NUMBER', forCalc(['NUMBER', 'NUMBER_DIGIT']), 'LINK', 'RECORD_NUMBER']),
|
||||
],
|
||||
MULTI_LINE_TEXT: ['MULTI_LINE_TEXT'],
|
||||
RICH_TEXT: ['RICH_TEXT'],
|
||||
NUMBER: [isSameLookupOr(['NUMBER', forCalc(['NUMBER', 'NUMBER_DIGIT']), 'RECORD_NUMBER'])],
|
||||
RADIO_BUTTON: ['RADIO_BUTTON'],
|
||||
CHECK_BOX: ['CHECK_BOX'],
|
||||
MULTI_SELECT: ['MULTI_SELECT'], // TODO 带选项字段报错
|
||||
DROP_DOWN: ['DROP_DOWN'],
|
||||
USER_SELECT: ['USER_SELECT', 'CREATOR', 'MODIFIER'],
|
||||
ORGANIZATION_SELECT: ['ORGANIZATION_SELECT'],
|
||||
GROUP_SELECT: ['GROUP_SELECT'],
|
||||
DATE: ['DATE', forCalc(['DATE'])],
|
||||
TIME: ['TIME', forCalc(['TIME'])],
|
||||
DATETIME: ['DATE', 'DATETIME', forCalc(['DATETIME']), 'CREATED_TIME', 'UPDATED_TIME'],
|
||||
LINK: [linkType],
|
||||
} as Record<FieldType, Array<AvailableLeft | LookupTypeR2L>>;
|
||||
|
||||
// methods
|
||||
// undefined means all
|
||||
export function getRightAvailableMappingType(left?: OneOf | '') {
|
||||
if (left === undefined) {
|
||||
return Object.keys(availableRightMappingType) as FieldType[];
|
||||
}
|
||||
return left ? availableLeftMappingType[left.type] : [];
|
||||
}
|
||||
|
||||
// undefined means all
|
||||
export function getLeftAvailableMappingType(right?: OneOf | '') {
|
||||
if (right === undefined) {
|
||||
return Object.keys(availableLeftMappingType) as FieldType[];
|
||||
}
|
||||
return right ? availableRightMappingType[right.type] : [];
|
||||
}
|
||||
|
||||
export function isSelectType(field: OneOf): field is SelectType {
|
||||
return 'options' in field;
|
||||
}
|
||||
32
vue-project/data-fetch-plugin/src/js/mobile.ts
Normal file
32
vue-project/data-fetch-plugin/src/js/mobile.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { Field, FieldLayout, SavedData } from "@/types/model";
|
||||
import { KintoneIndexEventHandler } from "./KintoneIndexEventHandler.mobile";
|
||||
|
||||
(function (PLUGIN_ID) {
|
||||
kintone.events.on('mobile.app.record.index.show', (event) => {
|
||||
try{
|
||||
const setting = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
const config:SavedData<FieldLayout> = getConfig(setting);
|
||||
const currentApp = kintone.mobile.app.getId()?.toString();
|
||||
if(!currentApp) return;
|
||||
const handler = new KintoneIndexEventHandler(config,currentApp);
|
||||
handler.init();
|
||||
}catch(error){
|
||||
const detailError =(error instanceof Error) ? "\n詳細:" + error.message : "";
|
||||
const errorMsg = `データ収集中処理中例外発生しました。${ detailError }`;
|
||||
event.error = errorMsg;
|
||||
}
|
||||
return event;
|
||||
});
|
||||
/**
|
||||
* Config設定値を変換する
|
||||
* @param setting
|
||||
* @returns
|
||||
*/
|
||||
function getConfig(setting:any):SavedData<FieldLayout>{
|
||||
const config:SavedData<FieldLayout>={
|
||||
buttonName:setting.buttonName,
|
||||
joinTables:JSON.parse(setting.joinTables)
|
||||
}
|
||||
return config;
|
||||
}
|
||||
})(kintone.$PLUGIN_ID);
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/kintone/js-sdk/%40kintone/plugin-manifest-validator%4010.2.0/packages/plugin-manifest-validator/manifest-schema.json",
|
||||
"manifest_version": 1,
|
||||
"version": 2,
|
||||
"version": 1,
|
||||
"type": "APP",
|
||||
"desktop": {
|
||||
"js": [
|
||||
"js/KintoneRestAPIClient.min.js",
|
||||
"js/kuc.min.js",
|
||||
"js/desktop.js"
|
||||
],
|
||||
"css": [
|
||||
@@ -16,26 +18,28 @@
|
||||
"config": {
|
||||
"html": "html/config.html",
|
||||
"js": [
|
||||
"js/config.js"
|
||||
"js/config.js"
|
||||
],
|
||||
"css": [
|
||||
"css/51-modern-default.css",
|
||||
"css/config.css"
|
||||
],
|
||||
"required_params": [
|
||||
"message"
|
||||
"buttonName"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"en": "data fetch pluging",
|
||||
"en": "data fetch plugin",
|
||||
"ja": "データ取得プラグイン"
|
||||
},
|
||||
"description": {
|
||||
"en": "create search data pluging",
|
||||
"en": "create search data plugin",
|
||||
"ja": "検索結果のデータを生成するプラグインです"
|
||||
},
|
||||
"mobile": {
|
||||
"js": [
|
||||
"js/KintoneRestAPIClient.min.js",
|
||||
"js/kuc.min.js",
|
||||
"js/mobile.js"
|
||||
],
|
||||
"css": [
|
||||
71
vue-project/data-fetch-plugin/src/types/model.d.ts
vendored
Normal file
71
vue-project/data-fetch-plugin/src/types/model.d.ts
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { ConditionValue } from '@/js/conditions';
|
||||
import type { Layout, Properties } from '@/js/kintone-rest-api-client';
|
||||
import type { DropdownItem } from 'kintone-ui-component';
|
||||
|
||||
|
||||
|
||||
export interface FieldsJoinMapping<FieldType = string> {
|
||||
id: string;
|
||||
leftField: FieldType;
|
||||
rightField: FieldType;
|
||||
}
|
||||
|
||||
export interface WhereCondition<FieldType = string> {
|
||||
id: string;
|
||||
field: FieldType;
|
||||
condition: ConditionValue;
|
||||
data: StringValue;
|
||||
}
|
||||
|
||||
export interface JoinTable<FieldType = string> {
|
||||
id: string;
|
||||
app: string; // 取得元アプリ
|
||||
table: string; // テーブル
|
||||
onConditions: FieldsJoinMapping<FieldType>[]; // 連結条件
|
||||
fieldsMapping: FieldsJoinMapping<FieldType>[]; // 取得フィールド
|
||||
whereConditions: WhereCondition<FieldType>[]; // 絞込条件
|
||||
meta?: Properties;
|
||||
}
|
||||
|
||||
// 存储的数据格式
|
||||
export interface SavedData<FieldType = string> {
|
||||
buttonName: string;
|
||||
joinTables: JoinTable<FieldType>[];
|
||||
}
|
||||
|
||||
export interface FieldsInfo {
|
||||
fields: Properties;
|
||||
layout: Layout;
|
||||
}
|
||||
|
||||
export interface CachedData {
|
||||
apps: DropdownItem[],
|
||||
currentAppFields: FieldsInfo,
|
||||
}
|
||||
|
||||
export interface CachedSelectedAppData {
|
||||
appFields: FieldsInfo,
|
||||
loading: boolean,
|
||||
table: JoinTable,
|
||||
}
|
||||
export type Record = {
|
||||
[fieldCode: string]: Field;
|
||||
};
|
||||
|
||||
export type RecordForParameter = {
|
||||
[fieldCode: string]: {
|
||||
value: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type Field={
|
||||
type:string;
|
||||
value:any;
|
||||
}
|
||||
|
||||
export type FieldLayout={
|
||||
type:string;
|
||||
code:string;
|
||||
}
|
||||
|
||||
export type StringValue = string | string[];
|
||||
14
vue-project/data-fetch-plugin/src/types/my-kintone.d.ts
vendored
Normal file
14
vue-project/data-fetch-plugin/src/types/my-kintone.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import { KintoneFormFieldProperty } from '@kintone/rest-api-client';
|
||||
|
||||
export interface KucEvent<T> {
|
||||
detail: T;
|
||||
}
|
||||
|
||||
export type CalcType = 'NUMBER' | 'NUMBER_DIGIT' | 'DATETIME' | 'DATE' | 'TIME' | 'HOUR_MINUTE' | 'DAY_HOUR_MINUTE';
|
||||
export type LinkProtocolType = 'WEB' | 'CALL' | 'MAIL';
|
||||
|
||||
export type SelectType =
|
||||
| KintoneFormFieldProperty.CheckBox
|
||||
| KintoneFormFieldProperty.RadioButton
|
||||
| KintoneFormFieldProperty.Dropdown
|
||||
| KintoneFormFieldProperty.MultiSelect;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import copy from "rollup-plugin-copy";
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
|
||||
function replaceKucTagsPlugin() {
|
||||
@@ -26,6 +26,9 @@ function replaceKucTagsPlugin() {
|
||||
const keyPascal = key.split('-')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join('');
|
||||
if (key === 'multi-choice') {
|
||||
key = 'multichoice';
|
||||
}
|
||||
importScript += `import * as Kuc${keyPascal} from "kintone-ui-component/lib/${key}";`
|
||||
});
|
||||
importScript += '</script>';
|
||||
@@ -80,6 +83,6 @@ export default defineConfig({
|
||||
assetFileNames: 'src/[ext]/[name].[ext]',
|
||||
},
|
||||
},
|
||||
sourcemap:'inline',
|
||||
sourcemap: 'inline',
|
||||
}
|
||||
})
|
||||
@@ -1,13 +0,0 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@cybozu/eslint-config/globals/kintone.js',
|
||||
'@cybozu/eslint-config/lib/base.js',
|
||||
'@cybozu/eslint-config/lib/kintone.js',
|
||||
'@cybozu/eslint-config/lib/prettier.js',
|
||||
],
|
||||
rules: {
|
||||
'prettier/prettier': ['error', { singleQuote: true }],
|
||||
'space-before-function-paren': 0,
|
||||
'object-curly-spacing': 0,
|
||||
},
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
# My-kintone-plugin
|
||||
|
||||
1. 命令:
|
||||
- `npm run build` 会将代码按照 kintone plugin 格式打包到 `dist`,并生成 `plugin.zip`
|
||||
- `npm run upload` 会将 `plugin.zip` 上传到 kintone
|
||||
|
||||
2. 使用 Vue3.0 开发:
|
||||
- 配置页面在 `components/Config.vue` 中开发
|
||||
- Desktop 页面在 `js/desktop.ts` 中开发
|
||||
|
||||
3. 关于组件:
|
||||
- 使用了 https://ui-component.kintone.dev/ 组件库,但是它没有支持 Vue,所以需要作为 Web Component 来使用
|
||||
- 又由于 Web Component 的格式是类似 `<kuc-button-1-18-0>`,为了开发方便能写成 `<kuc-button>`,手动进行了全局替换和引入包(见 `vite.config.js`)
|
||||
- 同时也定死了 kintone-ui-component 版本号,更新版本号需要修改 `vite.config.js` 的插件
|
||||
- 目前尚未实现 `<template>` 中组件的 ts 提示
|
||||
@@ -1,15 +0,0 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQCF7z/zsYmoe+L1AYTeCYvy9yBXlsXOniTzox6svsXunibVP3y+
|
||||
f1jEwu2cnTdp/GABOzsVHNSrYGedRDlwG93Y8qxe7qNKLZAFL6ujmJ0FJixuYrh4
|
||||
xvaWR6SlKIbws+803qAyE6dUN893xeZeJdWGelZNBsCZu8Nwmi28k1flzQIDAQAB
|
||||
AoGAbWJchJZ2qtejIB5BeWWqmqAiFebZXkniO+j44HReCue3J2pWYu52fRwGG2Z7
|
||||
H2AyuE67jh6hweVWOibCEkFwCM+MwkSpKNRyFqJwdzZGoMm/oT67dDGYELrmNCx/
|
||||
9G5DdLgLXsA2dAANxTybaK8wg123Hhrh7NwJDETn9OC+uzECQQDeJTq4OSK9qUw9
|
||||
RCpgijpVdnzc4hC0CNjKe/+z8bQOPVcX7zLcggwX/7i2UmNxBxfYFrCN8XIGJNGN
|
||||
VXMpUdCjAkEAmliRAdgAJvoMvaS+gCcJt9tU18F2aunnGudpdwMWDFYdsnztLSJQ
|
||||
uLPsPQM0TJJYwXWZ+akQuReqXeKg4WgmzwJBAMZAg38VvqN1C81BoHA37IeJDzYx
|
||||
qqaBnrhWoYV+GCr9I1UA7GtOxGxGlBpivMyKgAUher+y0wgYo8t2jyg5E/ECQCRH
|
||||
JO42AvMmWtBIZK5ifppEZ1C/HEJM8BEWy2c5xnjn1NsbGfQ92JNRVvmQQz6sN0hh
|
||||
h+tynYej1Ft05TOV82kCQQCDd0/JtINW3Myj2nWIe8c9IjsBUtNOkaCa13tGOzwJ
|
||||
3G8Bg0GzdVSC73OnEaguC72kBvyGO4enUFkOq6p6kmFQ
|
||||
-----END RSA PRIVATE KEY-----
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 110 B |
@@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<h2 class="settings-heading">{{ $t('config.title') }}</h2>
|
||||
<p class="kintoneplugin-desc">{{ $t('config.desc') }}</p>
|
||||
|
||||
<plugin-row class="header-row border">
|
||||
<plugin-input v-model="data.buttonName" placeholder="集約" label="集約ボタン名" />
|
||||
</plugin-row>
|
||||
|
||||
<div id="main-area" ref="mainArea">
|
||||
<plugin-table-area v-for="joinTable in data.joinTables" :table="joinTable" :key="joinTable.id" />
|
||||
</div>
|
||||
|
||||
<plugin-row class="footer-row border">
|
||||
<kuc-button text="キャンセル" type="normal" @click="cancel" />
|
||||
<kuc-button text="保存する" class="save-btn" type="submit" @click="save" />
|
||||
</plugin-row>
|
||||
|
||||
<kuc-spinner :container="mainArea" ref="spinner"></kuc-spinner>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { createEmptyJoinTable, loadApps, loadAppFieldsAndLayout, EMPTY_OPTION, getEmptyOnCondition } from '@/js/helper';
|
||||
import type { CachedData, FieldsInfo, JoinTable, SavedData } from '@/types/model';
|
||||
import type { Spinner } from 'kintone-ui-component';
|
||||
|
||||
import { onMounted, watch, provide, reactive, ref, shallowRef, nextTick } from 'vue';
|
||||
|
||||
const props = defineProps<{ pluginId: string }>();
|
||||
const loading = ref(false);
|
||||
const data: SavedData = reactive({
|
||||
buttonName: '',
|
||||
joinTables: [createEmptyJoinTable()],
|
||||
});
|
||||
|
||||
const cachedData: CachedData = reactive({
|
||||
apps: [EMPTY_OPTION],
|
||||
currentAppFields: { fields: {}, layout: [] } as FieldsInfo,
|
||||
});
|
||||
|
||||
provide('savedData', data);
|
||||
provide('cachedData', cachedData);
|
||||
|
||||
const mainArea = shallowRef<HTMLElement | null>(null);
|
||||
const spinner = shallowRef<Spinner | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
nextTick(async () => {
|
||||
spinner.value?.close(); // fix bug: kuc-spinner will not auto amount to target HTML element when init loading
|
||||
const savedData = kintone.plugin.app.getConfig(props.pluginId);
|
||||
loading.value = true;
|
||||
cachedData.apps = await loadApps();
|
||||
cachedData.currentAppFields = await loadAppFieldsAndLayout();
|
||||
if (savedData?.joinTables) {
|
||||
data.joinTables = JSON.parse(savedData.joinTables); //TODO JSON;
|
||||
}
|
||||
data.buttonName = savedData?.buttonName || '集約';
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
watch(loading, (load) => {
|
||||
load ? spinner.value?.open() : spinner.value?.close();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => data.joinTables.length,
|
||||
(newLength) => {
|
||||
if (newLength === 1) {
|
||||
data.joinTables[0].onConditions = [getEmptyOnCondition()];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function save() {
|
||||
kintone.plugin.app.setConfig({
|
||||
buttonName: data.buttonName,
|
||||
joinTables: JSON.stringify(data.joinTables || []),
|
||||
});
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
window.location.href = `../../${kintone.app.getId()}/plugin/`;
|
||||
}
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user