@coffic/cosy-ui 0.9.68 → 0.9.71
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/app.css +1 -1
- package/dist/src/components/button/buttonPropsBase.ts +1 -1
- package/dist/src/components/button/class-all.ts +21 -1
- package/dist/src/components/container/validate-sizing.ts +3 -4
- package/dist/src/components/link/class-all.ts +1 -3
- package/dist/src/components/link/linkPropsBase.ts +2 -0
- package/dist/src/utils/language.ts +2 -2
- package/dist/src/utils/link.ts +13 -13
- package/dist/src-astro/alert/Alert.astro +0 -3
- package/dist/src-astro/contact/Contact.astro +1 -1
- package/dist/src-astro/grid/Grid.astro +1 -1
- package/dist/src-astro/header/Header.astro +1 -1
- package/dist/src-astro/header/HeaderCenter.astro +1 -1
- package/dist/src-astro/header/MobileNav.astro +1 -1
- package/dist/src-astro/layout-basic/BaseLayout.astro +2 -2
- package/dist/src-astro/layout-dashboard/DashboardLayout.astro +1 -1
- package/dist/src-astro/link/types.ts +2 -4
- package/dist/src-vue/apple-phone/ApplePhone.vue +151 -133
- package/dist/src-vue/button/Button.vue +1 -0
- package/dist/src-vue/button/class.ts +21 -1
- package/dist/src-vue/image-display/ImageGrid.vue +1 -3
- package/dist/src-vue/image-display/ImageItem.vue +1 -3
- package/dist/src-vue/image-display/ImagePreview.vue +1 -3
- package/dist/src-vue/image-display/types.ts +1 -3
- package/dist/src-vue/key-catcher/KeyCatcher.vue +1 -1
- package/dist/src-vue/link/Link.vue +2 -0
- package/dist/src-vue/review/Review.vue +13 -3
- package/dist/src-vue/review/Reviews.vue +69 -0
- package/dist/src-vue/review/index.ts +5 -1
- package/dist/src-vue/review/props.ts +1 -10
- package/dist/src-vue/status-bar/StatusBarItem.vue +1 -3
- package/dist/src-vue/utils/link.ts +14 -14
- package/package.json +2 -17
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import type { IButtonProps } from "./props";
|
|
2
2
|
import { getBaseButtonClasses } from "../../src/components/button/class-all";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* 将可能的对象形式的 class 转换为字符串
|
|
6
|
+
* @param className 类名,可以是字符串或对象
|
|
7
|
+
* @returns 字符串形式的类名
|
|
8
|
+
*/
|
|
9
|
+
function normalizeClass(className: string | object | undefined): string {
|
|
10
|
+
if (!className) return "";
|
|
11
|
+
if (typeof className === "string") return className;
|
|
12
|
+
if (typeof className === "object") {
|
|
13
|
+
return Object.entries(className)
|
|
14
|
+
.filter(([, value]) => value)
|
|
15
|
+
.map(([key]) => key)
|
|
16
|
+
.join(" ");
|
|
17
|
+
}
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
/**
|
|
5
22
|
* 计算 Button 组件的组合类名(Vue 版本)
|
|
6
23
|
* @param props Button 组件的属性
|
|
@@ -17,6 +34,9 @@ export function getButtonCombinedClassesVue(props: IButtonProps): string {
|
|
|
17
34
|
class: className,
|
|
18
35
|
} = props;
|
|
19
36
|
|
|
37
|
+
// 规范化 class 属性
|
|
38
|
+
const normalizedClass = normalizeClass(className);
|
|
39
|
+
|
|
20
40
|
// 使用共用的工具函数计算基础类名
|
|
21
41
|
const baseClasses = getBaseButtonClasses({
|
|
22
42
|
variant,
|
|
@@ -25,7 +45,7 @@ export function getButtonCombinedClassesVue(props: IButtonProps): string {
|
|
|
25
45
|
wide,
|
|
26
46
|
block,
|
|
27
47
|
loading,
|
|
28
|
-
class:
|
|
48
|
+
class: normalizedClass,
|
|
29
49
|
});
|
|
30
50
|
|
|
31
51
|
return baseClasses.filter(Boolean).join(" ");
|
|
@@ -14,9 +14,7 @@ interface IImageGridProps {
|
|
|
14
14
|
maxDisplay: number;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
(e: "imageClick", imageUrl: string): void;
|
|
19
|
-
}
|
|
17
|
+
type IImageGridEmits = (e: "imageClick", imageUrl: string) => void;
|
|
20
18
|
|
|
21
19
|
const props = defineProps<IImageGridProps>();
|
|
22
20
|
const emit = defineEmits<IImageGridEmits>();
|
|
@@ -12,9 +12,7 @@ interface IImageItemProps {
|
|
|
12
12
|
size: "sm" | "md" | "lg";
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
(e: "imageClick", imageUrl: string): void;
|
|
17
|
-
}
|
|
15
|
+
type IImageItemEmits = (e: "imageClick", imageUrl: string) => void;
|
|
18
16
|
|
|
19
17
|
const props = defineProps<IImageItemProps>();
|
|
20
18
|
const emit = defineEmits<IImageItemEmits>();
|
|
@@ -12,9 +12,7 @@ interface IImagePreviewProps {
|
|
|
12
12
|
showPreview: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
(e: "close"): void;
|
|
17
|
-
}
|
|
15
|
+
type IImagePreviewEmits = (e: "close") => void;
|
|
18
16
|
|
|
19
17
|
const props = defineProps<IImagePreviewProps>();
|
|
20
18
|
const emit = defineEmits<IImagePreviewEmits>();
|
|
@@ -38,7 +38,7 @@ const lastKey = ref<string | null>(null);
|
|
|
38
38
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
39
39
|
|
|
40
40
|
const props = defineProps<{ showKey?: boolean }>();
|
|
41
|
-
const emit = defineEmits<
|
|
41
|
+
const emit = defineEmits<(e: "globalKey", key: string) => void>();
|
|
42
42
|
|
|
43
43
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
44
44
|
if (
|
|
@@ -21,6 +21,8 @@ import { getLinkCombinedClassesVue } from "./class";
|
|
|
21
21
|
* @props {boolean} [fullWidth=false] - 是否占满宽度
|
|
22
22
|
* @props {boolean} [ghost=false] - 是否为幽灵按钮(需配合 btn 使用)
|
|
23
23
|
* @props {string} href - 链接地址(必需)
|
|
24
|
+
* @props {string} [hoverImage] - 悬停时显示的图片URL
|
|
25
|
+
* @props {string} [hoverImageAlt] - 悬停图片的替代文本
|
|
24
26
|
* @props {string} [icon] - 图标名称,支持所有可用的图标组件
|
|
25
27
|
* @props {string} [navigationType] - 导航类型(需配合 navigation 变体使用):previous、next
|
|
26
28
|
* @props {boolean} [noUnderline=true] - 是否移除下划线
|
|
@@ -46,12 +46,17 @@ const {
|
|
|
46
46
|
px,
|
|
47
47
|
pl,
|
|
48
48
|
pr,
|
|
49
|
+
shadow, // 添加 shadow 属性
|
|
49
50
|
width,
|
|
50
51
|
rounded,
|
|
51
52
|
background,
|
|
52
53
|
border,
|
|
53
54
|
borderColor,
|
|
54
|
-
|
|
55
|
+
subtitle,
|
|
56
|
+
title,
|
|
57
|
+
imageUrl,
|
|
58
|
+
href,
|
|
59
|
+
compact,
|
|
55
60
|
} = props;
|
|
56
61
|
|
|
57
62
|
// 生成星级评分
|
|
@@ -104,14 +109,19 @@ const formattedDate = computed(() => {
|
|
|
104
109
|
border,
|
|
105
110
|
borderColor,
|
|
106
111
|
shadow,
|
|
112
|
+
subtitle,
|
|
113
|
+
title: userName, // 将 userName 作为 title 传递给 Card 组件
|
|
114
|
+
imageUrl,
|
|
115
|
+
href,
|
|
116
|
+
compact,
|
|
107
117
|
}"
|
|
108
118
|
:class="className"
|
|
109
119
|
:class:list="classList">
|
|
110
120
|
<div class="cosy:flex cosy:items-start cosy:gap-4">
|
|
111
121
|
<Avatar
|
|
112
122
|
v-if="avatar"
|
|
113
|
-
:
|
|
114
|
-
:
|
|
123
|
+
:avatar="avatar"
|
|
124
|
+
:userName="userName"
|
|
115
125
|
size="md"
|
|
116
126
|
class="cosy:flex-shrink-0" />
|
|
117
127
|
<div class="cosy:flex-1">
|
|
@@ -133,3 +133,72 @@ const gridColumns = computed(() => {
|
|
|
133
133
|
return cols;
|
|
134
134
|
});
|
|
135
135
|
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<div :class="['cosy:w-full', className]" :class:list="classList">
|
|
139
|
+
<!-- 标题 -->
|
|
140
|
+
<Heading v-if="title" level="h2" class="cosy:mb-6">
|
|
141
|
+
{{ title }}
|
|
142
|
+
</Heading>
|
|
143
|
+
|
|
144
|
+
<!-- 统计信息 -->
|
|
145
|
+
<div
|
|
146
|
+
v-if="stats"
|
|
147
|
+
class="cosy:flex cosy:flex-col md:cosy:flex-row md:cosy:items-center md:cosy:justify-between cosy:gap-6 cosy:mb-8">
|
|
148
|
+
<div class="cosy:flex cosy:items-center cosy:gap-4">
|
|
149
|
+
<Text variant="h1" class="cosy:text-5xl cosy:font-bold">
|
|
150
|
+
{{ stats.averageRating.toFixed(1) }}
|
|
151
|
+
</Text>
|
|
152
|
+
<div>
|
|
153
|
+
<div class="cosy:flex cosy:items-center cosy:gap-1">
|
|
154
|
+
<SmartIcon
|
|
155
|
+
v-for="(star, index) in averageStarArray"
|
|
156
|
+
:key="index"
|
|
157
|
+
:name="
|
|
158
|
+
star.filled ? 'star-filled' : star.half ? 'star-half' : 'star'
|
|
159
|
+
"
|
|
160
|
+
class="cosy:w-5 cosy:h-5 cosy:text-yellow-400" />
|
|
161
|
+
</div>
|
|
162
|
+
<Text variant="small" class="cosy:text-gray-500">
|
|
163
|
+
{{ stats.totalReviews }} 条评价
|
|
164
|
+
</Text>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- 评分分布 -->
|
|
169
|
+
<div class="cosy:space-y-1">
|
|
170
|
+
<div
|
|
171
|
+
v-for="(count, index) in stats.ratingDistribution"
|
|
172
|
+
:key="index"
|
|
173
|
+
class="cosy:flex cosy:items-center cosy:gap-2">
|
|
174
|
+
<Text variant="small" class="cosy:w-8">{{ 5 - index }}星</Text>
|
|
175
|
+
<div class="cosy:flex-1 cosy:h-2 cosy:bg-gray-200 cosy:rounded-full">
|
|
176
|
+
<div
|
|
177
|
+
class="cosy:h-full cosy:bg-yellow-400 cosy:rounded-full"
|
|
178
|
+
:style="{
|
|
179
|
+
width:
|
|
180
|
+
stats.totalReviews > 0
|
|
181
|
+
? (count / stats.totalReviews) * 100 + '%'
|
|
182
|
+
: '0%',
|
|
183
|
+
}"></div>
|
|
184
|
+
</div>
|
|
185
|
+
<Text variant="small" class="cosy:w-8">{{ count }}</Text>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- 评价列表 -->
|
|
191
|
+
<Grid v-if="layout === 'grid'" v-bind="gridColumns" class="cosy:gap-6">
|
|
192
|
+
<Review
|
|
193
|
+
v-for="(review, index) in displayReviews"
|
|
194
|
+
:key="index"
|
|
195
|
+
v-bind="review" />
|
|
196
|
+
</Grid>
|
|
197
|
+
<div v-else class="cosy:space-y-6">
|
|
198
|
+
<Review
|
|
199
|
+
v-for="(review, index) in displayReviews"
|
|
200
|
+
:key="index"
|
|
201
|
+
v-bind="review" />
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</template>
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { default as Review } from "./Review.vue";
|
|
2
2
|
export { default as Reviews } from "./Reviews.vue";
|
|
3
|
-
export type {
|
|
3
|
+
export type {
|
|
4
|
+
IReviewProps as ReviewProps,
|
|
5
|
+
IReviewsProps as ReviewsProps,
|
|
6
|
+
IReviewData as ReviewData,
|
|
7
|
+
} from "./props";
|
|
@@ -10,16 +10,7 @@ import type { ICardProps } from "../card/props";
|
|
|
10
10
|
*/
|
|
11
11
|
export interface IReviewProps
|
|
12
12
|
extends IReviewPropsBase,
|
|
13
|
-
Omit<
|
|
14
|
-
ICardProps,
|
|
15
|
-
| keyof IReviewPropsBase
|
|
16
|
-
| "title"
|
|
17
|
-
| "subtitle"
|
|
18
|
-
| "imageUrl"
|
|
19
|
-
| "href"
|
|
20
|
-
| "compact"
|
|
21
|
-
| "shadow"
|
|
22
|
-
> {
|
|
13
|
+
Omit<ICardProps, keyof IReviewPropsBase> {
|
|
23
14
|
/**
|
|
24
15
|
* 自定义类名
|
|
25
16
|
*/
|
|
@@ -55,9 +55,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
55
55
|
active: false,
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
const emit = defineEmits<
|
|
59
|
-
(e: "click", event: MouseEvent): void;
|
|
60
|
-
}>();
|
|
58
|
+
const emit = defineEmits<(e: "click", event: MouseEvent) => void>();
|
|
61
59
|
|
|
62
60
|
const handleClick = (event: MouseEvent) => {
|
|
63
61
|
if (props.clickable) {
|
|
@@ -24,7 +24,7 @@ export class LinkUtil {
|
|
|
24
24
|
*/
|
|
25
25
|
static normalizeLanguage(lang: string): string {
|
|
26
26
|
const normalizedLang = lang.toLowerCase().replace("zh-CN", "zh-cn");
|
|
27
|
-
if (normalizedLang.length
|
|
27
|
+
if (normalizedLang.length === 0) {
|
|
28
28
|
console.error("lang is empty");
|
|
29
29
|
return "en";
|
|
30
30
|
}
|
|
@@ -32,7 +32,7 @@ export class LinkUtil {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
static getHomeLink(lang: string): string {
|
|
35
|
-
return `${
|
|
35
|
+
return `${LinkUtil.getBaseUrl}/${lang}`;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
static getLessonsLink(lang: string): string {
|
|
@@ -62,10 +62,10 @@ export class LinkUtil {
|
|
|
62
62
|
|
|
63
63
|
static getLessonLink(lang: string, lessonId: string): string {
|
|
64
64
|
if (lessonId.endsWith(lang)) {
|
|
65
|
-
return `${
|
|
65
|
+
return `${LinkUtil.getBaseUrl()}${lang}/lessons/${lessonId.replace(`${lang}`, "")}`;
|
|
66
66
|
} else {
|
|
67
67
|
const idWithoutLang = lessonId.replace(`${lang}/`, "");
|
|
68
|
-
return `${
|
|
68
|
+
return `${LinkUtil.getBaseUrl()}${lang}/lessons/${idWithoutLang}`;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -97,43 +97,43 @@ export class LinkUtil {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
static getMetaLink(lang: string, slug: string): string {
|
|
100
|
-
return `/${
|
|
100
|
+
return `/${LinkUtil.normalizeLanguage(lang)}/meta/${slug}`;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
static getSigninLink(lang: string): string {
|
|
104
|
-
return `/${
|
|
104
|
+
return `/${LinkUtil.normalizeLanguage(lang)}/signin`;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
static getAuthCallbackCookieLink(lang: string): string {
|
|
108
|
-
return `/${
|
|
108
|
+
return `/${LinkUtil.normalizeLanguage(lang)}/auth/callback_cookie`;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
static getAuthCallbackTokenLink(lang: string): string {
|
|
112
|
-
return `/${
|
|
112
|
+
return `/${LinkUtil.normalizeLanguage(lang)}/auth/callback_token`;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
static getAuthAccountLink(lang: string): string {
|
|
116
|
-
return `/${
|
|
116
|
+
return `/${LinkUtil.normalizeLanguage(lang)}/auth/account`;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
static getDashboardUrl(lang: string): string {
|
|
120
|
-
return `/${
|
|
120
|
+
return `/${LinkUtil.normalizeLanguage(lang)}/auth/dashboard`;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
static getAuthErrorLink(lang: string): string {
|
|
124
|
-
return `/${
|
|
124
|
+
return `/${LinkUtil.normalizeLanguage(lang)}/auth/error`;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
static getPrivacyLink(lang: string): string {
|
|
128
|
-
return
|
|
128
|
+
return LinkUtil.getMetaLink(lang, "privacy");
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
static getTermsLink(lang: string): string {
|
|
132
|
-
return
|
|
132
|
+
return LinkUtil.getMetaLink(lang, "terms");
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
static getAboutLink(lang: string): string {
|
|
136
|
-
return
|
|
136
|
+
return LinkUtil.getMetaLink(lang, "about");
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffic/cosy-ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.71",
|
|
4
4
|
"description": "An astro component library",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "nookery",
|
|
@@ -63,35 +63,20 @@
|
|
|
63
63
|
"shiki": "^3.9.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@astrojs/check": "^0.9.4",
|
|
67
66
|
"@astrojs/mdx": "^4.3.3",
|
|
68
67
|
"@astrojs/ts-plugin": "^1.10.4",
|
|
69
|
-
"@eslint/js": "^9.32.0",
|
|
70
68
|
"@tailwindcss/typography": "^0.5.16",
|
|
71
69
|
"@tailwindcss/vite": "^4.1.11",
|
|
72
70
|
"@tsconfig/node22": "^22.0.2",
|
|
73
|
-
"@types/chai": "^5.2.2",
|
|
74
|
-
"@types/eslint": "^9.6.1",
|
|
75
71
|
"@types/fs-extra": "^11.0.4",
|
|
76
|
-
"@types/mocha": "^10.0.10",
|
|
77
72
|
"@types/node": "^22.17.0",
|
|
78
|
-
"@types/react": "^19.1.9",
|
|
79
|
-
"@typescript-eslint/parser": "^8.38.0",
|
|
80
|
-
"astro": "^5.12.8",
|
|
81
|
-
"chai": "^4.5.0",
|
|
82
73
|
"daisyui": "^5.0.50",
|
|
83
|
-
"eslint": "^9.32.0",
|
|
84
|
-
"eslint-plugin-astro": "^1.3.1",
|
|
85
74
|
"globals": "^16.3.0",
|
|
86
|
-
"mocha": "^10.8.2",
|
|
87
|
-
"prettier": "^3.6.2",
|
|
88
|
-
"prettier-plugin-astro": "^0.14.1",
|
|
89
75
|
"rollup-plugin-copy": "^3.5.0",
|
|
90
76
|
"sharp": "^0.33.5",
|
|
91
77
|
"tailwindcss": "^4.1.11",
|
|
92
78
|
"tsx": "^4.20.3",
|
|
93
79
|
"typescript": "^5.9.2",
|
|
94
|
-
"
|
|
95
|
-
"vite": "^6.3.5"
|
|
80
|
+
"vite": "^7.1.10"
|
|
96
81
|
}
|
|
97
82
|
}
|