Skip to content

Commit

Permalink
pref: implement business selector using new selector component (#6525)
Browse files Browse the repository at this point in the history
#### What type of PR is this?

/kind improvement
/area ui
/milestone 2.19.x

#### What this PR does / why we need it:

使用 #6473 中重构的 Formkit Select 组件来实现用户、文章、页面等各种业务搜索组件。 

Fixes #4931

#### How to test it?

测试各类搜索组件是否正常可用。
测试从旧版本升级后,原有数据是否可以正常显示。

#### Does this PR introduce a user-facing change?
```release-note
使用重构的 Formkit Select 组件来实现业务选择器。
```
  • Loading branch information
LIlGG authored Aug 27, 2024
1 parent a5c6d66 commit 2815678
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 85 deletions.
20 changes: 11 additions & 9 deletions ui/src/formkit/inputs/attachment-group-select.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
import { defaultIcon, select, selects } from "@formkit/inputs";
import { coreApiClient } from "@halo-dev/api-client";
import { select } from "./select";

function optionsHandler(node: FormKitNode) {
node.on("created", async () => {
Expand All @@ -9,18 +9,20 @@ function optionsHandler(node: FormKitNode) {
sort: ["metadata.creationTimestamp,desc"],
});

node.props.options = data.items.map((group) => {
return {
value: group.metadata.name,
label: group.spec.displayName,
};
});
if (node.context) {
node.context.attrs.options = data.items.map((group) => {
return {
value: group.metadata.name,
label: group.spec.displayName,
};
});
}
});
}

export const attachmentGroupSelect: FormKitTypeDefinition = {
...select,
props: ["placeholder"],
forceTypeProp: "nativeSelect",
features: [optionsHandler, selects, defaultIcon("select", "select")],
forceTypeProp: "select",
features: [optionsHandler],
};
20 changes: 11 additions & 9 deletions ui/src/formkit/inputs/attachment-policy-select.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
import { defaultIcon, select, selects } from "@formkit/inputs";
import { coreApiClient } from "@halo-dev/api-client";
import { select } from "./select";

function optionsHandler(node: FormKitNode) {
node.on("created", async () => {
const { data } = await coreApiClient.storage.policy.listPolicy();

node.props.options = data.items.map((policy) => {
return {
value: policy.metadata.name,
label: policy.spec.displayName,
};
});
if (node.context) {
node.context.attrs.options = data.items.map((policy) => {
return {
value: policy.metadata.name,
label: policy.spec.displayName,
};
});
}
});
}

export const attachmentPolicySelect: FormKitTypeDefinition = {
...select,
props: ["placeholder"],
forceTypeProp: "nativeSelect",
features: [optionsHandler, selects, defaultIcon("select", "select")],
forceTypeProp: "select",
features: [optionsHandler],
};
20 changes: 11 additions & 9 deletions ui/src/formkit/inputs/menu-item-select.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
import { defaultIcon, select, selects } from "@formkit/inputs";
import { coreApiClient } from "@halo-dev/api-client";
import { select } from "./select";

function optionsHandler(node: FormKitNode) {
node.on("created", async () => {
const { data } = await coreApiClient.menuItem.listMenuItem({
fieldSelector: [`name=(${node.props.menuItems.join(",")})`],
});

node.props.options = data.items.map((menuItem) => {
return {
value: menuItem.metadata.name,
label: menuItem.status?.displayName,
};
});
if (node.context) {
node.context.attrs.options = data.items.map((menuItem) => {
return {
value: menuItem.metadata.name,
label: menuItem.status?.displayName,
};
});
}
});
}

export const menuItemSelect: FormKitTypeDefinition = {
...select,
props: ["placeholder", "menuItems"],
forceTypeProp: "nativeSelect",
features: [optionsHandler, selects, defaultIcon("select", "select")],
forceTypeProp: "select",
features: [optionsHandler],
};
62 changes: 49 additions & 13 deletions ui/src/formkit/inputs/post-select.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,65 @@
import { postLabels } from "@/constants/labels";
import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
import { defaultIcon, select, selects } from "@formkit/inputs";
import { consoleApiClient } from "@halo-dev/api-client";
import { select } from "./select";

function optionsHandler(node: FormKitNode) {
node.on("created", async () => {
const { data } = await consoleApiClient.content.post.listPosts({
labelSelector: [
`${postLabels.DELETED}=false`,
`${postLabels.PUBLISHED}=true`,
],
});
async function search({ page, size, keyword }) {
const { data } = await consoleApiClient.content.post.listPosts({
page,
size,
keyword,
labelSelector: [
`${postLabels.DELETED}=false`,
`${postLabels.PUBLISHED}=true`,
],
});

node.props.options = data.items.map((post) => {
return {
options: data.items.map((post) => {
return {
value: post.post.metadata.name,
label: post.post.spec.title,
};
});
}),
total: data.total,
size: data.size,
page: data.page,
};
}

async function findOptionsByValues(values: string[]) {
if (values.length === 0) {
return [];
}

const { data } = await consoleApiClient.content.post.listPosts({
fieldSelector: [`metadata.name=(${values.join(",")})`],
});

return data.items.map((post) => {
return {
value: post.post.metadata.name,
label: post.post.spec.title,
};
});
}

function optionsHandler(node: FormKitNode) {
node.on("created", async () => {
node.props = {
...node.props,
remote: true,
remoteOption: {
search,
findOptionsByValues,
},
};
});
}

export const postSelect: FormKitTypeDefinition = {
...select,
props: ["placeholder"],
forceTypeProp: "nativeSelect",
features: [optionsHandler, selects, defaultIcon("select", "select")],
forceTypeProp: "select",
features: [optionsHandler],
};
9 changes: 6 additions & 3 deletions ui/src/formkit/inputs/role-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { rbacAnnotations } from "@/constants/annotations";
import { roleLabels } from "@/constants/labels";
import { i18n } from "@/locales";
import type { FormKitNode, FormKitTypeDefinition } from "@formkit/core";
import { defaultIcon, select, selects } from "@formkit/inputs";
import { coreApiClient } from "@halo-dev/api-client";
import { select } from "./select";

function optionsHandler(node: FormKitNode) {
node.on("created", async () => {
Expand All @@ -13,7 +13,7 @@ function optionsHandler(node: FormKitNode) {
labelSelector: [`!${roleLabels.TEMPLATE}`],
});

node.props.options = [
const options = [
{
label: i18n.global.t(
"core.user.grant_permission_modal.fields.role.placeholder"
Expand All @@ -29,12 +29,15 @@ function optionsHandler(node: FormKitNode) {
};
}),
];
if (node.context) {
node.context.attrs.options = options;
}
});
}

export const roleSelect: FormKitTypeDefinition = {
...select,
props: ["placeholder"],
forceTypeProp: "nativeSelect",
features: [optionsHandler, selects, defaultIcon("select", "select")],
features: [optionsHandler],
};
62 changes: 42 additions & 20 deletions ui/src/formkit/inputs/select/SelectMain.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SelectContainer from "./SelectContainer.vue";
import { axiosInstance } from "@halo-dev/api-client";
import { get, has, type PropertyPath } from "lodash-es";
import { useDebounceFn } from "@vueuse/core";
import { useFuse } from "@vueuse/integrations/useFuse";
import type { AxiosRequestConfig } from "axios";
export interface SelectProps {
Expand Down Expand Up @@ -184,7 +185,6 @@ const selectProps: SelectProps = shallowReactive({
placeholder: "",
});
const hasSelected = ref(false);
const isRemote = computed(() => !!selectProps.action || !!selectProps.remote);
const hasMoreOptions = computed(
() => options.value && options.value.length < total.value
Expand Down Expand Up @@ -400,7 +400,7 @@ const mapUnresolvedOptions = async (
value: string;
}>
> => {
if (!selectProps.action || !selectProps.remote) {
if (!isRemote.value) {
if (selectProps.allowCreate) {
// TODO: Add mapped values to options
return unmappedSelectValues.map((value) => ({ label: value, value }));
Expand All @@ -413,17 +413,29 @@ const mapUnresolvedOptions = async (
}
// Asynchronous request for options, fetch label and value via API.
let mappedOptions: Array<{
label: string;
value: string;
}> = [];
if (selectProps.action) {
mappedOptions = await fetchRemoteMappedOptions(unmappedSelectValues);
} else if (selectProps.remote) {
const remoteOption = selectProps.remoteOption as SelectRemoteOption;
mappedOptions = await remoteOption.findOptionsByValues(
unmappedSelectValues
let mappedOptions:
| Array<{
label: string;
value: string;
}>
| undefined = undefined;
if (noNeedFetchOptions.value) {
mappedOptions = cacheAllOptions.value?.filter((option) =>
unmappedSelectValues.includes(option.value)
);
} else {
if (selectProps.action) {
mappedOptions = await fetchRemoteMappedOptions(unmappedSelectValues);
} else if (selectProps.remote) {
const remoteOption = selectProps.remoteOption as SelectRemoteOption;
mappedOptions = await remoteOption.findOptionsByValues(
unmappedSelectValues
);
}
}
if (!mappedOptions) {
return unmappedSelectValues.map((value) => ({ label: value, value }));
}
// Get values that are still unresolved.
const unmappedValues = unmappedSelectValues.filter(
Expand Down Expand Up @@ -496,11 +508,12 @@ onMounted(async () => {
}
});
watch(
const stopSelectedWatch = watch(
() => [options.value, props.context.value],
async () => {
if (!hasSelected.value && options.value) {
selectOptions.value = await fetchSelectedOptions();
if (options.value) {
const selectedOption = await fetchSelectedOptions();
selectOptions.value = selectedOption;
}
},
{
Expand All @@ -521,7 +534,7 @@ watch(
const handleUpdate = (value: Array<{ label: string; value: string }>) => {
const values = value.map((item) => item.value);
hasSelected.value = true;
stopSelectedWatch();
selectOptions.value = value;
if (selectProps.multiple) {
props.context.node.input(values);
Expand All @@ -544,14 +557,23 @@ const fetchOptions = async (
}
// If the total number of options is less than the page size, no more requests are made.
if (noNeedFetchOptions.value) {
const filterOptions = cacheAllOptions.value?.filter((option) =>
option.label.includes(tempKeyword)
);
const { results } = useFuse<{
label: string;
value: string;
}>(tempKeyword, cacheAllOptions.value || [], {
fuseOptions: {
keys: ["label", "value"],
threshold: 0,
ignoreLocation: true,
},
matchAllWhenSearchEmpty: true,
});
const filterOptions = results.value?.map((fuseItem) => fuseItem.item) || [];
return {
options: filterOptions || [],
page: page.value,
size: size.value,
total: filterOptions?.length || 0,
total: filterOptions.length || 0,
};
}
isLoading.value = true;
Expand Down
Loading

0 comments on commit 2815678

Please sign in to comment.