@lark-apaas/coding-template-nestjs-react-fullstack 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -0
- package/README.md +41 -0
- package/hooks/after-install.mjs +3 -0
- package/hooks/after-render.mjs +3 -0
- package/hooks/before-render.mjs +3 -0
- package/package.json +36 -0
- package/template/.codebase/pipelines/ci.yaml +34 -0
- package/template/.codebase/pipelines/coco-code-review.yaml +21 -0
- package/template/.prettierrc +8 -0
- package/template/.spark_project +16 -0
- package/template/.stylelintrc.js +12 -0
- package/template/_env.local +38 -0
- package/template/_gitignore +64 -0
- package/template/_npmrc +1 -0
- package/template/client/index.html +23 -0
- package/template/client/public/favicon.svg +40 -0
- package/template/client/src/api/index.ts +19 -0
- package/template/client/src/app.tsx +22 -0
- package/template/client/src/components/Layout.tsx +11 -0
- package/template/client/src/components/business-ui/README.md +519 -0
- package/template/client/src/components/business-ui/api/chats/queries.ts +39 -0
- package/template/client/src/components/business-ui/api/chats/service.ts +29 -0
- package/template/client/src/components/business-ui/api/departments/queries.ts +42 -0
- package/template/client/src/components/business-ui/api/departments/service.ts +18 -0
- package/template/client/src/components/business-ui/api/files/service.ts +28 -0
- package/template/client/src/components/business-ui/api/user-profiles/queries.ts +42 -0
- package/template/client/src/components/business-ui/api/user-profiles/service.ts +23 -0
- package/template/client/src/components/business-ui/api/users/queries.ts +34 -0
- package/template/client/src/components/business-ui/api/users/service.ts +40 -0
- package/template/client/src/components/business-ui/chat-select/README.md +128 -0
- package/template/client/src/components/business-ui/chat-select/chat-item.tsx +85 -0
- package/template/client/src/components/business-ui/chat-select/chat-select-tag.tsx +83 -0
- package/template/client/src/components/business-ui/chat-select/chat-select.tsx +197 -0
- package/template/client/src/components/business-ui/chat-select/index.tsx +12 -0
- package/template/client/src/components/business-ui/chat-select/types.ts +102 -0
- package/template/client/src/components/business-ui/chat-select/use-chat-value.ts +180 -0
- package/template/client/src/components/business-ui/chat-select/utils.ts +81 -0
- package/template/client/src/components/business-ui/department-select/department-item.tsx +55 -0
- package/template/client/src/components/business-ui/department-select/department-select-field.tsx +86 -0
- package/template/client/src/components/business-ui/department-select/department-select-tag.tsx +56 -0
- package/template/client/src/components/business-ui/department-select/department-select.tsx +168 -0
- package/template/client/src/components/business-ui/department-select/icon-department.tsx +22 -0
- package/template/client/src/components/business-ui/department-select/index.tsx +7 -0
- package/template/client/src/components/business-ui/department-select/types.ts +15 -0
- package/template/client/src/components/business-ui/department-select/utils.ts +17 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-content.tsx +35 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-empty.tsx +28 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-error.tsx +35 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-item.tsx +55 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-list.tsx +174 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-loading.tsx +19 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-search.tsx +31 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox-trigger.tsx +281 -0
- package/template/client/src/components/business-ui/entity-combobox/base-combobox.tsx +132 -0
- package/template/client/src/components/business-ui/entity-combobox/context.tsx +19 -0
- package/template/client/src/components/business-ui/entity-combobox/entity-combobox.tsx +145 -0
- package/template/client/src/components/business-ui/entity-combobox/highlight-text.tsx +52 -0
- package/template/client/src/components/business-ui/entity-combobox/hooks.tsx +21 -0
- package/template/client/src/components/business-ui/entity-combobox/index.tsx +31 -0
- package/template/client/src/components/business-ui/entity-combobox/item-pill.tsx +179 -0
- package/template/client/src/components/business-ui/entity-combobox/popover-wrapper.tsx +149 -0
- package/template/client/src/components/business-ui/entity-combobox/search-trigger.tsx +262 -0
- package/template/client/src/components/business-ui/entity-combobox/shared-types.ts +296 -0
- package/template/client/src/components/business-ui/entity-combobox/size-variants.tsx +322 -0
- package/template/client/src/components/business-ui/entity-combobox/types.ts +246 -0
- package/template/client/src/components/business-ui/entity-combobox/use-fetch-data.tsx +81 -0
- package/template/client/src/components/business-ui/entity-combobox/use-infinite-scroll.tsx +99 -0
- package/template/client/src/components/business-ui/entity-combobox/use-popover-outside-click.tsx +32 -0
- package/template/client/src/components/business-ui/form/checkbox-field.tsx +83 -0
- package/template/client/src/components/business-ui/form/context.tsx +38 -0
- package/template/client/src/components/business-ui/form/field-layout.tsx +91 -0
- package/template/client/src/components/business-ui/form/form.tsx +54 -0
- package/template/client/src/components/business-ui/form/hooks/form-context.tsx +4 -0
- package/template/client/src/components/business-ui/form/hooks/form-utils.ts +23 -0
- package/template/client/src/components/business-ui/form/hooks/form.tsx +40 -0
- package/template/client/src/components/business-ui/form/index.tsx +4 -0
- package/template/client/src/components/business-ui/form/input-field.tsx +74 -0
- package/template/client/src/components/business-ui/form/radio-group-field.tsx +138 -0
- package/template/client/src/components/business-ui/form/select-field.tsx +155 -0
- package/template/client/src/components/business-ui/form/switch-field.tsx +66 -0
- package/template/client/src/components/business-ui/form/textarea-field.tsx +72 -0
- package/template/client/src/components/business-ui/form/types.ts +32 -0
- package/template/client/src/components/business-ui/tiptap-editor/README.md +324 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/attachment-toolbar-button.tsx +74 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/blockquote-toolbar-button.tsx +41 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/code-block-toolbar-button.tsx +41 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/color-highlight-toolbar-button.tsx +141 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/heading-toolbar-button.tsx +99 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/horizontal-rule-toolbar-button.tsx +39 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/image-upload-toolbar-button.tsx +65 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx +127 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/link-hover-toolbar.tsx +380 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx +96 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx +75 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx +118 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/text-align-toolbar-button.tsx +84 -0
- package/template/client/src/components/business-ui/tiptap-editor/components/undo-redo-toolbar-button.tsx +54 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/attachment.tsx +539 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/code-block-shiki.tsx +236 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/complete-kit.ts +260 -0
- package/template/client/src/components/business-ui/tiptap-editor/extensions/image.tsx +456 -0
- package/template/client/src/components/business-ui/tiptap-editor/hooks/use-tiptap-editor.ts +47 -0
- package/template/client/src/components/business-ui/tiptap-editor/index.ts +38 -0
- package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor-complete.tsx +112 -0
- package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor.tsx +169 -0
- package/template/client/src/components/business-ui/types/user.ts +74 -0
- package/template/client/src/components/business-ui/user-display/index.tsx +2 -0
- package/template/client/src/components/business-ui/user-display/overflow-tooltip-text.tsx +90 -0
- package/template/client/src/components/business-ui/user-display/type.ts +27 -0
- package/template/client/src/components/business-ui/user-display/user-display.tsx +99 -0
- package/template/client/src/components/business-ui/user-display/user-with-avatar.tsx +158 -0
- package/template/client/src/components/business-ui/user-display/utils.ts +37 -0
- package/template/client/src/components/business-ui/user-profile/error-image.tsx +302 -0
- package/template/client/src/components/business-ui/user-profile/user-external-script.ts +73 -0
- package/template/client/src/components/business-ui/user-profile/user-profile.tsx +370 -0
- package/template/client/src/components/business-ui/user-select/index.tsx +28 -0
- package/template/client/src/components/business-ui/user-select/types.ts +109 -0
- package/template/client/src/components/business-ui/user-select/use-user-value.ts +160 -0
- package/template/client/src/components/business-ui/user-select/user-item.tsx +98 -0
- package/template/client/src/components/business-ui/user-select/user-pill.tsx +140 -0
- package/template/client/src/components/business-ui/user-select/user-select-tag.tsx +82 -0
- package/template/client/src/components/business-ui/user-select/user-select.tsx +359 -0
- package/template/client/src/components/business-ui/user-select/utils.tsx +188 -0
- package/template/client/src/components/business-ui/utils/user.ts +72 -0
- package/template/client/src/components/ui/README.md +134 -0
- package/template/client/src/components/ui/accordion.tsx +66 -0
- package/template/client/src/components/ui/alert-dialog.tsx +157 -0
- package/template/client/src/components/ui/alert.tsx +71 -0
- package/template/client/src/components/ui/aspect-ratio.tsx +11 -0
- package/template/client/src/components/ui/avatar.tsx +53 -0
- package/template/client/src/components/ui/badge.tsx +42 -0
- package/template/client/src/components/ui/breadcrumb.tsx +109 -0
- package/template/client/src/components/ui/button-group.tsx +83 -0
- package/template/client/src/components/ui/button.tsx +69 -0
- package/template/client/src/components/ui/calendar.tsx +213 -0
- package/template/client/src/components/ui/card.tsx +82 -0
- package/template/client/src/components/ui/carousel.tsx +241 -0
- package/template/client/src/components/ui/chart.tsx +357 -0
- package/template/client/src/components/ui/checkbox.tsx +32 -0
- package/template/client/src/components/ui/collapsible.tsx +33 -0
- package/template/client/src/components/ui/command.tsx +208 -0
- package/template/client/src/components/ui/context-menu.tsx +324 -0
- package/template/client/src/components/ui/dialog.tsx +143 -0
- package/template/client/src/components/ui/drawer.tsx +135 -0
- package/template/client/src/components/ui/dropdown-menu.tsx +329 -0
- package/template/client/src/components/ui/empty.tsx +104 -0
- package/template/client/src/components/ui/field.tsx +248 -0
- package/template/client/src/components/ui/form.tsx +167 -0
- package/template/client/src/components/ui/hover-card.tsx +44 -0
- package/template/client/src/components/ui/icons/file-ae-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/icons/file-ai-colorful-icon.tsx +36 -0
- package/template/client/src/components/ui/icons/file-android-colorful-icon.tsx +33 -0
- package/template/client/src/components/ui/icons/file-audio-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/icons/file-code-colorful-icon.tsx +28 -0
- package/template/client/src/components/ui/icons/file-csv-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/icons/file-eml-colorful-icon.tsx +29 -0
- package/template/client/src/components/ui/icons/file-ios-colorful-icon.tsx +25 -0
- package/template/client/src/components/ui/icons/file-keynote-colorful-icon.tsx +29 -0
- package/template/client/src/components/ui/icons/file-pages-colorful-icon.tsx +29 -0
- package/template/client/src/components/ui/icons/file-ps-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/icons/file-sketch-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/icons/file-slide-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/icons/file-vcf-colorful-icon.tsx +29 -0
- package/template/client/src/components/ui/icons/file-wiki-excel-colorful-icon.tsx +23 -0
- package/template/client/src/components/ui/icons/file-wiki-image-colorful-icon.tsx +27 -0
- package/template/client/src/components/ui/icons/file-wiki-pdf-colorful-icon.tsx +20 -0
- package/template/client/src/components/ui/icons/file-wiki-ppt-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/icons/file-wiki-text-colorful-icon.tsx +12 -0
- package/template/client/src/components/ui/icons/file-wiki-unknown-colorful-icon.tsx +14 -0
- package/template/client/src/components/ui/icons/file-wiki-video-colorful-icon.tsx +23 -0
- package/template/client/src/components/ui/icons/file-wiki-word-colorful-icon.tsx +38 -0
- package/template/client/src/components/ui/icons/file-wiki-zip-colorful-icon.tsx +21 -0
- package/template/client/src/components/ui/image.tsx +183 -0
- package/template/client/src/components/ui/input-group.tsx +166 -0
- package/template/client/src/components/ui/input-otp.tsx +77 -0
- package/template/client/src/components/ui/input.tsx +21 -0
- package/template/client/src/components/ui/item.tsx +193 -0
- package/template/client/src/components/ui/kbd.tsx +28 -0
- package/template/client/src/components/ui/label.tsx +24 -0
- package/template/client/src/components/ui/menubar.tsx +348 -0
- package/template/client/src/components/ui/native-select.tsx +48 -0
- package/template/client/src/components/ui/navigation-menu.tsx +168 -0
- package/template/client/src/components/ui/pagination.tsx +127 -0
- package/template/client/src/components/ui/popover.tsx +48 -0
- package/template/client/src/components/ui/progress.tsx +31 -0
- package/template/client/src/components/ui/radio-group.tsx +45 -0
- package/template/client/src/components/ui/resizable.tsx +56 -0
- package/template/client/src/components/ui/scroll-area.tsx +58 -0
- package/template/client/src/components/ui/select.tsx +243 -0
- package/template/client/src/components/ui/separator.tsx +28 -0
- package/template/client/src/components/ui/sheet.tsx +139 -0
- package/template/client/src/components/ui/sidebar.tsx +727 -0
- package/template/client/src/components/ui/skeleton.tsx +13 -0
- package/template/client/src/components/ui/slider.tsx +87 -0
- package/template/client/src/components/ui/sonner.tsx +67 -0
- package/template/client/src/components/ui/spinner.tsx +16 -0
- package/template/client/src/components/ui/streamdown.tsx +186 -0
- package/template/client/src/components/ui/switch.tsx +31 -0
- package/template/client/src/components/ui/table.tsx +116 -0
- package/template/client/src/components/ui/tabs.tsx +66 -0
- package/template/client/src/components/ui/textarea.tsx +18 -0
- package/template/client/src/components/ui/toggle-group.tsx +83 -0
- package/template/client/src/components/ui/toggle.tsx +47 -0
- package/template/client/src/components/ui/tooltip.tsx +61 -0
- package/template/client/src/hooks/use-example.ts +52 -0
- package/template/client/src/hooks/use-mobile.ts +19 -0
- package/template/client/src/index.css +7 -0
- package/template/client/src/index.tsx +36 -0
- package/template/client/src/lib/shiki.ts +92 -0
- package/template/client/src/lib/utils.ts +6 -0
- package/template/client/src/pages/ExamplePage/ExamplePage.tsx +37 -0
- package/template/client/src/pages/NotFound/NotFound.tsx +11 -0
- package/template/client/src/tailwind-theme.css +257 -0
- package/template/client/src/types/common.ts +34 -0
- package/template/client/src/types/global.d.ts +53 -0
- package/template/client/src/types/index.ts +4 -0
- package/template/client/src/typography.css +41 -0
- package/template/client/src/utils/img-resources/avatar-placeholders.ts +12 -0
- package/template/client/src/utils/img-resources/banner-placeholders.ts +29 -0
- package/template/client/src/utils/img-resources/cover-placeholders.ts +47 -0
- package/template/components.json +21 -0
- package/template/eslint.config.js +50 -0
- package/template/nest-cli.json +25 -0
- package/template/package-lock.json +29195 -0
- package/template/package.json +202 -0
- package/template/postcss.config.js +10 -0
- package/template/scripts/build.sh +120 -0
- package/template/scripts/dev.js +295 -0
- package/template/scripts/dev.sh +2 -0
- package/template/scripts/lint.js +150 -0
- package/template/scripts/prune-smart.js +281 -0
- package/template/scripts/run.sh +5 -0
- package/template/server/app.module.ts +27 -0
- package/template/server/common/constants/api_response_code.ts +54 -0
- package/template/server/common/filters/exception.filter.ts +78 -0
- package/template/server/common/interfaces/api_response.interface.ts +21 -0
- package/template/server/common/interfaces/exception.interface.ts +19 -0
- package/template/server/database/schema.ts +90 -0
- package/template/server/main.ts +31 -0
- package/template/server/modules/hello/hello.controller.ts +16 -0
- package/template/server/modules/hello/hello.module.ts +10 -0
- package/template/server/modules/hello/hello.service.ts +28 -0
- package/template/server/modules/view/view.controller.ts +17 -0
- package/template/server/modules/view/view.module.ts +8 -0
- package/template/shared/api.interface.ts +1 -0
- package/template/tailwind.config.ts +9 -0
- package/template/tsconfig.app.json +23 -0
- package/template/tsconfig.json +8 -0
- package/template/tsconfig.node.json +16 -0
- package/template/vite.config.ts +10 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { UserInfo, SearchAvatar } from '@lark-apaas/client-toolkit/tools/services';
|
|
2
|
+
|
|
3
|
+
import type { AccountType } from '@client/src/components/business-ui/api/users/service';
|
|
4
|
+
import type {
|
|
5
|
+
UserSelectItemValue,
|
|
6
|
+
UserSelectValue,
|
|
7
|
+
} from '@client/src/components/business-ui/user-select/types';
|
|
8
|
+
import { getI18nText } from '@client/src/components/business-ui/utils/user';
|
|
9
|
+
import type { User } from '@client/src/components/business-ui/types/user';
|
|
10
|
+
|
|
11
|
+
export function getUserDisplayName(name: UserInfo['name']): string {
|
|
12
|
+
return name.zh_cn || name.en_us || '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getUserId(
|
|
16
|
+
user: Pick<UserInfo, 'userID' | 'larkUserID'>,
|
|
17
|
+
accountType: AccountType,
|
|
18
|
+
): string {
|
|
19
|
+
if (accountType === 'lark') {
|
|
20
|
+
return user.larkUserID || `_unknown_${Math.random().toString(36).slice(2)}`;
|
|
21
|
+
}
|
|
22
|
+
// 未注册外部联系人没有 userID,暂用 larkUserID 作为临时标识
|
|
23
|
+
return user.userID || user.larkUserID || `_unknown_${Math.random().toString(36).slice(2)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 判断用户是否为未注册的外部联系人
|
|
28
|
+
*/
|
|
29
|
+
export function isUnregisteredExternalContact(user: UserSelectItemValue): boolean {
|
|
30
|
+
const raw = user.raw;
|
|
31
|
+
return !!raw && !raw.user_id && !!raw.larkUserId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createUnknownUser(id: string): UserSelectItemValue {
|
|
35
|
+
return {
|
|
36
|
+
id,
|
|
37
|
+
name: '未知用户',
|
|
38
|
+
avatar: undefined,
|
|
39
|
+
raw: undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function userInfoToUser(
|
|
44
|
+
userInfo: UserInfo & SearchAvatar,
|
|
45
|
+
accountType: AccountType,
|
|
46
|
+
): UserSelectItemValue {
|
|
47
|
+
// 构建统一的 User 类型作为 raw 数据
|
|
48
|
+
const rawUser: User = {
|
|
49
|
+
user_id: userInfo.userID || undefined,
|
|
50
|
+
larkUserId: userInfo.larkUserID || undefined,
|
|
51
|
+
name: userInfo.name,
|
|
52
|
+
avatar: userInfo.avatar?.image?.large,
|
|
53
|
+
user_type: userInfo.userType,
|
|
54
|
+
department: userInfo.department as any,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
id: getUserId(userInfo, accountType),
|
|
59
|
+
name: getI18nText(userInfo.name),
|
|
60
|
+
avatar: userInfo.avatar?.image?.large,
|
|
61
|
+
raw: rawUser,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function searchUserInfoToUser(
|
|
66
|
+
userInfo: UserInfo & { avatar?: string; tenantName?: string },
|
|
67
|
+
accountType: AccountType,
|
|
68
|
+
): UserSelectItemValue {
|
|
69
|
+
// 构建统一的 User 类型作为 raw 数据
|
|
70
|
+
const rawUser: User = {
|
|
71
|
+
user_id: userInfo.userID || undefined,
|
|
72
|
+
larkUserId: userInfo.larkUserID || undefined,
|
|
73
|
+
name: userInfo.name,
|
|
74
|
+
avatar: userInfo.avatar,
|
|
75
|
+
user_type: userInfo.userType,
|
|
76
|
+
department: userInfo.department as any,
|
|
77
|
+
tenantName: userInfo.tenantName,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
id: getUserId(userInfo, accountType),
|
|
82
|
+
name: getI18nText(userInfo.name),
|
|
83
|
+
avatar: userInfo.avatar,
|
|
84
|
+
raw: rawUser,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
declare global {
|
|
89
|
+
interface Window {
|
|
90
|
+
MIAODA_APP_ID?: string;
|
|
91
|
+
__platform__?: {
|
|
92
|
+
appId?: string;
|
|
93
|
+
};
|
|
94
|
+
appId?: string;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getAppId(path: string): string | null | undefined {
|
|
99
|
+
if (window?.__platform__?.appId) {
|
|
100
|
+
return window.__platform__.appId;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (window.MIAODA_APP_ID) {
|
|
104
|
+
return window.MIAODA_APP_ID;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let prefix: string;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 从路径中提取 appId
|
|
111
|
+
* @example "/app/my-app/settings" → "my-app"
|
|
112
|
+
*/
|
|
113
|
+
const appMatch = path.match(/\/app\/([^/]+)/);
|
|
114
|
+
if (appMatch) {
|
|
115
|
+
return appMatch[1];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 检查路径是否以固定前缀开头
|
|
119
|
+
if (path.includes('/ai/feida/runtime/')) {
|
|
120
|
+
prefix = '/ai/feida/runtime/';
|
|
121
|
+
} else if (path.includes('/spark/r/')) {
|
|
122
|
+
prefix = '/spark/r/';
|
|
123
|
+
} else {
|
|
124
|
+
prefix = '/ai/miaoda/';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 兼容全栈appId挂载在window上,作为兜底
|
|
128
|
+
const windowAppId = window.appId || null;
|
|
129
|
+
|
|
130
|
+
if (!path.startsWith(prefix)) {
|
|
131
|
+
return windowAppId;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 截取前缀后的部分
|
|
135
|
+
const remainder = path.substring(prefix.length);
|
|
136
|
+
|
|
137
|
+
// 找到下一个斜杠的位置
|
|
138
|
+
const nextSlashIndex = remainder.indexOf('/');
|
|
139
|
+
|
|
140
|
+
// 如果没有斜杠,返回整个remainder;否则返回斜杠前的部分
|
|
141
|
+
if (nextSlashIndex === -1) {
|
|
142
|
+
return remainder || windowAppId;
|
|
143
|
+
}
|
|
144
|
+
return remainder.substring(0, nextSlashIndex) || windowAppId;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 从 value 中提取 ID 列表
|
|
149
|
+
* 支持 User 类型(user_id)和 UserSelectItemValue 类型(id)
|
|
150
|
+
*/
|
|
151
|
+
export function extractIdsFromValue(value: UserSelectValue): string[] {
|
|
152
|
+
if (value === null || value === undefined) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
if (typeof value === 'string') {
|
|
156
|
+
return [value];
|
|
157
|
+
}
|
|
158
|
+
if (Array.isArray(value)) {
|
|
159
|
+
if (value.length === 0) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
// 检查第一个元素判断类型
|
|
163
|
+
if (typeof value[0] === 'string') {
|
|
164
|
+
return value as string[];
|
|
165
|
+
}
|
|
166
|
+
// 对象数组:可能是 User[] 或 UserSelectItemValue[]
|
|
167
|
+
return value.map((item: any) => {
|
|
168
|
+
// UserSelectItemValue 有 id 字段(内部类型)
|
|
169
|
+
if ('id' in item && item.id) {
|
|
170
|
+
return item.id;
|
|
171
|
+
}
|
|
172
|
+
// User 有 user_id 字段(外部类型)
|
|
173
|
+
if ('user_id' in item && item.user_id) {
|
|
174
|
+
return item.user_id;
|
|
175
|
+
}
|
|
176
|
+
return '';
|
|
177
|
+
}).filter(Boolean);
|
|
178
|
+
}
|
|
179
|
+
// 单个对象:可能是 User 或 UserSelectItemValue
|
|
180
|
+
const item = value as any;
|
|
181
|
+
if ('id' in item && item.id) {
|
|
182
|
+
return [item.id];
|
|
183
|
+
}
|
|
184
|
+
if ('user_id' in item && item.user_id) {
|
|
185
|
+
return [item.user_id];
|
|
186
|
+
}
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// client/src/utils/user.ts
|
|
2
|
+
|
|
3
|
+
import type { I18nText, User, UserInput } from '../types/user';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 获取国际化文本的显示值
|
|
7
|
+
* @param text 国际化文本或普通字符串
|
|
8
|
+
* @param locale 语言,默认 zh_cn
|
|
9
|
+
*/
|
|
10
|
+
export function getI18nText(
|
|
11
|
+
text: I18nText | string | undefined,
|
|
12
|
+
locale: 'zh_cn' | 'en_us' | 'ja_jp' = 'zh_cn',
|
|
13
|
+
): string {
|
|
14
|
+
if (!text) return '';
|
|
15
|
+
if (typeof text === 'string') return text;
|
|
16
|
+
// 添加更完整的 fallback 逻辑
|
|
17
|
+
return text[locale] || text.zh_cn || text.en_us || text.ja_jp || '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 将任意格式的用户数据转换为标准 User 类型(下划线命名)
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeUser(input: UserInput): User {
|
|
24
|
+
const data = input as any;
|
|
25
|
+
|
|
26
|
+
// 提取 user_id(兼容驼峰和下划线)
|
|
27
|
+
const user_id = data.user_id || data.userId || '';
|
|
28
|
+
|
|
29
|
+
// 提取其他字段
|
|
30
|
+
const larkUserId = data.larkUserId; // larkUserId 保持驼峰
|
|
31
|
+
const name = data.name;
|
|
32
|
+
const avatar = data.avatar;
|
|
33
|
+
const email = data.email;
|
|
34
|
+
|
|
35
|
+
// 提取 user_type(兼容驼峰和下划线)
|
|
36
|
+
const user_type = data.user_type || data.userType;
|
|
37
|
+
|
|
38
|
+
const department = data.department;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
user_id,
|
|
42
|
+
larkUserId,
|
|
43
|
+
name,
|
|
44
|
+
avatar,
|
|
45
|
+
email,
|
|
46
|
+
user_type,
|
|
47
|
+
department,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 验证 user_id 是否有效
|
|
53
|
+
*/
|
|
54
|
+
export function isValidUserId(user_id: string | undefined): boolean {
|
|
55
|
+
if (!user_id) return false;
|
|
56
|
+
const trimmed = user_id.trim();
|
|
57
|
+
if (!trimmed) return false;
|
|
58
|
+
if (trimmed === '""') return false; // 过滤 AI 可能生成的 ""
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 获取用户显示名称
|
|
64
|
+
* 自动处理 I18nText 类型
|
|
65
|
+
*/
|
|
66
|
+
export function getUserDisplayName(
|
|
67
|
+
user: User | undefined,
|
|
68
|
+
locale: 'zh_cn' | 'en_us' | 'ja_jp' = 'zh_cn',
|
|
69
|
+
): string {
|
|
70
|
+
if (!user) return '';
|
|
71
|
+
return getI18nText(user.name, locale) || '无效人员';
|
|
72
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# shadcn/ui 组件开发指南
|
|
2
|
+
|
|
3
|
+
## 核心原则
|
|
4
|
+
|
|
5
|
+
- 组件位置:`/client/src/components/ui/`
|
|
6
|
+
- 图标库:必须使用 `lucide-react`,禁用 Emoji
|
|
7
|
+
- 查阅源码实现:直接读取组件源码了解最新组件实现
|
|
8
|
+
- 交互系统:Button、Badge 使用 elevate 遮罩系统处理 hover/active 状态(通过 `::after` 伪元素叠加 `--elevate-1` / `--elevate-2`)
|
|
9
|
+
|
|
10
|
+
## 关键组件规范
|
|
11
|
+
|
|
12
|
+
#### 1. Button 组件
|
|
13
|
+
|
|
14
|
+
**导入路径**:`import { Button } from '@/components/ui/button'`
|
|
15
|
+
|
|
16
|
+
**Props**:
|
|
17
|
+
- `variant`: `"link" | "default" | "destructive" | "outline" | "secondary" | "ghost"` - 按钮样式变体
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// ✅ 使用变体
|
|
21
|
+
<Button variant="secondary">标准按钮</Button>
|
|
22
|
+
|
|
23
|
+
// ✅ 自定义颜色时必须配对前景色
|
|
24
|
+
<Button className="bg-primary text-primary-foreground">自定义按钮</Button>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### 2. Badge 组件
|
|
28
|
+
|
|
29
|
+
**导入路径**:`import { Badge } from '@/components/ui/badge'`
|
|
30
|
+
|
|
31
|
+
**Props**:
|
|
32
|
+
- `variant`: `"default" | "destructive" | "outline" | "secondary"` - 样式变体
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// ✅ 使用变体
|
|
36
|
+
<Badge>默认</Badge>
|
|
37
|
+
<Badge variant="secondary">次要</Badge>
|
|
38
|
+
<Badge variant="outline">边框</Badge>
|
|
39
|
+
<Badge variant="destructive">危险</Badge>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### 3. Alert 组件
|
|
43
|
+
|
|
44
|
+
**导入路径**:`import { Alert, AlertTitle, AlertDescription, AlertAction } from '@/components/ui/alert'`
|
|
45
|
+
|
|
46
|
+
**Props**:
|
|
47
|
+
- `variant`: `"default" | "destructive" | "success" | "warning"` - 样式变体
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// ✅ default / destructive / success / warning 四种变体
|
|
51
|
+
<Alert variant="success">
|
|
52
|
+
<CheckCircle className="size-4" />
|
|
53
|
+
<AlertTitle>操作成功</AlertTitle>
|
|
54
|
+
<AlertDescription>您的更改已保存</AlertDescription>
|
|
55
|
+
</Alert>
|
|
56
|
+
|
|
57
|
+
<Alert variant="warning">
|
|
58
|
+
<AlertTriangle className="size-4" />
|
|
59
|
+
<AlertTitle>警告</AlertTitle>
|
|
60
|
+
<AlertDescription>请注意检查</AlertDescription>
|
|
61
|
+
</Alert>
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### 4. Empty 组件
|
|
66
|
+
|
|
67
|
+
**导入路径**:`import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent } from '@/components/ui/empty'`
|
|
68
|
+
|
|
69
|
+
**子组件 Props**:
|
|
70
|
+
- `EmptyMedia` 组件的 `variant`: `"default" | "icon"` - 媒体展示方式
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// 标准结构:EmptyHeader 包含 EmptyMedia + EmptyTitle + EmptyDescription
|
|
74
|
+
<Empty>
|
|
75
|
+
<EmptyHeader>
|
|
76
|
+
<EmptyMedia variant="icon">
|
|
77
|
+
<SearchIcon className="size-6" />
|
|
78
|
+
</EmptyMedia>
|
|
79
|
+
<EmptyTitle>暂无数据</EmptyTitle>
|
|
80
|
+
<EmptyDescription>当前没有找到相关内容</EmptyDescription>
|
|
81
|
+
</EmptyHeader>
|
|
82
|
+
<EmptyContent>
|
|
83
|
+
<Button>添加数据</Button>
|
|
84
|
+
</EmptyContent>
|
|
85
|
+
</Empty>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### 5. Card Padding 系统
|
|
89
|
+
|
|
90
|
+
**导入路径**:`import { CardHeader, CardContent, CardFooter } from '@/components/ui/card'`
|
|
91
|
+
|
|
92
|
+
- `CardHeader`: `p-6` (24px 全方向)
|
|
93
|
+
- `CardContent`: `p-6 pt-0` (与 header 无缝衔接)
|
|
94
|
+
- `CardFooter`: `p-6 pt-0` (与 content 无缝衔接)
|
|
95
|
+
|
|
96
|
+
#### 6. Dialog 组件
|
|
97
|
+
|
|
98
|
+
**导入路径**:`import { Dialog, DialogContent } from '@/components/ui/dialog'`
|
|
99
|
+
|
|
100
|
+
Dialog 默认提供了右上角的close能力,同时也提供了自定义关闭按钮的能力,即通过设置showCloseButton为false来关闭默认的关闭按钮。所以当默认存在close时,禁止在内容区域提供自定义的关闭按钮。
|
|
101
|
+
|
|
102
|
+
#### 7. Image 组件
|
|
103
|
+
|
|
104
|
+
**导入路径**:`import { Image } from '@/components/ui/image'`
|
|
105
|
+
|
|
106
|
+
**Props**:支持原生 `<img>` 标签所有属性。
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**使用规范**:
|
|
113
|
+
|
|
114
|
+
1. **响应式图片场景**:当图片尺寸需要根据视口宽度变化时,必须设置 `sizes` 属性
|
|
115
|
+
2. **固定尺寸图片场景**:当图片有固定尺寸时,必须设置 `width` 属性(number 类型)
|
|
116
|
+
3. **必须提供有意义的 `alt` 属性**
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
// ✅ 响应式图片
|
|
120
|
+
<Image
|
|
121
|
+
src="/path/to/image.jpg"
|
|
122
|
+
alt="描述文字"
|
|
123
|
+
sizes="(max-width: 768px) 100vw, 50vw"
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
// ✅ 固定尺寸图片(width/height 使用 number)
|
|
127
|
+
<Image
|
|
128
|
+
src="/path/to/image.jpg"
|
|
129
|
+
alt="描述文字"
|
|
130
|
+
width={300}
|
|
131
|
+
height={200}
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
|
5
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils"
|
|
8
|
+
|
|
9
|
+
function Accordion({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
12
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AccordionItem({
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
19
|
+
return (
|
|
20
|
+
<AccordionPrimitive.Item
|
|
21
|
+
data-slot="accordion-item"
|
|
22
|
+
className={cn("border-b last:border-b-0", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AccordionTrigger({
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
33
|
+
return (
|
|
34
|
+
<AccordionPrimitive.Header className="flex">
|
|
35
|
+
<AccordionPrimitive.Trigger
|
|
36
|
+
data-slot="accordion-trigger"
|
|
37
|
+
className={cn(
|
|
38
|
+
"focus-visible:border-ring focus-visible:ring-ring/20 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none enabled:hover:underline focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
45
|
+
</AccordionPrimitive.Trigger>
|
|
46
|
+
</AccordionPrimitive.Header>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AccordionContent({
|
|
51
|
+
className,
|
|
52
|
+
children,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
55
|
+
return (
|
|
56
|
+
<AccordionPrimitive.Content
|
|
57
|
+
data-slot="accordion-content"
|
|
58
|
+
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
62
|
+
</AccordionPrimitive.Content>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { buttonVariants } from "@/components/ui/button"
|
|
8
|
+
|
|
9
|
+
function AlertDialog({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
12
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AlertDialogTrigger({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
18
|
+
return (
|
|
19
|
+
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function AlertDialogPortal({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
26
|
+
return (
|
|
27
|
+
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function AlertDialogOverlay({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
|
35
|
+
return (
|
|
36
|
+
<AlertDialogPrimitive.Overlay
|
|
37
|
+
data-slot="alert-dialog-overlay"
|
|
38
|
+
className={cn(
|
|
39
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function AlertDialogContent({
|
|
48
|
+
className,
|
|
49
|
+
...props
|
|
50
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
|
51
|
+
return (
|
|
52
|
+
<AlertDialogPortal>
|
|
53
|
+
<AlertDialogOverlay />
|
|
54
|
+
<AlertDialogPrimitive.Content
|
|
55
|
+
data-slot="alert-dialog-content"
|
|
56
|
+
className={cn(
|
|
57
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-sm:max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 max-w-lg",
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
</AlertDialogPortal>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function AlertDialogHeader({
|
|
67
|
+
className,
|
|
68
|
+
...props
|
|
69
|
+
}: React.ComponentProps<"div">) {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
data-slot="alert-dialog-header"
|
|
73
|
+
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function AlertDialogFooter({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}: React.ComponentProps<"div">) {
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
data-slot="alert-dialog-footer"
|
|
86
|
+
className={cn(
|
|
87
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function AlertDialogTitle({
|
|
96
|
+
className,
|
|
97
|
+
...props
|
|
98
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
99
|
+
return (
|
|
100
|
+
<AlertDialogPrimitive.Title
|
|
101
|
+
data-slot="alert-dialog-title"
|
|
102
|
+
className={cn("text-lg font-semibold", className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function AlertDialogDescription({
|
|
109
|
+
className,
|
|
110
|
+
...props
|
|
111
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
112
|
+
return (
|
|
113
|
+
<AlertDialogPrimitive.Description
|
|
114
|
+
data-slot="alert-dialog-description"
|
|
115
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function AlertDialogAction({
|
|
122
|
+
className,
|
|
123
|
+
...props
|
|
124
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
|
|
125
|
+
return (
|
|
126
|
+
<AlertDialogPrimitive.Action
|
|
127
|
+
className={cn(buttonVariants(), className)}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function AlertDialogCancel({
|
|
134
|
+
className,
|
|
135
|
+
...props
|
|
136
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
|
137
|
+
return (
|
|
138
|
+
<AlertDialogPrimitive.Cancel
|
|
139
|
+
className={cn(buttonVariants({ variant: "outline" }), className)}
|
|
140
|
+
{...props}
|
|
141
|
+
/>
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
AlertDialog,
|
|
147
|
+
AlertDialogPortal,
|
|
148
|
+
AlertDialogOverlay,
|
|
149
|
+
AlertDialogTrigger,
|
|
150
|
+
AlertDialogContent,
|
|
151
|
+
AlertDialogHeader,
|
|
152
|
+
AlertDialogFooter,
|
|
153
|
+
AlertDialogTitle,
|
|
154
|
+
AlertDialogDescription,
|
|
155
|
+
AlertDialogAction,
|
|
156
|
+
AlertDialogCancel,
|
|
157
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-card text-card-foreground",
|
|
12
|
+
destructive:
|
|
13
|
+
"text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
|
|
14
|
+
// 新增语义化 success/warning variant
|
|
15
|
+
success:
|
|
16
|
+
"bg-success text-success-foreground border-success/50 [&>svg]:text-success-foreground *:data-[slot=alert-description]:text-success-foreground/90",
|
|
17
|
+
warning:
|
|
18
|
+
"bg-warning text-warning-foreground border-warning/50 [&>svg]:text-warning-foreground *:data-[slot=alert-description]:text-warning-foreground/90",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: "default",
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
function Alert({
|
|
28
|
+
className,
|
|
29
|
+
variant,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-slot="alert"
|
|
35
|
+
role="alert"
|
|
36
|
+
className={cn(alertVariants({ variant }), className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
data-slot="alert-title"
|
|
46
|
+
className={cn(
|
|
47
|
+
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function AlertDescription({
|
|
56
|
+
className,
|
|
57
|
+
...props
|
|
58
|
+
}: React.ComponentProps<"div">) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
data-slot="alert-description"
|
|
62
|
+
className={cn(
|
|
63
|
+
"text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed",
|
|
64
|
+
className
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { Alert, AlertTitle, AlertDescription }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
|
4
|
+
|
|
5
|
+
function AspectRatio({
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<typeof AspectRatioPrimitive.Root>) {
|
|
8
|
+
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...props} />
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { AspectRatio }
|