From aaf50f5441db4ca45bb84572e8dea00db6d451fe Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 22 May 2026 11:00:13 +0800 Subject: [PATCH] update --- .../[appInstanceId]/access/page.tsx | 8 + .../deployments/[appInstanceId]/api/page.tsx | 8 + .../deployments/detail/access-tab.tsx | 15 ++ web/features/deployments/detail/common.tsx | 6 +- .../deployments/detail/deployment-sidebar.tsx | 14 ++ .../deployments/detail/developer-api-tab.tsx | 13 ++ web/features/deployments/detail/index.tsx | 14 +- .../deployments/detail/settings-tab.tsx | 6 - .../detail/settings-tab/access/api-keys.tsx | 87 +++++-- .../settings-tab/access/channels-section.tsx | 213 ++++++++++-------- .../access/developer-api-section.tsx | 97 ++++---- .../access/permissions-section.tsx | 16 +- .../settings-tab/access/permissions.tsx | 56 +++-- web/features/deployments/detail/tabs.ts | 2 +- .../deployments/list/instance-card.tsx | 29 ++- web/i18n/en-US/deployments.json | 16 +- web/i18n/zh-Hans/deployments.json | 16 +- 17 files changed, 408 insertions(+), 208 deletions(-) create mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx create mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/api/page.tsx create mode 100644 web/features/deployments/detail/access-tab.tsx create mode 100644 web/features/deployments/detail/developer-api-tab.tsx diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx new file mode 100644 index 0000000000..5fa2ff4225 --- /dev/null +++ b/web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx @@ -0,0 +1,8 @@ +import { AccessTab } from '@/features/deployments/detail/access-tab' + +export default async function InstanceDetailAccessPage({ params }: { + params: Promise<{ appInstanceId: string }> +}) { + const { appInstanceId } = await params + return +} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/api/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/api/page.tsx new file mode 100644 index 0000000000..39c67f0eb4 --- /dev/null +++ b/web/app/(commonLayout)/deployments/[appInstanceId]/api/page.tsx @@ -0,0 +1,8 @@ +import { DeveloperApiTab } from '@/features/deployments/detail/developer-api-tab' + +export default async function InstanceDetailApiPage({ params }: { + params: Promise<{ appInstanceId: string }> +}) { + const { appInstanceId } = await params + return +} diff --git a/web/features/deployments/detail/access-tab.tsx b/web/features/deployments/detail/access-tab.tsx new file mode 100644 index 0000000000..5bd82d4526 --- /dev/null +++ b/web/features/deployments/detail/access-tab.tsx @@ -0,0 +1,15 @@ +'use client' + +import { AccessChannelsSection } from './settings-tab/access/channels-section' +import { AccessPermissionsSection } from './settings-tab/access/permissions-section' + +export function AccessTab({ appInstanceId }: { + appInstanceId: string +}) { + return ( +
+ + +
+ ) +} diff --git a/web/features/deployments/detail/common.tsx b/web/features/deployments/detail/common.tsx index 1485caaae6..131ef5adfe 100644 --- a/web/features/deployments/detail/common.tsx +++ b/web/features/deployments/detail/common.tsx @@ -10,6 +10,7 @@ type SectionProps = { children: ReactNode layout?: 'block' | 'row' tone?: 'default' | 'destructive' + showDivider?: boolean } export function SectionState({ children }: { @@ -39,6 +40,7 @@ export function Section({ children, layout = 'block', tone = 'default', + showDivider = true, }: SectionProps) { const titleClassName = cn( 'system-sm-semibold', @@ -55,7 +57,7 @@ export function Section({ if (layout === 'row') { return ( -
+
@@ -87,7 +89,7 @@ export function Section({ } return ( -
+
diff --git a/web/features/deployments/detail/deployment-sidebar.tsx b/web/features/deployments/detail/deployment-sidebar.tsx index aab6678c8f..43d805dd76 100644 --- a/web/features/deployments/detail/deployment-sidebar.tsx +++ b/web/features/deployments/detail/deployment-sidebar.tsx @@ -53,6 +53,18 @@ function VersionsIcon({ className }: TailwindNavIconProps) { function VersionsSelectedIcon({ className }: TailwindNavIconProps) { return } +function AccessIcon({ className }: TailwindNavIconProps) { + return +} +function AccessSelectedIcon({ className }: TailwindNavIconProps) { + return +} +function ApiIcon({ className }: TailwindNavIconProps) { + return +} +function ApiSelectedIcon({ className }: TailwindNavIconProps) { + return +} function SettingsIcon({ className }: TailwindNavIconProps) { return } @@ -64,6 +76,8 @@ const TABS: TabDef[] = [ { key: 'overview', icon: OverviewIcon, selectedIcon: OverviewSelectedIcon }, { key: 'deploy', icon: DeployIcon, selectedIcon: DeploySelectedIcon }, { key: 'releases', icon: VersionsIcon, selectedIcon: VersionsSelectedIcon }, + { key: 'access', icon: AccessIcon, selectedIcon: AccessSelectedIcon }, + { key: 'api', icon: ApiIcon, selectedIcon: ApiSelectedIcon }, { key: 'settings', icon: SettingsIcon, selectedIcon: SettingsSelectedIcon }, ] diff --git a/web/features/deployments/detail/developer-api-tab.tsx b/web/features/deployments/detail/developer-api-tab.tsx new file mode 100644 index 0000000000..d2bde2d848 --- /dev/null +++ b/web/features/deployments/detail/developer-api-tab.tsx @@ -0,0 +1,13 @@ +'use client' + +import { DeveloperApiSection } from './settings-tab/access/developer-api-section' + +export function DeveloperApiTab({ appInstanceId }: { + appInstanceId: string +}) { + return ( +
+ +
+ ) +} diff --git a/web/features/deployments/detail/index.tsx b/web/features/deployments/detail/index.tsx index 486759ee79..ce8324f920 100644 --- a/web/features/deployments/detail/index.tsx +++ b/web/features/deployments/detail/index.tsx @@ -7,6 +7,7 @@ import useDocumentTitle from '@/hooks/use-document-title' import { useSelectedLayoutSegment } from '@/next/navigation' import { DeployDrawer } from '../components/deploy-drawer' import { DeploymentSidebar } from './deployment-sidebar' +import { DeveloperApiHeaderActions } from './settings-tab/access/developer-api-section' import { isInstanceDetailTabKey } from './tabs' export function InstanceDetail({ appInstanceId, children }: { @@ -27,8 +28,17 @@ export function InstanceDetail({ appInstanceId, children }: {
-
{t(`tabs.${activeTab}.name`)}
-
{t(`tabs.${activeTab}.description`)}
+
+
+
{t(`tabs.${activeTab}.name`)}
+
{t(`tabs.${activeTab}.description`)}
+
+ {activeTab === 'api' && ( +
+ +
+ )} +
{children}
diff --git a/web/features/deployments/detail/settings-tab.tsx b/web/features/deployments/detail/settings-tab.tsx index 635fbf1931..50a3f4b926 100644 --- a/web/features/deployments/detail/settings-tab.tsx +++ b/web/features/deployments/detail/settings-tab.tsx @@ -22,9 +22,6 @@ import { useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/client' import { isUndeployedDeploymentRow } from '../runtime-status' import { Section, SectionState } from './common' -import { AccessChannelsSection } from './settings-tab/access/channels-section' -import { DeveloperApiSection } from './settings-tab/access/developer-api-section' -import { AccessPermissionsSection } from './settings-tab/access/permissions-section' type AppInstanceWithId = AppInstanceBasicInfo & { id: string } @@ -404,9 +401,6 @@ export function SettingsTab({ appInstanceId }: { }) { return (
- - -
diff --git a/web/features/deployments/detail/settings-tab/access/api-keys.tsx b/web/features/deployments/detail/settings-tab/access/api-keys.tsx index bb62b36203..c61bb03de5 100644 --- a/web/features/deployments/detail/settings-tab/access/api-keys.tsx +++ b/web/features/deployments/detail/settings-tab/access/api-keys.tsx @@ -38,26 +38,79 @@ function ApiKeyRow({ appInstanceId, apiKey }: { } return ( -
-
- {apiKey.name || apiKey.id} - - {t('access.api.envPrefix', { env: environmentLabel })} - -
-
-
- {displayValue} + + +
+ {apiKey.name || apiKey.id}
+ + + + {environmentLabel} + + + +
+
+ {displayValue} +
+
+ + -
+ + + ) +} + +function ApiKeyTableHeader() { + const { t } = useTranslation('deployments') + + return ( + + + + {t('access.api.table.name')} + + + {t('access.api.table.environment')} + + + {t('access.api.table.key')} + + + {t('access.api.table.action')} + + + + ) +} + +function ApiKeyTable({ appInstanceId, apiKeys }: { + appInstanceId: string + apiKeys: DeveloperApiKeyRow[] +}) { + return ( +
+ + + + {apiKeys.map(apiKey => ( + + ))} + +
) } @@ -67,15 +120,7 @@ export function ApiKeyList({ appInstanceId, apiKeys }: { apiKeys: DeveloperApiKeyRow[] }) { return ( -
- {apiKeys.map(apiKey => ( - - ))} -
+ ) } diff --git a/web/features/deployments/detail/settings-tab/access/channels-section.tsx b/web/features/deployments/detail/settings-tab/access/channels-section.tsx index cd0a4bdbca..7742269ff8 100644 --- a/web/features/deployments/detail/settings-tab/access/channels-section.tsx +++ b/web/features/deployments/detail/settings-tab/access/channels-section.tsx @@ -1,5 +1,6 @@ 'use client' +import type { ReactNode } from 'react' import { Switch, SwitchSkeleton } from '@langgenius/dify-ui/switch' import { useMutation, useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' @@ -12,8 +13,8 @@ import { CopyPill, EndpointRow } from './common' import { getUrlOrigin } from './url' const ACCESS_CHANNEL_SKELETON_SECTIONS = [ - { key: 'webapp', className: 'flex flex-col gap-2' }, - { key: 'cli', className: 'flex flex-col gap-2 border-t border-divider-subtle pt-3' }, + { key: 'webapp' }, + { key: 'cli' }, ] function AccessChannelsSwitch({ appInstanceId, checked, disabled }: { @@ -39,28 +40,55 @@ function AccessChannelsSwitch({ appInstanceId, checked, disabled }: { function AccessChannelsSkeleton() { return ( -
+
{ACCESS_CHANNEL_SKELETON_SECTIONS.map(section => ( -
- +
- - - - - - - - -
+ +
+ + ))}
) } +function ChannelStatusBadge() { + const { t } = useTranslation('deployments') + + return ( + + {t('access.channels.followPermission')} + + ) +} + +function ChannelInfo({ icon, title, description, status }: { + icon: ReactNode + title: string + description: string + status: ReactNode +}) { + return ( +
+ + {icon} + +
+
+ {title} + {status} +
+
{description}
+
+
+ ) +} + export function AccessChannelsSection({ appInstanceId, }: { @@ -82,7 +110,6 @@ export function AccessChannelsSection({
@@ -101,90 +128,78 @@ export function AccessChannelsSection({ ? {t('common.loadFailed')} : runEnabled ? ( -
-
-
-
-
- {t('access.runAccess.webapp')} -
- - {t('access.channels.followPermission')} - -
-
- {t('access.runAccess.webappDesc')} -
- {webappRows.length > 0 - ? ( -
- {webappRows.map((row) => { - const endpointUrl = webappUrl(row.url) +
+
+
-
-
-
- {t('access.cli.title')} -
- - {t('access.channels.followPermission')} - -
-
- {t('access.cli.description')} -
- {cliDomain - ? ( - - ) - : ( -
- {t('access.cli.empty')} -
- )} -
+ return ( + + ) + })} +
+ ) + : ( + + {t('access.runAccess.webappEmpty')} + + )} +
+
+
) diff --git a/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx b/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx index 6ca0931b00..957ac908e2 100644 --- a/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx +++ b/web/features/deployments/detail/settings-tab/access/developer-api-section.tsx @@ -6,11 +6,11 @@ import type { } from '@dify/contracts/enterprise/types.gen' import { Switch, SwitchSkeleton } from '@langgenius/dify-ui/switch' import { useMutation, useQuery } from '@tanstack/react-query' -import { useState } from 'react' +import { atom, useAtom, useSetAtom } from 'jotai' import { useTranslation } from 'react-i18next' import { SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton' import { consoleQuery } from '@/service/client' -import { Section, SectionState } from '../../common' +import { SectionState } from '../../common' import { ApiKeyGenerateMenu, ApiKeyList } from './api-keys' import { CopyPill } from './common' @@ -19,6 +19,8 @@ type CreatedApiToken = { token: string } +const createdApiTokenAtom = atom(undefined) + const DEVELOPER_API_KEY_SKELETON_KEYS = ['primary-key', 'secondary-key'] function permissionEnvironment(row: EnvironmentAccessRow): AppDeployEnvironment | undefined { @@ -46,6 +48,50 @@ function DeveloperApiSwitch({ appInstanceId, checked, disabled }: { ) } +export function DeveloperApiHeaderActions({ appInstanceId }: { + appInstanceId: string +}) { + const setCreatedApiToken = useSetAtom(createdApiTokenAtom) + const accessConfigQuery = useQuery(consoleQuery.enterprise.appDeployAccessService.getAppInstanceAccess.queryOptions({ + input: { + params: { appInstanceId }, + }, + })) + const accessConfig = accessConfigQuery.data + const apiEnabled = accessConfig?.developerApi?.enabled ?? false + const apiKeys = accessConfig?.developerApi?.apiKeys ?? [] + const environments = accessConfig?.permissions + ?.map(permissionEnvironment) + .filter((environment): environment is AppDeployEnvironment => Boolean(environment)) ?? [] + + if (accessConfigQuery.isLoading) { + return ( +
+ + +
+ ) + } + + return ( +
+ {apiEnabled && ( + setCreatedApiToken({ appInstanceId, token })} + /> + )} + +
+ ) +} + function CreatedApiTokenCard({ token, onDismiss }: { token: string onDismiss: () => void @@ -82,18 +128,18 @@ function CreatedApiTokenCard({ token, onDismiss }: { function DeveloperApiSkeleton() { return ( -
+
- +
-
+
{DEVELOPER_API_KEY_SKELETON_KEYS.map(key => ( - +
@@ -112,7 +158,7 @@ export function DeveloperApiSection({ appInstanceId: string }) { const { t } = useTranslation('deployments') - const [createdApiToken, setCreatedApiToken] = useState() + const [createdApiToken, setCreatedApiToken] = useAtom(createdApiTokenAtom) const accessConfigQuery = useQuery(consoleQuery.enterprise.appDeployAccessService.getAppInstanceAccess.queryOptions({ input: { params: { appInstanceId }, @@ -130,51 +176,20 @@ export function DeveloperApiSection({ : undefined return ( -
- : ( - - ) - )} - > + <> {accessConfigQuery.isLoading ? : accessConfigQuery.isError ? {t('common.loadFailed')} : apiEnabled ? ( -
+
{apiUrl && ( )} -
-
- - {t('access.api.backendTitle')} - - - {t('access.api.keyList')} - -
- setCreatedApiToken({ appInstanceId, token })} - /> -
{visibleCreatedApiToken && ( )} -
+ ) } diff --git a/web/features/deployments/detail/settings-tab/access/permissions-section.tsx b/web/features/deployments/detail/settings-tab/access/permissions-section.tsx index c4b2617ec4..7e79fd6e62 100644 --- a/web/features/deployments/detail/settings-tab/access/permissions-section.tsx +++ b/web/features/deployments/detail/settings-tab/access/permissions-section.tsx @@ -20,11 +20,15 @@ function hasEnvironment(row: EnvironmentAccessRow): row is EnvironmentAccessRow function AccessPermissionsSkeleton() { return ( -
+
{ACCESS_PERMISSIONS_SKELETON_KEYS.map(key => ( - - - + + + + ))}
@@ -49,7 +53,7 @@ export function AccessPermissionsSection({
{accessConfigQuery.isLoading ? @@ -62,7 +66,7 @@ export function AccessPermissionsSection({ ) : ( -
+
{permissionRows.map(row => ( - - {environmentName(environment)} - +
+
+
+ {t('access.permissions.col.environment')} +
+
+
+
+
+ {t('access.permissions.col.permission')} +
- {permissionKind === 'specific' && ( -
- - {subjects.length === 0 && ( - - {t('access.members.emptySelection')} - - )} +
+
+ {t('access.permissions.col.subjects')}
- )} + {permissionKind === 'specific' + ? ( + <> + + {subjects.length === 0 && ( + + {t('access.members.emptySelection')} + + )} + + ) + : ( +
+ {t(`access.permission.${permissionKind}Desc`)} +
+ )} +
) } diff --git a/web/features/deployments/detail/tabs.ts b/web/features/deployments/detail/tabs.ts index d0ec75d63b..304a4919d9 100644 --- a/web/features/deployments/detail/tabs.ts +++ b/web/features/deployments/detail/tabs.ts @@ -1,4 +1,4 @@ -export const INSTANCE_DETAIL_TAB_KEYS = ['overview', 'deploy', 'releases', 'settings'] as const +export const INSTANCE_DETAIL_TAB_KEYS = ['overview', 'deploy', 'releases', 'access', 'api', 'settings'] as const export type InstanceDetailTabKey = typeof INSTANCE_DETAIL_TAB_KEYS[number] diff --git a/web/features/deployments/list/instance-card.tsx b/web/features/deployments/list/instance-card.tsx index ed23d68799..94a7a9750f 100644 --- a/web/features/deployments/list/instance-card.tsx +++ b/web/features/deployments/list/instance-card.tsx @@ -244,26 +244,29 @@ function DeploymentAccessLinks({ appInstanceId, access, isLoading }: { const links = [ access?.accessChannelsEnabled && access.webappUrl ? { - href: getInstanceTabHref(appInstanceId, 'overview'), + key: 'webapp', + href: getInstanceTabHref(appInstanceId, 'access'), label: t('card.access.webApp'), icon: 'i-ri-global-line', } : undefined, access?.accessChannelsEnabled && access.cliUrl ? { - href: getInstanceTabHref(appInstanceId, 'deploy'), + key: 'cli', + href: getInstanceTabHref(appInstanceId, 'access'), label: t('card.access.cli'), icon: 'i-ri-terminal-box-line', } : undefined, access?.developerApiEnabled && access.apiUrl ? { - href: getInstanceTabHref(appInstanceId, 'settings'), + key: 'api', + href: getInstanceTabHref(appInstanceId, 'api'), label: t('card.access.api'), icon: 'i-ri-code-s-slash-line', } : undefined, - ].filter((link): link is { href: string, label: string, icon: string } => Boolean(link)) + ].filter((link): link is { key: string, href: string, label: string, icon: string } => Boolean(link)) if (links.length === 0) return
@@ -271,7 +274,7 @@ function DeploymentAccessLinks({ appInstanceId, access, isLoading }: { return (
{links.map(link => ( - + ) : ( -

- {description || t('card.noDescription')} -

+ description + ? ( +

+ {description} +

+ ) + :
)} diff --git a/web/i18n/en-US/deployments.json b/web/i18n/en-US/deployments.json index 53fcc50da7..e5d23566c9 100644 --- a/web/i18n/en-US/deployments.json +++ b/web/i18n/en-US/deployments.json @@ -14,7 +14,14 @@ "access.api.newTokenLabel": "Token", "access.api.newTokenTitle": "API key created", "access.api.noKeys": "No API keys yet. Create one to start calling the API.", + "access.api.table.action": "Action", + "access.api.table.environment": "Environment", + "access.api.table.key": "API key", + "access.api.table.name": "Name", "access.api.title": "API", + "access.channels.col.channel": "Channel", + "access.channels.col.endpoint": "Entry point", + "access.channels.col.status": "Status", "access.channels.description": "WebApp and CLI entry points use the access permissions above.", "access.channels.disabled": "Access channels are turned off for this instance.", "access.channels.followPermission": "Follows permissions", @@ -55,6 +62,9 @@ "access.permission.specificDesc": "Pick groups or individual members", "access.permission.specificUnavailable": "Specific member selection is disabled until real workspace subjects are connected.", "access.permission.updateFailed": "Failed to update access policy.", + "access.permissions.col.environment": "Environment", + "access.permissions.col.permission": "Access", + "access.permissions.col.subjects": "Allowed subjects", "access.permissions.description": "Configure who can access this instance in each deployed environment.", "access.permissions.title": "Access permissions", "access.revoke": "Revoke", @@ -418,13 +428,17 @@ "status.ready": "Ready", "status.unknown": "Unknown", "subtitle": "Deploy and manage your apps across environments.", + "tabs.access.description": "Configure environment permissions and WebApp or CLI access channels.", + "tabs.access.name": "Access", + "tabs.api.description": "Manage HTTP endpoint access and API keys for this instance.", + "tabs.api.name": "Developer API", "tabs.deploy.description": "Environments this instance is deployed to and their current releases.", "tabs.deploy.name": "Deploy", "tabs.overview.description": "Deploy releases and review target environments.", "tabs.overview.name": "Overview", "tabs.releases.description": "All releases for this app. Deploy any release to an environment.", "tabs.releases.name": "Releases", - "tabs.settings.description": "Access, API keys, metadata, and backend-managed settings.", + "tabs.settings.description": "Metadata and backend-managed settings for this instance.", "tabs.settings.name": "Settings", "title": "App instances", "versions.cancelCreate": "Cancel", diff --git a/web/i18n/zh-Hans/deployments.json b/web/i18n/zh-Hans/deployments.json index bcb5e2f237..aeb80956f0 100644 --- a/web/i18n/zh-Hans/deployments.json +++ b/web/i18n/zh-Hans/deployments.json @@ -14,7 +14,14 @@ "access.api.newTokenLabel": "密钥", "access.api.newTokenTitle": "API Key 已创建", "access.api.noKeys": "尚无 API 密钥,创建一个即可调用 API。", + "access.api.table.action": "操作", + "access.api.table.environment": "环境", + "access.api.table.key": "API Key", + "access.api.table.name": "名称", "access.api.title": "API", + "access.channels.col.channel": "渠道", + "access.channels.col.endpoint": "入口", + "access.channels.col.status": "状态", "access.channels.description": "WebApp 与 CLI 入口遵循上方访问权限。", "access.channels.disabled": "该实例的接入渠道已关闭。", "access.channels.followPermission": "随权限开放", @@ -55,6 +62,9 @@ "access.permission.specificDesc": "选择指定的分组或单个成员", "access.permission.specificUnavailable": "特定成员暂未启用,需接入真实工作区成员与分组后再开放。", "access.permission.updateFailed": "更新访问策略失败。", + "access.permissions.col.environment": "环境", + "access.permissions.col.permission": "访问范围", + "access.permissions.col.subjects": "授权对象", "access.permissions.description": "配置该实例在每个已部署环境中的访问人员。", "access.permissions.title": "访问权限", "access.revoke": "撤销", @@ -418,13 +428,17 @@ "status.ready": "就绪", "status.unknown": "未知", "subtitle": "在不同环境中部署和管理你的应用。", + "tabs.access.description": "配置环境访问权限,以及 WebApp 或 CLI 接入渠道。", + "tabs.access.name": "访问", + "tabs.api.description": "管理该实例的 HTTP 调用入口和 API Key。", + "tabs.api.name": "开发者 API", "tabs.deploy.description": "该实例已部署的环境及其当前发布版本。", "tabs.deploy.name": "部署", "tabs.overview.description": "部署发布版本并查看目标环境。", "tabs.overview.name": "概览", "tabs.releases.description": "此应用的所有发布版本,可将任一发布版本部署到环境。", "tabs.releases.name": "发布版本", - "tabs.settings.description": "管理接入、API Key、元数据和后端托管设置。", + "tabs.settings.description": "管理该实例的元数据和后端托管设置。", "tabs.settings.name": "设置", "title": "应用实例", "versions.cancelCreate": "取消",