mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 16:16:42 +08:00
Improve plugin card statuses and compact market details
This commit is contained in:
@@ -39,6 +39,16 @@ const emit = defineEmits(['remove', 'save', 'actionDone'])
|
|||||||
// 多语言
|
// 多语言
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const hasCardRating = computed(() => (props.plugin?.rating_count || 0) > 0)
|
||||||
|
const hasCardStatus = computed(() => Boolean(props.plugin?.has_update) || hasCardRating.value)
|
||||||
|
const cardRatingValue = computed(() => Number(props.plugin?.average_rating || 0).toFixed(1))
|
||||||
|
const cardRatingSummary = computed(() =>
|
||||||
|
t('plugin.ratingSummary', {
|
||||||
|
rating: cardRatingValue.value,
|
||||||
|
count: props.plugin?.rating_count || 0,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
// 显示器宽度
|
// 显示器宽度
|
||||||
const display = useDisplay()
|
const display = useDisplay()
|
||||||
|
|
||||||
@@ -642,6 +652,7 @@ watch(
|
|||||||
<VCardText class="px-2 pt-2 pb-0">
|
<VCardText class="px-2 pt-2 pb-0">
|
||||||
<VCardTitle
|
<VCardTitle
|
||||||
class="text-white px-2 pb-0 text-lg text-shadow whitespace-nowrap overflow-hidden text-ellipsis"
|
class="text-white px-2 pb-0 text-lg text-shadow whitespace-nowrap overflow-hidden text-ellipsis"
|
||||||
|
:class="{ 'plugin-card__title--with-status': hasCardStatus }"
|
||||||
>
|
>
|
||||||
<VBadge dot inline :color="props.plugin?.state ? 'success' : 'secondary'" />
|
<VBadge dot inline :color="props.plugin?.state ? 'success' : 'secondary'" />
|
||||||
{{ props.plugin?.plugin_name }}
|
{{ props.plugin?.plugin_name }}
|
||||||
@@ -721,8 +732,22 @@ watch(
|
|||||||
</IconBtn>
|
</IconBtn>
|
||||||
</div>
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<div v-if="props.plugin?.has_update" class="me-n3 absolute top-0 right-5">
|
<div
|
||||||
<VIcon icon="mdi-new-box" class="text-white" />
|
v-if="props.plugin?.has_update"
|
||||||
|
class="plugin-card__status plugin-card__status--update"
|
||||||
|
:aria-label="t('plugin.hasUpdate')"
|
||||||
|
:title="t('plugin.hasUpdate')"
|
||||||
|
>
|
||||||
|
<VIcon icon="mdi-new-box" class="text-white" size="20" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="hasCardRating"
|
||||||
|
class="plugin-card__status plugin-card__status--rating"
|
||||||
|
:aria-label="cardRatingSummary"
|
||||||
|
:title="cardRatingSummary"
|
||||||
|
>
|
||||||
|
<VIcon icon="mdi-star" color="warning" size="16" />
|
||||||
|
<span>{{ cardRatingValue }}</span>
|
||||||
</div>
|
</div>
|
||||||
</VCard>
|
</VCard>
|
||||||
</div>
|
</div>
|
||||||
@@ -736,6 +761,28 @@ watch(
|
|||||||
inline-size: 100%;
|
inline-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.plugin-card__title--with-status {
|
||||||
|
padding-inline-end: 4rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card__status {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
inset-block-start: 0.625rem;
|
||||||
|
inset-inline-end: 0.625rem;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plugin-card__status--rating {
|
||||||
|
gap: 0.125rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.card-cover-blurred::before {
|
.card-cover-blurred::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
/* stylelint-disable-next-line property-no-vendor-prefix */
|
/* stylelint-disable-next-line property-no-vendor-prefix */
|
||||||
|
|||||||
@@ -122,4 +122,39 @@ describe('PluginCard about menu', () => {
|
|||||||
await waitFor(() => expect(replace).toHaveBeenCalledWith('https://github.com/example/plugins'))
|
await waitFor(() => expect(replace).toHaveBeenCalledWith('https://github.com/example/plugins'))
|
||||||
expect(popup.opener).toBeNull()
|
expect(popup.opener).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('prioritizes the update status over the rating status', async () => {
|
||||||
|
await renderWithProviders(PluginCard, {
|
||||||
|
props: {
|
||||||
|
plugin: { ...plugin, has_update: true, average_rating: 4.3, rating_count: 12 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('有更新')).toBeInTheDocument()
|
||||||
|
expect(screen.queryByLabelText('4.3 分,共 12 人评分')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows the rating status when no update is available', async () => {
|
||||||
|
await renderWithProviders(PluginCard, {
|
||||||
|
props: {
|
||||||
|
plugin: { ...plugin, has_update: false, average_rating: 4.3, rating_count: 12 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const ratingStatus = screen.getByLabelText('4.3 分,共 12 人评分')
|
||||||
|
|
||||||
|
expect(ratingStatus).toHaveClass('plugin-card__status--rating')
|
||||||
|
expect(ratingStatus).toHaveTextContent('4.3')
|
||||||
|
expect(screen.queryByLabelText('有更新')).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaves the status position empty without an update or rating', async () => {
|
||||||
|
await renderWithProviders(PluginCard, {
|
||||||
|
props: {
|
||||||
|
plugin: { ...plugin, has_update: false, average_rating: 0, rating_count: 0 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(document.querySelector('.plugin-card__status')).toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VDialog v-if="visible" v-model="visible" width="100%" max-width="32rem" max-height="90dvh" scrollable>
|
<VDialog v-if="visible" v-model="visible" width="100%" max-width="25rem" max-height="90dvh" scrollable>
|
||||||
<VCard class="plugin-market-detail">
|
<VCard class="plugin-market-detail">
|
||||||
<VDialogCloseBtn v-model="visible" />
|
<VDialogCloseBtn v-model="visible" />
|
||||||
<VCardText class="plugin-market-detail__content">
|
<VCardText class="plugin-market-detail__content">
|
||||||
@@ -359,7 +359,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.plugin-market-detail__content {
|
.plugin-market-detail__content {
|
||||||
padding: 2.25rem 1.5rem 1.5rem;
|
padding: 1.75rem 1.25rem 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail__header {
|
.plugin-market-detail__header {
|
||||||
@@ -375,16 +375,16 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.plugin-market-detail__title {
|
.plugin-market-detail__title {
|
||||||
max-inline-size: 100%;
|
max-inline-size: 100%;
|
||||||
margin: 1rem 0 0;
|
margin: 0.75rem 0 0;
|
||||||
font-size: 1.25rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail__description {
|
.plugin-market-detail__description {
|
||||||
max-inline-size: 28rem;
|
max-inline-size: 24rem;
|
||||||
margin: 0.35rem 0 0;
|
margin: 0.25rem 0 0;
|
||||||
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
@@ -394,16 +394,15 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.plugin-market-detail__metadata {
|
.plugin-market-detail__metadata {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.8rem;
|
gap: 0.625rem;
|
||||||
max-inline-size: 26rem;
|
margin: 1.125rem auto 0;
|
||||||
margin: 1.5rem auto 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail__metadata-row {
|
.plugin-market-detail__metadata-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 7rem minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.625rem;
|
||||||
min-inline-size: 0;
|
min-inline-size: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,6 +420,7 @@ onUnmounted(() => {
|
|||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
text-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail__author {
|
.plugin-market-detail__author {
|
||||||
@@ -448,15 +448,15 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail__warning {
|
.plugin-market-detail__warning {
|
||||||
margin-block-start: 1.25rem;
|
margin-block-start: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail-actions {
|
.plugin-market-detail-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
margin-block-start: 1.25rem;
|
margin-block-start: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail-actions__buttons {
|
.plugin-market-detail-actions__buttons {
|
||||||
@@ -478,13 +478,13 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail-user-rating {
|
.plugin-market-detail-user-rating {
|
||||||
margin-block-start: 1.5rem;
|
margin-block-start: 1.25rem;
|
||||||
padding-block-start: 1.25rem;
|
padding-block-start: 1rem;
|
||||||
border-block-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
border-block-start: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail-user-rating__title {
|
.plugin-market-detail-user-rating__title {
|
||||||
margin: 0 0 0.75rem;
|
margin: 0 0 0.5rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
@@ -495,7 +495,7 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
min-inline-size: 0;
|
min-inline-size: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,15 +504,6 @@ onUnmounted(() => {
|
|||||||
padding-inline: 1rem;
|
padding-inline: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-market-detail__metadata-row {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
gap: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-market-detail__metadata dt {
|
|
||||||
text-align: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plugin-market-detail-actions__buttons {
|
.plugin-market-detail-actions__buttons {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -521,11 +512,4 @@ onUnmounted(() => {
|
|||||||
inline-size: 100%;
|
inline-size: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width >= 480px) {
|
|
||||||
.plugin-market-detail-user-rating__controls {
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ describe('PluginMarketDetailDialog', () => {
|
|||||||
expect(screen.getByRole('button', { name: '提交评分' })).toBeInTheDocument()
|
expect(screen.getByRole('button', { name: '提交评分' })).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps long plugin details in aligned metadata rows', async () => {
|
it('keeps long plugin details in centered metadata rows', async () => {
|
||||||
const pluginDescription = '支持包含较长说明文字和换行内容的插件详情。\n第二行内容保持完整展示。'
|
const pluginDescription = '支持包含较长说明文字和换行内容的插件详情。\n第二行内容保持完整展示。'
|
||||||
const pluginAuthor = 'MoviePilot-Plugin-Author-With-A-Long-Name'
|
const pluginAuthor = 'MoviePilot-Plugin-Author-With-A-Long-Name'
|
||||||
|
|
||||||
@@ -148,7 +148,9 @@ describe('PluginMarketDetailDialog', () => {
|
|||||||
system_version: 'v2.15.0 or later',
|
system_version: 'v2.15.0 or later',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(await screen.findByText(pluginDescription)).toHaveClass('plugin-market-detail__description')
|
const description = document.querySelector('.plugin-market-detail__description')
|
||||||
|
|
||||||
|
expect(description).toHaveTextContent('支持包含较长说明文字和换行内容的插件详情。 第二行内容保持完整展示。')
|
||||||
expect(screen.getByRole('button', { name: pluginAuthor })).toHaveClass('plugin-market-detail__author')
|
expect(screen.getByRole('button', { name: pluginAuthor })).toHaveClass('plugin-market-detail__author')
|
||||||
|
|
||||||
const metadata = document.querySelector('.plugin-market-detail__metadata')
|
const metadata = document.querySelector('.plugin-market-detail__metadata')
|
||||||
|
|||||||
Reference in New Issue
Block a user