@coffic/cosy-ui 0.8.29 → 0.9.2
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/dist/src/utils/i18n.ts +0 -6
- package/dist/src/utils/language.ts +80 -101
- package/dist/src-astro/header/Header.astro +26 -5
- package/dist/src-astro/language-switcher/LanguageSwitcher.astro +64 -35
- package/dist/src-astro/language-switcher/index.ts +1 -9
- package/dist/src-astro/language-switcher/switcher_util.ts +84 -0
- package/dist/src-astro/layout-dashboard/tools.ts +1 -1
- package/dist/src-astro/link/Link.astro +6 -1
- package/dist/src-astro/list/ListItem.astro +58 -11
- package/dist/src-astro/types/header.ts +71 -70
- package/dist/src-astro/types/layout.ts +52 -52
- package/dist/src-astro/types/meta.ts +3 -1
- package/package.json +1 -1
- package/dist/src-astro/language-switcher/LanguageSwitcherBasic.astro +0 -7
- package/dist/src-vue/utils/language.ts +0 -121
package/dist/src/utils/i18n.ts
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
import { getRelativeLocaleUrl } from 'astro:i18n';
|
2
1
|
import type { AstroGlobal } from 'astro';
|
3
|
-
import { cosyLogger } from '../../src-astro/cosy';
|
4
2
|
|
5
3
|
// 默认语言
|
6
4
|
export const DEFAULT_LANGUAGE = 'en';
|
@@ -11,111 +9,92 @@ export const DEFAULT_LANGUAGE = 'en';
|
|
11
9
|
* 提供语言相关的工具函数,用于多语言支持
|
12
10
|
*/
|
13
11
|
export class LanguageUtil {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
`getRelativeLink: locale=${locale}, currentPath=${originalPath}, currentLocale=${currentLocale}, result=${result}`
|
26
|
-
);
|
27
|
-
}
|
28
|
-
|
29
|
-
return result;
|
30
|
-
}
|
31
|
-
|
32
|
-
static getLanguageName(code: string | undefined): string {
|
33
|
-
switch (code) {
|
34
|
-
case 'en':
|
35
|
-
return 'English';
|
36
|
-
case 'zh-cn':
|
37
|
-
return '简体中文';
|
38
|
-
case 'zh':
|
39
|
-
return '中文';
|
40
|
-
case undefined:
|
41
|
-
default:
|
42
|
-
return 'not known';
|
12
|
+
static getLanguageName(code: string | undefined): string {
|
13
|
+
switch (code) {
|
14
|
+
case 'en':
|
15
|
+
return 'English';
|
16
|
+
case 'zh-cn':
|
17
|
+
return '简体中文';
|
18
|
+
case 'zh':
|
19
|
+
return '中文';
|
20
|
+
default:
|
21
|
+
return code || 'not known';
|
22
|
+
}
|
43
23
|
}
|
44
|
-
}
|
45
24
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
25
|
+
/**
|
26
|
+
* 获取当前语言
|
27
|
+
* @param astro Astro全局对象
|
28
|
+
* @returns 当前应使用的语言代码
|
29
|
+
*/
|
30
|
+
static getCurrentLanguage(astro: AstroGlobal): string {
|
31
|
+
// 尝试从Astro全局对象中获取语言
|
32
|
+
const astroLang = astro.currentLocale;
|
33
|
+
if (astroLang) {
|
34
|
+
return astroLang;
|
35
|
+
}
|
36
|
+
// 尝试从URL中获取语言
|
37
|
+
const urlLang = this.getLanguageFromURL(astro.url.pathname);
|
38
|
+
if (urlLang) {
|
39
|
+
return urlLang;
|
40
|
+
}
|
41
|
+
|
42
|
+
// 尝试从浏览器设置中获取语言
|
43
|
+
const browserLang = this.getLanguageFromBrowser();
|
44
|
+
if (browserLang) {
|
45
|
+
return browserLang;
|
46
|
+
}
|
47
|
+
|
48
|
+
// 尝试从Astro全局对象中获取语言
|
49
|
+
const preferredLocale = astro.preferredLocale;
|
50
|
+
if (preferredLocale) {
|
51
|
+
return preferredLocale;
|
52
|
+
}
|
53
|
+
|
54
|
+
// 如果无法检测,返回默认语言
|
55
|
+
return DEFAULT_LANGUAGE;
|
67
56
|
}
|
68
57
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
58
|
+
/**
|
59
|
+
* 从URL中提取语言代码
|
60
|
+
* @param url 当前URL
|
61
|
+
* @returns 从URL中提取的语言代码,如果无法提取则返回undefined
|
62
|
+
*/
|
63
|
+
private static getLanguageFromURL(url: string): string | undefined {
|
64
|
+
let currentUrl = url;
|
65
|
+
|
66
|
+
if (currentUrl === undefined) {
|
67
|
+
if (typeof window === 'undefined') return undefined;
|
68
|
+
currentUrl = window.location.href;
|
69
|
+
}
|
70
|
+
|
71
|
+
// 尝试从路径中提取语言代码
|
72
|
+
// 例如: /zh-cn/components/button
|
73
|
+
const pathMatch = currentUrl.match(/^\/([\w-]+)\//);
|
74
|
+
if (pathMatch) {
|
75
|
+
return pathMatch[1];
|
76
|
+
}
|
77
|
+
|
78
|
+
// 尝试从查询参数中提取语言代码
|
79
|
+
// 例如: ?lang=zh-cn
|
80
|
+
const urlParams = new URLSearchParams(currentUrl.split('?')[1]);
|
81
|
+
const langParam = urlParams.get('lang');
|
82
|
+
if (langParam) {
|
83
|
+
return langParam;
|
84
|
+
}
|
85
|
+
|
86
|
+
return undefined;
|
73
87
|
}
|
74
88
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
* @param url 当前URL
|
82
|
-
* @returns 从URL中提取的语言代码,如果无法提取则返回undefined
|
83
|
-
*/
|
84
|
-
private static getLanguageFromURL(url: string): string | undefined {
|
85
|
-
let currentUrl = url;
|
89
|
+
/**
|
90
|
+
* 从浏览器设置中获取首选语言
|
91
|
+
* @returns 从浏览器设置中获取的语言代码,如果无法获取则返回undefined
|
92
|
+
*/
|
93
|
+
private static getLanguageFromBrowser(): string | undefined {
|
94
|
+
if (typeof navigator === 'undefined') return undefined;
|
86
95
|
|
87
|
-
|
88
|
-
|
89
|
-
|
96
|
+
// 获取浏览器语言
|
97
|
+
const browserLang = navigator.language.toLowerCase();
|
98
|
+
return browserLang;
|
90
99
|
}
|
91
|
-
|
92
|
-
// 尝试从路径中提取语言代码
|
93
|
-
// 例如: /zh-cn/components/button
|
94
|
-
const pathMatch = currentUrl.match(/^\/([\w-]+)\//);
|
95
|
-
if (pathMatch) {
|
96
|
-
return pathMatch[1];
|
97
|
-
}
|
98
|
-
|
99
|
-
// 尝试从查询参数中提取语言代码
|
100
|
-
// 例如: ?lang=zh-cn
|
101
|
-
const urlParams = new URLSearchParams(currentUrl.split('?')[1]);
|
102
|
-
const langParam = urlParams.get('lang');
|
103
|
-
if (langParam) {
|
104
|
-
return langParam;
|
105
|
-
}
|
106
|
-
|
107
|
-
return undefined;
|
108
|
-
}
|
109
|
-
|
110
|
-
/**
|
111
|
-
* 从浏览器设置中获取首选语言
|
112
|
-
* @returns 从浏览器设置中获取的语言代码,如果无法获取则返回undefined
|
113
|
-
*/
|
114
|
-
private static getLanguageFromBrowser(): string | undefined {
|
115
|
-
if (typeof navigator === 'undefined') return undefined;
|
116
|
-
|
117
|
-
// 获取浏览器语言
|
118
|
-
const browserLang = navigator.language.toLowerCase();
|
119
|
-
return browserLang;
|
120
|
-
}
|
121
100
|
}
|
@@ -12,17 +12,34 @@
|
|
12
12
|
* 2. 响应式适配 - 在移动端和桌面端都有合适的展现形式
|
13
13
|
* 3. 可定制性 - 支持多种配置选项,适应不同网站的风格和需求
|
14
14
|
* 4. 多语言支持 - 内置语言切换功能,便于构建国际化网站
|
15
|
+
*
|
16
|
+
* @usage
|
17
|
+
* 基本用法:
|
18
|
+
* ```astro
|
19
|
+
* <Header />
|
20
|
+
* ```
|
21
|
+
*
|
22
|
+
* 启用语言切换功能:
|
23
|
+
* ```astro
|
24
|
+
* ---
|
25
|
+
* import * as astroI18n from 'astro:i18n';
|
26
|
+
* ---
|
27
|
+
* <Header astroI18n={astroI18n} />
|
28
|
+
* ```
|
29
|
+
*
|
30
|
+
* @props
|
31
|
+
* - astroI18n - 完整的 astro:i18n 模块(启用语言切换时需要)
|
15
32
|
*/
|
16
33
|
import {
|
34
|
+
Image,
|
17
35
|
LanguageSwitcher,
|
36
|
+
Link,
|
18
37
|
LinkUtil,
|
19
38
|
type IHeaderProps,
|
20
39
|
type INavItem,
|
21
40
|
NavItems,
|
22
|
-
Link,
|
23
|
-
Image,
|
24
41
|
ThemeSwitcher,
|
25
|
-
} from '
|
42
|
+
} from '../../index-astro';
|
26
43
|
import Logo from '../../src/assets/logo-rounded.png';
|
27
44
|
|
28
45
|
export interface Props extends IHeaderProps {
|
@@ -31,7 +48,6 @@ export interface Props extends IHeaderProps {
|
|
31
48
|
|
32
49
|
const {
|
33
50
|
height = 'md',
|
34
|
-
languages = ['zh-cn', 'en'],
|
35
51
|
logo = Logo,
|
36
52
|
logoHref = '/',
|
37
53
|
navItems = [],
|
@@ -42,6 +58,7 @@ const {
|
|
42
58
|
paddingVertical = 'none',
|
43
59
|
navPosition = 'center',
|
44
60
|
showThemeSwitcher = true,
|
61
|
+
astroI18n,
|
45
62
|
} = Astro.props;
|
46
63
|
|
47
64
|
// 根据高度设置样式
|
@@ -79,6 +96,10 @@ const logoSizeClasses = {
|
|
79
96
|
|
80
97
|
const logoSizeClass = logoSizeClasses[height];
|
81
98
|
const linkHeightClass = linkHeightClasses[height];
|
99
|
+
|
100
|
+
// 检查 i18n 是否启用,通过 Astro.currentLocale 来判断
|
101
|
+
let isI18nEnabled = Astro.currentLocale !== undefined;
|
102
|
+
|
82
103
|
const currentPath = Astro.url.pathname;
|
83
104
|
const activeLink = LinkUtil.getActiveLink(
|
84
105
|
currentPath,
|
@@ -200,7 +221,7 @@ const activeLink = LinkUtil.getActiveLink(
|
|
200
221
|
}
|
201
222
|
|
202
223
|
{showThemeSwitcher && <ThemeSwitcher />}
|
203
|
-
{
|
224
|
+
{isI18nEnabled && <LanguageSwitcher astroI18n={astroI18n} />}
|
204
225
|
|
205
226
|
<slot name="navbar-end" />
|
206
227
|
</div>
|
@@ -14,55 +14,84 @@
|
|
14
14
|
* 4. 一致的视觉风格 - 使用与整体设计系统一致的下拉菜单样式
|
15
15
|
*
|
16
16
|
* @usage
|
17
|
-
*
|
17
|
+
* 基本用法(需要用户传入 astro:i18n 模块):
|
18
18
|
* ```astro
|
19
|
-
*
|
19
|
+
* ---
|
20
|
+
* import * as astroI18n from 'astro:i18n';
|
21
|
+
* ---
|
22
|
+
* <LanguageSwitcher astroI18n={astroI18n} />
|
20
23
|
* ```
|
21
24
|
*
|
22
|
-
*
|
23
|
-
*
|
24
|
-
*
|
25
|
-
* languages={['zh-cn', 'en']}
|
26
|
-
* />
|
27
|
-
* ```
|
25
|
+
* @props
|
26
|
+
* - astroI18n - 完整的 astro:i18n 模块
|
27
|
+
* - class - 自定义CSS类名
|
28
28
|
*/
|
29
29
|
|
30
|
-
import { ChevronDownIcon,
|
30
|
+
import { ChevronDownIcon, Link, ListItem } from '../../index-astro';
|
31
|
+
import {
|
32
|
+
checkSwitcherRenderState,
|
33
|
+
generateSwitcherLinks,
|
34
|
+
type SwitcherLink,
|
35
|
+
} from './switcher_util';
|
31
36
|
import '../../style.ts';
|
32
37
|
|
33
38
|
interface Props {
|
34
|
-
|
39
|
+
/**
|
40
|
+
* 完整的 astro:i18n 模块
|
41
|
+
*/
|
42
|
+
astroI18n?: any;
|
43
|
+
|
35
44
|
/**
|
36
45
|
* 自定义类名
|
37
46
|
*/
|
38
47
|
class?: string;
|
39
48
|
}
|
40
49
|
|
41
|
-
const {
|
50
|
+
const { astroI18n, class: className = '' } = Astro.props;
|
51
|
+
|
52
|
+
// 检查渲染状态
|
53
|
+
const renderState = checkSwitcherRenderState(Astro.currentLocale, astroI18n);
|
42
54
|
|
43
|
-
|
44
|
-
|
55
|
+
// 输出警告信息
|
56
|
+
if (renderState.warnings) {
|
57
|
+
renderState.warnings.forEach((warning) => console.warn(warning));
|
58
|
+
}
|
59
|
+
|
60
|
+
let links: SwitcherLink[] = [];
|
61
|
+
|
62
|
+
// 如果应该渲染,生成切换链接
|
63
|
+
if (renderState.shouldRender && Astro.currentLocale) {
|
64
|
+
try {
|
65
|
+
links = generateSwitcherLinks(
|
66
|
+
astroI18n,
|
67
|
+
Astro.currentLocale,
|
68
|
+
Astro.url.pathname
|
69
|
+
);
|
70
|
+
} catch (error) {
|
71
|
+
// 如果生成链接失败,设置为不渲染
|
72
|
+
renderState.shouldRender = false;
|
73
|
+
}
|
74
|
+
}
|
45
75
|
---
|
46
76
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
</div>
|
77
|
+
{
|
78
|
+
renderState.shouldRender && (
|
79
|
+
<div class={`cosy:dropdown cosy:dropdown-end ${className}`}>
|
80
|
+
<div tabindex="0" role="button" class:list={['cosy:btn cosy:btn-ghost']}>
|
81
|
+
<span class="cosy:mr-1">{renderState.currentLanguageName}</span>
|
82
|
+
<ChevronDownIcon size="16px" class="cosy:w-4 cosy:h-4" />
|
83
|
+
</div>
|
84
|
+
<ul
|
85
|
+
tabindex="0"
|
86
|
+
class="cosy:z-[1] cosy:bg-base-100 cosy:shadow cosy:p-2 cosy:rounded-box cosy:w-32 cosy:dropdown-content cosy:menu">
|
87
|
+
{links.map((link) => (
|
88
|
+
<ListItem>
|
89
|
+
<Link href={link.url} active={Astro.currentLocale === link.locale}>
|
90
|
+
{link.name}
|
91
|
+
</Link>
|
92
|
+
</ListItem>
|
93
|
+
))}
|
94
|
+
</ul>
|
95
|
+
</div>
|
96
|
+
)
|
97
|
+
}
|
@@ -1,11 +1,3 @@
|
|
1
1
|
import LanguageSwitcher from './LanguageSwitcher.astro';
|
2
|
-
import LanguageSwitcherBasic from './LanguageSwitcherBasic.astro';
|
3
|
-
import BasicSourceCode from './LanguageSwitcherBasic.astro?raw';
|
4
|
-
import { extractSimpleExample } from '../../src/utils/component';
|
5
2
|
|
6
|
-
export { LanguageSwitcher
|
7
|
-
|
8
|
-
// 导出示例源代码
|
9
|
-
export const LanguageSwitcherExampleCodes = {
|
10
|
-
Basic: extractSimpleExample(BasicSourceCode, 'LanguageSwitcher'),
|
11
|
-
};
|
3
|
+
export { LanguageSwitcher };
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { LanguageUtil } from "../../src/utils/language"
|
2
|
+
|
3
|
+
export interface SwitcherLink {
|
4
|
+
locale: string;
|
5
|
+
name: string;
|
6
|
+
url: string;
|
7
|
+
}
|
8
|
+
|
9
|
+
/**
|
10
|
+
* 获取基础 URL
|
11
|
+
*/
|
12
|
+
export const getBaseUrl = (): string => {
|
13
|
+
return import.meta.env.BASE_URL || '/';
|
14
|
+
};
|
15
|
+
|
16
|
+
/**
|
17
|
+
* 从 URL 中提取语言代码
|
18
|
+
*/
|
19
|
+
export const getLocaleFromUrl = (url: string): string => {
|
20
|
+
return url.replace(getBaseUrl(), '').split('/')[0];
|
21
|
+
};
|
22
|
+
|
23
|
+
/**
|
24
|
+
* 生成语言切换链接
|
25
|
+
* @param astroI18n - astro:i18n 模块
|
26
|
+
* @param currentLocale - 当前语言代码
|
27
|
+
* @param pathname - 当前页面路径
|
28
|
+
* @returns 语言切换链接数组
|
29
|
+
*/
|
30
|
+
export const generateSwitcherLinks = (
|
31
|
+
astroI18n: any,
|
32
|
+
currentLocale: string,
|
33
|
+
pathname: string
|
34
|
+
): SwitcherLink[] => {
|
35
|
+
try {
|
36
|
+
const { getRelativeLocaleUrl, getRelativeLocaleUrlList } = astroI18n;
|
37
|
+
|
38
|
+
const currentLocalURLPrefix = getRelativeLocaleUrl(currentLocale, '');
|
39
|
+
const pathWithSlash = pathname + '/';
|
40
|
+
const slug = pathWithSlash.replace(currentLocalURLPrefix, '');
|
41
|
+
const urls = getRelativeLocaleUrlList(slug);
|
42
|
+
|
43
|
+
return urls.map((url: string) => ({
|
44
|
+
locale: getLocaleFromUrl(url),
|
45
|
+
name: LanguageUtil.getLanguageName(getLocaleFromUrl(url)),
|
46
|
+
url: url,
|
47
|
+
}));
|
48
|
+
} catch (error) {
|
49
|
+
console.warn('LanguageSwitcher: Error generating switcher links:', error);
|
50
|
+
throw error;
|
51
|
+
}
|
52
|
+
};
|
53
|
+
|
54
|
+
/**
|
55
|
+
* 检查语言切换器是否应该渲染
|
56
|
+
* @param currentLocale - 当前语言代码
|
57
|
+
* @param astroI18n - astro:i18n 模块
|
58
|
+
* @returns 是否应该渲染的状态信息
|
59
|
+
*/
|
60
|
+
export const checkSwitcherRenderState = (
|
61
|
+
currentLocale: string | undefined,
|
62
|
+
astroI18n: any
|
63
|
+
): {
|
64
|
+
shouldRender: boolean;
|
65
|
+
currentLanguageName?: string;
|
66
|
+
warnings?: string[];
|
67
|
+
} => {
|
68
|
+
const warnings: string[] = [];
|
69
|
+
|
70
|
+
if (!currentLocale) {
|
71
|
+
warnings.push('LanguageSwitcher: i18n is not enabled in the current project');
|
72
|
+
return { shouldRender: false, warnings };
|
73
|
+
}
|
74
|
+
|
75
|
+
if (!astroI18n) {
|
76
|
+
warnings.push('LanguageSwitcher: astroI18n module is required. Please pass the astro:i18n module as a prop.');
|
77
|
+
return { shouldRender: false, warnings };
|
78
|
+
}
|
79
|
+
|
80
|
+
return {
|
81
|
+
shouldRender: true,
|
82
|
+
currentLanguageName: LanguageUtil.getLanguageName(currentLocale),
|
83
|
+
};
|
84
|
+
};
|
@@ -115,7 +115,7 @@ const hrefToIconMap: Record<string, string> = {
|
|
115
115
|
*/
|
116
116
|
export function getIconFromHref(href: string, fallbackIcon: string = 'folder'): string {
|
117
117
|
// 将 href 转换为小写并移除路径分隔符
|
118
|
-
const normalizedHref = href.toLowerCase().replace(/[
|
118
|
+
const normalizedHref = href.toLowerCase().replace(/[/\-_]/g, '');
|
119
119
|
|
120
120
|
// 遍历映射表,找到匹配的关键词
|
121
121
|
for (const [keyword, iconName] of Object.entries(hrefToIconMap)) {
|
@@ -100,6 +100,7 @@ interface Props extends HTMLAttributes<'a'> {
|
|
100
100
|
| 'warning'
|
101
101
|
| 'error';
|
102
102
|
align?: 'left' | 'center' | 'right';
|
103
|
+
active?: boolean;
|
103
104
|
}
|
104
105
|
|
105
106
|
const {
|
@@ -121,6 +122,7 @@ const {
|
|
121
122
|
fullWidth = false,
|
122
123
|
color,
|
123
124
|
align,
|
125
|
+
active = false,
|
124
126
|
...rest
|
125
127
|
} = Astro.props;
|
126
128
|
|
@@ -159,7 +161,10 @@ const classes = [
|
|
159
161
|
animation === 'hover-scale' &&
|
160
162
|
'cosy:hover:scale-105 cosy:transition-transform',
|
161
163
|
|
162
|
-
//
|
164
|
+
// Active state
|
165
|
+
active && 'cosy:active',
|
166
|
+
|
167
|
+
// 按钮风格
|
163
168
|
btn && 'cosy:btn',
|
164
169
|
btn && size === 'sm' && 'cosy:btn-sm',
|
165
170
|
btn && size === 'lg' && 'cosy:btn-lg',
|
@@ -1,10 +1,35 @@
|
|
1
1
|
---
|
2
2
|
/**
|
3
3
|
* @component ListItem
|
4
|
-
*
|
5
|
-
* @
|
6
|
-
*
|
7
|
-
*
|
4
|
+
*
|
5
|
+
* @description
|
6
|
+
* ListItem 组件用于在列表中显示单个项目。它内置了多种加载动画效果,可以方便地通过 `loading` 属性来控制加载状态,并通过 `loadingAnimationType` 属性切换不同的加载Loading动画样式。
|
7
|
+
*
|
8
|
+
* @usage
|
9
|
+
* 基本用法:
|
10
|
+
* ```astro
|
11
|
+
* <ListItem>这是一个列表项</ListItem>
|
12
|
+
* ```
|
13
|
+
*
|
14
|
+
* 加载状态:
|
15
|
+
* ```astro
|
16
|
+
* <ListItem loading={true}>加载中的列表项</ListItem>
|
17
|
+
* ```
|
18
|
+
*
|
19
|
+
* 指定加载动画类型:
|
20
|
+
* ```astro
|
21
|
+
* <ListItem loading={true} loadingAnimationType="pulse">使用脉冲动画加载</ListItem>
|
22
|
+
* <ListItem loading={true} loadingAnimationType="icon-left">使用左侧图标动画加载</ListItem>
|
23
|
+
* ```
|
24
|
+
*
|
25
|
+
* @props
|
26
|
+
* @prop {string} [class] - 自定义 CSS 类名。
|
27
|
+
* @prop {boolean} [loading=false] - 控制是否显示加载动画。
|
28
|
+
* @prop {number} [duration] - 动画的持续时间(单位:毫秒)。
|
29
|
+
* @prop {('none'|'ring'|'icon-left'|'icon-right'|'breath'|'pulse'|'glow')} [loadingAnimationType='none'] - 加载动画的类型。
|
30
|
+
*
|
31
|
+
* @slots
|
32
|
+
* @slot default - 列表项的内容。
|
8
33
|
*/
|
9
34
|
import '../../style.ts';
|
10
35
|
import ListItemRing from './ListItemRing.astro';
|
@@ -14,16 +39,38 @@ import ListItemBreath from './ListItemBreath.astro';
|
|
14
39
|
import ListItemPulse from './ListItemPulse.astro';
|
15
40
|
import ListItemGlow from './ListItemGlow.astro';
|
16
41
|
|
42
|
+
interface Props {
|
43
|
+
class?: string;
|
44
|
+
loading?: boolean;
|
45
|
+
duration?: number;
|
46
|
+
loadingAnimationType?:
|
47
|
+
| 'none'
|
48
|
+
| 'ring'
|
49
|
+
| 'icon-left'
|
50
|
+
| 'icon-right'
|
51
|
+
| 'breath'
|
52
|
+
| 'pulse'
|
53
|
+
| 'glow';
|
54
|
+
}
|
55
|
+
|
17
56
|
const {
|
18
57
|
loading = false,
|
19
58
|
duration,
|
20
|
-
|
59
|
+
loadingAnimationType = 'none',
|
21
60
|
...restProps
|
22
61
|
} = Astro.props;
|
23
62
|
---
|
24
63
|
|
25
64
|
{
|
26
|
-
|
65
|
+
loadingAnimationType === 'none' && (
|
66
|
+
<li {...restProps}>
|
67
|
+
<slot />
|
68
|
+
</li>
|
69
|
+
)
|
70
|
+
}
|
71
|
+
|
72
|
+
{
|
73
|
+
loadingAnimationType === 'ring' && (
|
27
74
|
<ListItemRing loading={loading} duration={duration} {...restProps}>
|
28
75
|
<slot />
|
29
76
|
</ListItemRing>
|
@@ -31,7 +78,7 @@ const {
|
|
31
78
|
}
|
32
79
|
|
33
80
|
{
|
34
|
-
|
81
|
+
loadingAnimationType === 'icon-left' && (
|
35
82
|
<ListItemIconLeft loading={loading} duration={duration} {...restProps}>
|
36
83
|
<slot />
|
37
84
|
</ListItemIconLeft>
|
@@ -39,7 +86,7 @@ const {
|
|
39
86
|
}
|
40
87
|
|
41
88
|
{
|
42
|
-
|
89
|
+
loadingAnimationType === 'icon-right' && (
|
43
90
|
<ListItemIconRight loading={loading} duration={duration} {...restProps}>
|
44
91
|
<slot />
|
45
92
|
</ListItemIconRight>
|
@@ -47,7 +94,7 @@ const {
|
|
47
94
|
}
|
48
95
|
|
49
96
|
{
|
50
|
-
|
97
|
+
loadingAnimationType === 'breath' && (
|
51
98
|
<ListItemBreath loading={loading} duration={duration} {...restProps}>
|
52
99
|
<slot />
|
53
100
|
</ListItemBreath>
|
@@ -55,7 +102,7 @@ const {
|
|
55
102
|
}
|
56
103
|
|
57
104
|
{
|
58
|
-
|
105
|
+
loadingAnimationType === 'pulse' && (
|
59
106
|
<ListItemPulse loading={loading} duration={duration} {...restProps}>
|
60
107
|
<slot />
|
61
108
|
</ListItemPulse>
|
@@ -63,7 +110,7 @@ const {
|
|
63
110
|
}
|
64
111
|
|
65
112
|
{
|
66
|
-
|
113
|
+
loadingAnimationType === 'glow' && (
|
67
114
|
<ListItemGlow loading={loading} duration={duration} {...restProps}>
|
68
115
|
<slot />
|
69
116
|
</ListItemGlow>
|
@@ -1,87 +1,88 @@
|
|
1
1
|
import type { INavItem } from './nav';
|
2
|
+
import type { ImageMetadata } from 'astro';
|
2
3
|
|
3
4
|
export interface IHeaderProps {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
/**
|
6
|
+
* 侧边栏是否默认展开
|
7
|
+
* @default false
|
8
|
+
*/
|
9
|
+
defaultSidebarOpen?: boolean;
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
height?: '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
11
|
+
/**
|
12
|
+
* 完整的 astro:i18n 模块(启用语言切换时需要)
|
13
|
+
*/
|
14
|
+
astroI18n?: any;
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
/**
|
17
|
+
* 导航栏高度
|
18
|
+
* @default "md"
|
19
|
+
*/
|
20
|
+
height?: '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
/**
|
23
|
+
* Logo图片元数据
|
24
|
+
*/
|
25
|
+
logo?: ImageMetadata;
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
/**
|
28
|
+
* Logo 链接地址
|
29
|
+
* @default "/"
|
30
|
+
*/
|
31
|
+
logoHref?: string;
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
/**
|
34
|
+
* 导航菜单项
|
35
|
+
*/
|
36
|
+
navItems?: INavItem[];
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
/**
|
39
|
+
* 导航栏位置
|
40
|
+
* @default "start"
|
41
|
+
*/
|
42
|
+
navPosition?: 'start' | 'center' | 'end';
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
/**
|
45
|
+
* 水平内边距
|
46
|
+
* @default "md"
|
47
|
+
*/
|
48
|
+
paddingHorizontal?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
/**
|
51
|
+
* 垂直内边距
|
52
|
+
* @default "md"
|
53
|
+
*/
|
54
|
+
paddingVertical?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
/**
|
57
|
+
* 圆角大小
|
58
|
+
* @default "md"
|
59
|
+
*/
|
60
|
+
rounded?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
/**
|
63
|
+
* 是否显示侧边栏切换按钮
|
64
|
+
* @default false
|
65
|
+
*/
|
66
|
+
showSidebarToggle?: boolean;
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
/**
|
69
|
+
* 是否显示主题切换按钮
|
70
|
+
* @default false
|
71
|
+
*/
|
72
|
+
showThemeSwitcher?: boolean;
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
/**
|
75
|
+
* 社交媒体链接列表
|
76
|
+
*/
|
77
|
+
socialLinks?: Array<{
|
78
|
+
name: string;
|
79
|
+
url: string;
|
80
|
+
icon: any;
|
81
|
+
}>;
|
81
82
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
/**
|
84
|
+
* 是否固定在顶部
|
85
|
+
* @default true
|
86
|
+
*/
|
87
|
+
sticky?: boolean;
|
87
88
|
}
|
@@ -5,67 +5,67 @@ import type { IMetaProps } from './meta';
|
|
5
5
|
import type { ISidebarProps } from './sidebar';
|
6
6
|
|
7
7
|
export interface IAppLayoutProps {
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
/**
|
9
|
+
* 是否显示侧边栏
|
10
|
+
* @default true
|
11
|
+
*/
|
12
|
+
showSidebar?: boolean;
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
/**
|
15
|
+
* 是否显示页眉
|
16
|
+
* @default true
|
17
|
+
*/
|
18
|
+
showHeader?: boolean;
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
/**
|
21
|
+
* 是否显示页脚
|
22
|
+
* @default true
|
23
|
+
*/
|
24
|
+
showFooter?: boolean;
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
/**
|
27
|
+
* 自定义头部内容
|
28
|
+
*/
|
29
|
+
head?: any;
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
/**
|
32
|
+
* 自定义头部内容
|
33
|
+
*/
|
34
|
+
headerConfig: IHeaderProps;
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
/**
|
37
|
+
* 侧边栏配置
|
38
|
+
*/
|
39
|
+
sidebarConfig: ISidebarProps;
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
/**
|
42
|
+
* 主内容配置
|
43
|
+
*/
|
44
|
+
mainContentConfig: IMainContentProps;
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
/**
|
47
|
+
* 页面类名
|
48
|
+
*/
|
49
|
+
class?: string;
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
/**
|
52
|
+
* 类名列表
|
53
|
+
*/
|
54
|
+
'class:list'?: any;
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
/**
|
57
|
+
* 调试模式,显示各个部分的边框
|
58
|
+
* @default false
|
59
|
+
*/
|
60
|
+
debug?: boolean;
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
/**
|
63
|
+
* 元数据配置
|
64
|
+
*/
|
65
|
+
metaConfig: IMetaProps;
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
/**
|
68
|
+
* 页脚相关配置
|
69
|
+
*/
|
70
|
+
footerConfig: IFooterProps;
|
71
71
|
}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import type { ImageMetadata } from 'astro';
|
2
|
+
|
1
3
|
export interface IMetaProps {
|
2
4
|
title: string;
|
3
5
|
description: string;
|
@@ -41,7 +43,7 @@ export interface IMetaProps {
|
|
41
43
|
/**
|
42
44
|
* 自定义头部内容
|
43
45
|
*/
|
44
|
-
head?:
|
46
|
+
head?: any;
|
45
47
|
|
46
48
|
/**
|
47
49
|
* 页面类名
|
package/package.json
CHANGED
@@ -1,121 +0,0 @@
|
|
1
|
-
import { getRelativeLocaleUrl } from 'astro:i18n';
|
2
|
-
import type { AstroGlobal } from 'astro';
|
3
|
-
import { cosyLogger } from '../cosy';
|
4
|
-
|
5
|
-
// 默认语言
|
6
|
-
export const DEFAULT_LANGUAGE = 'en';
|
7
|
-
|
8
|
-
/**
|
9
|
-
* 语言工具模块
|
10
|
-
*
|
11
|
-
* 提供语言相关的工具函数,用于多语言支持
|
12
|
-
*/
|
13
|
-
export class LanguageUtil {
|
14
|
-
static getRelativeLink(locale: string, astro: AstroGlobal): string {
|
15
|
-
const debug = false;
|
16
|
-
const currentLocale = astro.currentLocale;
|
17
|
-
const originalPath = astro.originPathname;
|
18
|
-
const result = getRelativeLocaleUrl(
|
19
|
-
locale,
|
20
|
-
originalPath.replaceAll('/' + currentLocale, '')
|
21
|
-
);
|
22
|
-
|
23
|
-
if (debug) {
|
24
|
-
cosyLogger.debug(
|
25
|
-
`getRelativeLink: locale=${locale}, currentPath=${originalPath}, currentLocale=${currentLocale}, result=${result}`
|
26
|
-
);
|
27
|
-
}
|
28
|
-
|
29
|
-
return result;
|
30
|
-
}
|
31
|
-
|
32
|
-
static getLanguageName(code: string | undefined): string {
|
33
|
-
switch (code) {
|
34
|
-
case 'en':
|
35
|
-
return 'English';
|
36
|
-
case 'zh-cn':
|
37
|
-
return '简体中文';
|
38
|
-
case 'zh':
|
39
|
-
return '中文';
|
40
|
-
case undefined:
|
41
|
-
default:
|
42
|
-
return 'not known';
|
43
|
-
}
|
44
|
-
}
|
45
|
-
|
46
|
-
/**
|
47
|
-
* 获取当前语言
|
48
|
-
* @param astro Astro全局对象
|
49
|
-
* @returns 当前应使用的语言代码
|
50
|
-
*/
|
51
|
-
static getCurrentLanguage(astro: AstroGlobal): string {
|
52
|
-
// 尝试从Astro全局对象中获取语言
|
53
|
-
const astroLang = astro.currentLocale;
|
54
|
-
if (astroLang) {
|
55
|
-
return astroLang;
|
56
|
-
}
|
57
|
-
// 尝试从URL中获取语言
|
58
|
-
const urlLang = this.getLanguageFromURL(astro.url.pathname);
|
59
|
-
if (urlLang) {
|
60
|
-
return urlLang;
|
61
|
-
}
|
62
|
-
|
63
|
-
// 尝试从浏览器设置中获取语言
|
64
|
-
const browserLang = this.getLanguageFromBrowser();
|
65
|
-
if (browserLang) {
|
66
|
-
return browserLang;
|
67
|
-
}
|
68
|
-
|
69
|
-
// 尝试从Astro全局对象中获取语言
|
70
|
-
const preferredLocale = astro.preferredLocale;
|
71
|
-
if (preferredLocale) {
|
72
|
-
return preferredLocale;
|
73
|
-
}
|
74
|
-
|
75
|
-
// 如果无法检测,返回默认语言
|
76
|
-
return DEFAULT_LANGUAGE;
|
77
|
-
}
|
78
|
-
|
79
|
-
/**
|
80
|
-
* 从URL中提取语言代码
|
81
|
-
* @param url 当前URL
|
82
|
-
* @returns 从URL中提取的语言代码,如果无法提取则返回undefined
|
83
|
-
*/
|
84
|
-
private static getLanguageFromURL(url: string): string | undefined {
|
85
|
-
let currentUrl = url;
|
86
|
-
|
87
|
-
if (currentUrl === undefined) {
|
88
|
-
if (typeof window === 'undefined') return undefined;
|
89
|
-
currentUrl = window.location.href;
|
90
|
-
}
|
91
|
-
|
92
|
-
// 尝试从路径中提取语言代码
|
93
|
-
// 例如: /zh-cn/components/button
|
94
|
-
const pathMatch = currentUrl.match(/^\/([\w-]+)\//);
|
95
|
-
if (pathMatch) {
|
96
|
-
return pathMatch[1];
|
97
|
-
}
|
98
|
-
|
99
|
-
// 尝试从查询参数中提取语言代码
|
100
|
-
// 例如: ?lang=zh-cn
|
101
|
-
const urlParams = new URLSearchParams(currentUrl.split('?')[1]);
|
102
|
-
const langParam = urlParams.get('lang');
|
103
|
-
if (langParam) {
|
104
|
-
return langParam;
|
105
|
-
}
|
106
|
-
|
107
|
-
return undefined;
|
108
|
-
}
|
109
|
-
|
110
|
-
/**
|
111
|
-
* 从浏览器设置中获取首选语言
|
112
|
-
* @returns 从浏览器设置中获取的语言代码,如果无法获取则返回undefined
|
113
|
-
*/
|
114
|
-
private static getLanguageFromBrowser(): string | undefined {
|
115
|
-
if (typeof navigator === 'undefined') return undefined;
|
116
|
-
|
117
|
-
// 获取浏览器语言
|
118
|
-
const browserLang = navigator.language.toLowerCase();
|
119
|
-
return browserLang;
|
120
|
-
}
|
121
|
-
}
|