Fix load apps

This commit is contained in:
2025-01-22 13:57:33 +08:00
parent d0ce7b8349
commit 6aba3fc065
10 changed files with 205 additions and 85 deletions

View File

@@ -17,5 +17,6 @@ declare module 'vue' {
PluginTableArea: typeof import('./src/components/basic/PluginTableArea.vue')['default'] PluginTableArea: typeof import('./src/components/basic/PluginTableArea.vue')['default']
PluginTableConditionRow: typeof import('./src/components/basic/PluginTableConditionRow.vue')['default'] PluginTableConditionRow: typeof import('./src/components/basic/PluginTableConditionRow.vue')['default']
PluginTableConnectRow: typeof import('./src/components/basic/PluginTableConnectRow.vue')['default'] PluginTableConnectRow: typeof import('./src/components/basic/PluginTableConnectRow.vue')['default']
TableCombobox: typeof import('./src/components/basic/TableCombobox.vue')['default']
} }
} }

View File

@@ -18,9 +18,9 @@
<kuc-spinner :container="mainArea" ref="spinner"></kuc-spinner> <kuc-spinner :container="mainArea" ref="spinner"></kuc-spinner>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { createEmptyJoinTable, fetchApps } from '@/js/helper'; import { createEmptyJoinTable, loadApps, loadAppFieldsAndLayout, EMPTY_OPTION } from '@/js/helper';
import type { JoinTable, SavedData } from '@/types/model'; import type { CachedData, FieldsInfo, JoinTable, SavedData } from '@/types/model';
import type { KucSpinnerEl } from '@/types/my-kintone'; import type { KucDropdownItem, KucSpinnerEl } from '@/types/my-kintone';
import { onMounted, watch, provide, reactive, ref, shallowRef, nextTick } from 'vue'; import { onMounted, watch, provide, reactive, ref, shallowRef, nextTick } from 'vue';
@@ -31,19 +31,26 @@ const data: SavedData = reactive({
joinTables: [] as JoinTable[], joinTables: [] as JoinTable[],
}); });
const cachedData: CachedData = reactive({
apps: [EMPTY_OPTION],
currentAppFields: { fields: [], layout: [] } as FieldsInfo,
});
provide('savedData', data); provide('savedData', data);
provide('cachedData', cachedData);
const mainArea = shallowRef<HTMLElement | null>(null); const mainArea = shallowRef<HTMLElement | null>(null);
const spinner = shallowRef<KucSpinnerEl | null>(null); const spinner = shallowRef<KucSpinnerEl | null>(null);
onMounted(async () => { onMounted(async () => {
spinner.value?.close(); // 修复不自动挂载到节点的 bug
const savedData = kintone.plugin.app.getConfig(props.pluginId); const savedData = kintone.plugin.app.getConfig(props.pluginId);
data.buttonName = savedData?.buttonName || '集約'; data.buttonName = savedData?.buttonName || '集約';
data.joinTables = savedData?.joinTables ? JSON.parse(savedData.joinTables) : [createEmptyJoinTable()]; data.joinTables = savedData?.joinTables ? JSON.parse(savedData.joinTables) : [createEmptyJoinTable()];
nextTick(async () => { nextTick(async () => {
spinner.value?.close(); // 修复不自动挂载到节点的 bug
loading.value = true; loading.value = true;
await fetchApps(); cachedData.apps = await loadApps();
cachedData.currentAppFields = await loadAppFieldsAndLayout();
loading.value = false; loading.value = false;
}); });
}); });
@@ -55,8 +62,9 @@ watch(loading, (load) => {
watch( watch(
() => data.joinTables.length, () => data.joinTables.length,
(newLength) => { (newLength) => {
console.log(data.joinTables);
if (newLength === 1) { if (newLength === 1) {
data.joinTables[0].onConditions = []; data.joinTables[0].onConditions = [{ leftField: '', rightField: '' }];
} }
}, },
); );

View File

@@ -1,23 +1,23 @@
<template> <template>
<plugin-row class="table-area flex-row border"> <plugin-row class="table-area flex-row border">
<div class="table-main-area "> <div class="table-main-area">
<plugin-row> <plugin-row>
<plugin-dropdown :disabled="false" label="取得元アプリ" :items="apps" v-model="table.app" @change="selectApp"/> <plugin-dropdown :disabled="false" label="取得元アプリ" :items="apps" v-model="table.app" @change="selectApp" />
</plugin-row> </plugin-row>
<plugin-row> <plugin-row>
<plugin-dropdown :disabled="!table.app" label="テーブル" :items="tables" v-model="table.table" @change="select"/> <plugin-dropdown :disabled="selectedAppData.loading" label="テーブル" :items="tableOptions" v-model="table.table" />
</plugin-row> </plugin-row>
<plugin-row class="flex-row" v-if="isJoinConditionShown(table)"> <plugin-row class="flex-row" v-if="isJoinConditionShown(table)">
<plugin-label label="連結条件" /> <plugin-label label="連結条件" />
<plugin-table-connect-row connector="=" :disabled="!table.app" v-model="table.onConditions" /> <plugin-table-connect-row connector="=" v-model="table.onConditions" />
</plugin-row> </plugin-row>
<plugin-row class="flex-row"> <plugin-row class="flex-row">
<plugin-label label="取得フィールド" /> <plugin-label label="取得フィールド" />
<plugin-table-connect-row connector="→" :disabled="!table.app" v-model="table.fieldsMapping" /> <plugin-table-connect-row connector="→" v-model="table.fieldsMapping" />
</plugin-row> </plugin-row>
<plugin-row class="flex-row"> <plugin-row class="flex-row">
<plugin-label label="絞込条件" /> <plugin-label label="絞込条件" />
<plugin-table-condition-row :disabled="!table.app" v-model="table.whereConditions" /> <plugin-table-condition-row v-model="table.whereConditions" />
</plugin-row> </plugin-row>
</div> </div>
<div class="table-action-area"> <div class="table-action-area">
@@ -27,30 +27,42 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { fetchApps, fetchFields, getField, resetTable } from '@/js/helper'; import { EMPTY_OPTION, getFieldsDropdownItems, getTableFieldsDropdownItems, loadAppFieldsAndLayout, resetTable } from '@/js/helper';
import { types } from '@/js/kintone-rest-api-client'; import { types, type Layout, type Properties } from '@/js/kintone-rest-api-client';
import type { JoinTable, SavedData } from '@/types/model'; import type { CachedData, CachedSelectedAppData, FieldsInfo, JoinTable, SavedData } from '@/types/model';
import type { KucDropdownItem, KucEvent } from '@/types/my-kintone'; import type { KucDropdownItem, KucEvent } from '@/types/my-kintone';
import { inject, onMounted, ref } from 'vue'; import { computed, inject, onMounted, provide, reactive, ref, watch } from 'vue';
const savedData = inject<SavedData>('savedData') as SavedData; const savedData = inject<SavedData>('savedData') as SavedData;
const cachedData = inject<CachedData>('cachedData') as CachedData;
const props = defineProps<{ table: JoinTable }>(); const props = defineProps<{ table: JoinTable }>();
const apps = ref([] as KucDropdownItem[]); const apps = computed(() => cachedData.apps);
const tables = ref([] as KucDropdownItem[]); const tableOptions = ref([EMPTY_OPTION]);
const loading = ref(true);
onMounted(async () => { const selectedAppData: CachedSelectedAppData = reactive({
apps.value = await fetchApps(); appFields: { fields: [], layout: [] } as FieldsInfo,
table: props.table,
loading: false,
}); });
provide('selectedAppData', selectedAppData);
const selectApp = async (e: KucEvent) => { const selectApp = async (e: KucEvent) => {
loading.value = true;
const fields = await loadAppFieldsAndLayout(e.detail.value);
tableOptions.value = getTableFieldsDropdownItems(fields, types.SUBTABLE);
resetTable(props.table); resetTable(props.table);
tables.value = await fetchFields(e.detail.value, types.SUBTABLE); selectedAppData.appFields = fields;
} loading.value = false;
};
const select = async (e: KucEvent) => { watch(
console.log(getField(e.detail.value)); () => !props.table.app || loading.value,
} (val) => {
selectedAppData.loading = val;
},
);
const isJoinConditionShown = (table: JoinTable) => { const isJoinConditionShown = (table: JoinTable) => {
return savedData.joinTables[0].id !== table.id; return savedData.joinTables[0].id !== table.id;

View File

@@ -4,17 +4,20 @@
<script setup lang="ts"> <script setup lang="ts">
import type { KucDropdownItem } from '@/types/my-kintone'; import type { KucDropdownItem } from '@/types/my-kintone';
import type { WhereCondition } from '@/types/model'; import type { CachedData, CachedSelectedAppData, SavedData, WhereCondition } from '@/types/model';
import { defineProps, defineEmits } from 'vue'; import { defineProps, defineEmits, inject } from 'vue';
import { Dropdown } from 'kintone-ui-component/lib/dropdown'; import { Combobox } from 'kintone-ui-component/lib/combobox';
const props = defineProps<{ const props = defineProps<{
modelValue: WhereCondition[]; modelValue: WhereCondition[];
disabled: boolean;
}>(); }>();
const savedData = inject<SavedData>('savedData') as SavedData;
const cachedData = inject<CachedData>('cachedData') as CachedData;
const selectedAppData = inject<CachedSelectedAppData>('selectedAppData') as CachedSelectedAppData;
const renderDropdown = (cellData: string) => { const renderDropdown = (cellData: string) => {
const dropdown = new Dropdown({ const dropdown = new Combobox({
items: [ items: [
{ label: 'John Brown', value: 'john' }, { label: 'John Brown', value: 'john' },
{ label: 'Steven Gerard', value: 'steven' }, { label: 'Steven Gerard', value: 'steven' },
@@ -25,7 +28,7 @@ const renderDropdown = (cellData: string) => {
}; };
const renderCondition = (cellData: string) => { const renderCondition = (cellData: string) => {
const dropdown = new Dropdown({ const dropdown = new Combobox({
items: [ items: [
{ label: 'John Brown', value: 'john' }, { label: 'John Brown', value: 'john' },
{ label: 'Steven Gerard', value: 'steven' }, { label: 'Steven Gerard', value: 'steven' },
@@ -36,7 +39,7 @@ const renderCondition = (cellData: string) => {
}; };
const renderDynamicData = (cellData: string) => { const renderDynamicData = (cellData: string) => {
const dropdown = new Dropdown({ const dropdown = new Combobox({
items: [ items: [
{ label: 'John Brown', value: 'john' }, { label: 'John Brown', value: 'john' },
{ label: 'Steven Gerard', value: 'steven' }, { label: 'Steven Gerard', value: 'steven' },

View File

@@ -1,60 +1,74 @@
<template> <template>
<kuc-table :class-name.camel="['plugin-kuc-table']" :columns="columns" :data="modelValue" @change="changeRow"/> <kuc-table :class-name.camel="['plugin-kuc-table']" :columns="columns" :data="modelValue" @change="changeRow" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { KucDropdownItem } from '@/types/my-kintone'; import type { KucDropdownItem } from '@/types/my-kintone';
import type { FieldsJoinMapping } from '@/types/model'; import type { CachedData, CachedSelectedAppData, SavedData, FieldsJoinMapping, FieldsInfo } from '@/types/model';
import { defineProps, defineEmits } from 'vue'; import { defineProps, defineEmits, inject, computed, ref, reactive, render, h, watch, onMounted } from 'vue';
import { Dropdown } from 'kintone-ui-component/lib/dropdown'; import { Combobox } from 'kintone-ui-component/lib/combobox';
import { getFieldsDropdownItems } from '@/js/helper';
import TableCombobox from './TableCombobox.vue';
const props = defineProps<{ const props = defineProps<{
connector: string; connector: string;
modelValue: FieldsJoinMapping[]; modelValue: FieldsJoinMapping[];
disabled: boolean;
}>(); }>();
const renderDropdown = (cellData: string) => { const savedData = inject<SavedData>('savedData') as SavedData;
const dropdown = new Dropdown({ const cachedData = inject<CachedData>('cachedData') as CachedData;
items: [ const selectedAppData = inject<CachedSelectedAppData>('selectedAppData') as CachedSelectedAppData;
{ label: 'John Brown', value: 'john' }, const table = computed(() => selectedAppData.table.table);
{ label: 'Steven Gerard', value: 'steven' },
],
value: cellData,
});
return dropdown;
};
const renderConnector = () => {
return props.connector;
};
const columns = [ const columns = reactive([
{ {
title: '取得元アプリのフィールド', title: '取得元アプリのフィールド',
field: 'leftField', field: 'leftField',
render: renderDropdown, render: (cellData: string, rowData: any, rowIndex: number) => {
const container = document.createElement('div');
const vnode = h(TableCombobox, {
items: getFieldsDropdownItems(selectedAppData.appFields, table.value),
modelValue: '',
selectedAppData,
'onUpdate:modelValue': (newValue: string) => {
props.modelValue[rowIndex].leftField = newValue;
},
});
render(vnode, container);
return container;
},
}, },
{ {
title: '', title: '',
field: 'connector', field: 'connector',
render: renderConnector, render: () => {
return props.connector;
},
}, },
{ {
title: 'このアプリのフィールド', title: 'このアプリのフィールド',
field: 'rightField', field: 'rightField',
render: renderDropdown, render: (cellData: string, rowData: any, rowIndex: number) => {
const container = document.createElement('div');
const vnode = h(TableCombobox, {
items: getFieldsDropdownItems(cachedData.currentAppFields, table.value),
modelValue: '',
selectedAppData,
'onUpdate:modelValue': (newValue: string) => {
props.modelValue[rowIndex].rightField = newValue;
},
});
render(vnode, container);
return container;
},
}, },
]; ]);
const leftFields = [] as KucDropdownItem[];
const rightFields = [] as KucDropdownItem[];
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: FieldsJoinMapping[]): void; (e: 'update:modelValue', value: FieldsJoinMapping[]): void;
}>(); }>();
const changeRow = (item: FieldsJoinMapping, key: keyof FieldsJoinMapping, value: string) => { const changeRow = (item: FieldsJoinMapping, key: keyof FieldsJoinMapping, value: string) => {};
};
const changeField = (item: FieldsJoinMapping, key: keyof FieldsJoinMapping, value: string) => { const changeField = (item: FieldsJoinMapping, key: keyof FieldsJoinMapping, value: string) => {
item[key] = value; item[key] = value;

View File

@@ -0,0 +1,24 @@
<template>
<kuc-combobox :items="items" :value="modelValue" @change="updateValue" :disabled="selectedAppData.loading == undefined ? false : selectedAppData.loading" />
</template>
<script setup lang="ts">
import type { CachedData, CachedSelectedAppData, SavedData } from '@/types/model';
import type { KucEvent } from '@/types/my-kintone';
import type { DropdownItem } from 'kintone-ui-component';
import { defineProps, defineEmits, ref, inject, watch, onMounted } from 'vue';
const props = defineProps<{
items: DropdownItem[];
modelValue: string;
selectedAppData: CachedSelectedAppData;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
}>();
const updateValue = (event: KucEvent) => {
emit('update:modelValue', event.detail.value);
};
</script>

View File

@@ -1,6 +1,6 @@
import type { JoinTable } from '@/types/model'; import type { FieldsInfo, JoinTable } from '@/types/model';
import type { KucDropdownItem } from '@/types/my-kintone'; import type { KucDropdownItem } from '@/types/my-kintone';
import { client, isType, type App, type Properties, type OneOf } from './kintone-rest-api-client'; import { client, isType, type OneOf, type App, type Layout } from './kintone-rest-api-client';
export const condition = { export const condition = {
unset: '', unset: '',
@@ -18,50 +18,87 @@ export function createEmptyJoinTable(id = Number(new Date())) {
} }
export function resetTable(table: JoinTable) { export function resetTable(table: JoinTable) {
table.table = '';
table.whereConditions = [{ field: '', condition: condition.unset, data: '' }]; table.whereConditions = [{ field: '', condition: condition.unset, data: '' }];
table.onConditions = [{ leftField: '', rightField: '' }]; table.onConditions = [{ leftField: '', rightField: '' }];
table.fieldsMapping = [{ leftField: '', rightField: '' }]; table.fieldsMapping = [{ leftField: '', rightField: '' }];
} }
export const EMPTY_OPTION = {
value: "",
label: "--------",
} as KucDropdownItem;
const LIMIT = 100; // 每次请求的最大应用数量 const LIMIT = 100; // 每次请求的最大应用数量
let allAppsCache: KucDropdownItem[] = []; export const loadApps = async (offset = 0, _apps: KucDropdownItem[] = []): Promise<KucDropdownItem[]> => {
let allFieldsCache: Properties = {};
export const fetchApps = async (offset = 0, _apps: KucDropdownItem[] = []): Promise<KucDropdownItem[]> => {
if (allAppsCache.length) return allAppsCache;
const { apps } = await client.app.getApps({ limit: LIMIT, offset }); const { apps } = await client.app.getApps({ limit: LIMIT, offset });
const allApps: KucDropdownItem[] = [ const allApps: KucDropdownItem[] = [
..._apps, ..._apps,
...apps.map((app: App) => ({ value: app.appId, label: app.name + 'ID: ' + app.appId + '' })), ...apps.map((app: App) => ({ value: app.appId, label: app.name + 'ID: ' + app.appId + '' })),
]; ];
if (apps.length === LIMIT) { if (apps.length === LIMIT) {
return fetchApps(offset + LIMIT, allApps); return loadApps(offset + LIMIT, allApps);
} }
allAppsCache = allApps.sort((a, b) => Number(b.value) - Number(a.value)); allApps.sort((a, b) => Number(b.value) - Number(a.value));
allApps.unshift(EMPTY_OPTION);
return allApps; return allApps;
}; };
export const fetchFields = async (appId: string, filter: OneOf['type']) => { export const loadAppFieldsAndLayout = async (appId: string | number = kintone.app.getId() as number) => {
let properties = allFieldsCache; return {
if (!allFieldsCache.length) { fields: (await client.app.getFormFields({ app: appId })).properties,
properties = (await client.app.getFormFields({ app: appId })).properties; layout: (await client.app.getFormLayout({ app: appId })).layout,
allFieldsCache = properties; } as FieldsInfo;
};
export const getFieldsDropdownItems = (
{ fields, layout }: FieldsInfo,
subTable?: string,
filterType?: OneOf['type'],
) => {
let fieldOrder: string[];
if (subTable) {
const subTableFields = layout.find((each) => each.type === 'SUBTABLE' && each.code === subTable) as any;
fieldOrder = subTableFields?.fields.map((field: { code: string }) => field.code) || [];
} else {
fieldOrder = extractNoSubTableFields(layout);
} }
const labels = Object.keys(properties).reduce((acc, fieldCode) => { return fieldOrder.reduce((acc, fieldCode) => {
const field = properties[fieldCode]; const field = fields[fieldCode];
if (filter && !isType[filter](field)) return acc; if (filterType && !isType[filterType](field)) return acc;
acc.push({ acc.push({
value: fieldCode, value: fieldCode,
label: field.label + 'FC: ' + fieldCode + '', label: field.label + 'FC: ' + fieldCode + '',
}); });
return acc; return acc;
}, [] as KucDropdownItem[]); }, [EMPTY_OPTION]);
return labels;
}; };
export const getField = (fieldCode: string) => { export const getTableFieldsDropdownItems = ({ fields }: FieldsInfo, filterType?: OneOf['type']) => {
const field = allFieldsCache[fieldCode]; return Object.keys(fields).reduce((acc, fieldCode) => {
if (isType.SUBTABLE(field)) { const field = fields[fieldCode];
console.log(field.fields); if (filterType && !isType[filterType](field)) return acc;
} acc.push({
return field; value: fieldCode,
label: field.label + 'FC: ' + fieldCode + '',
});
return acc;
}, [EMPTY_OPTION]);
}; };
const extractNoSubTableFields = (layout: Layout) => {
return layout.reduce((acc, each) => {
if (each.type === 'SUBTABLE') {
return acc;
} else if (each.type === 'ROW') {
acc.push(...each.fields.map((field: any) => field.code));
} else if (each.type === 'GROUP') {
acc.push(...extractNoSubTableFields(each.layout));
}
return acc;
}, [] as string[]);
};
// if (isType.SUBTABLE(field)) {
// console.log(field.fields);
// }

View File

@@ -8,6 +8,7 @@ export type App = {
}; };
export type Properties = Awaited<ReturnType<typeof client.app.getFormFields>>['properties']; 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 OneOf = Properties[string];
const typeNames = [ const typeNames = [

View File

@@ -1,4 +1,7 @@
import { Layout } from './../js/kintone-rest-api-client';
import type { Layout } from '@/js/kintone-rest-api-client';
import { condition } from './helper'; import { condition } from './helper';
import type { KucDropdownItem } from './my-kintone';
export interface FieldsJoinMapping { export interface FieldsJoinMapping {
leftField: string; leftField: string;
@@ -24,4 +27,20 @@ export interface JoinTable {
export interface SavedData { export interface SavedData {
buttonName: string; buttonName: string;
joinTables: JoinTable[]; joinTables: JoinTable[];
} }
export interface FieldsInfo {
fields: Properties;
layout: Layout;
}
export interface CachedData {
apps: KucDropdownItem[],
currentAppFields: FieldsInfo,
}
export interface CachedSelectedAppData {
appFields: FieldsInfo,
loading: boolean,
table: JoinTable,
}

View File

@@ -66,6 +66,7 @@ export default defineConfig({
}, },
}, },
build: { build: {
sourcemap: 'inline',
rollupOptions: { rollupOptions: {
input: { input: {
config: path.resolve(__dirname, 'index.html'), config: path.resolve(__dirname, 'index.html'),