Refactor page titles and dialog close buttons across multiple views

- Replaced instances of `DialogCloseBtn` with `VDialogCloseBtn` in TransferHistoryView, AccountSettingAbout, AccountSettingSystem, and UserProfileView for consistency.
- Introduced a new component `PageContentTitle` to standardize page titles in SiteCardListView, SubscribeListView, UserListView, and WorkflowListView, improving layout and readability.
- Added keyword filtering functionality in SubscribeListView and SubscribeShareView to enhance user experience during searches.
- Added a new site logo image to the assets.
This commit is contained in:
jxxghp
2025-04-15 13:23:56 +08:00
parent 65ebdb61d0
commit e97d815dc3
63 changed files with 347 additions and 232 deletions

View File

@@ -47,6 +47,9 @@ const loading = ref(false)
// 已安装插件列表
const dataList = ref<Plugin[]>([])
// 过滤后的已安装插件列表
const filteredDataList = ref<Plugin[]>([])
// 未安装插件列表
const uninstalledList = ref<Plugin[]>([])
@@ -101,6 +104,25 @@ const filterForm = reactive({
repo: [] as string[],
})
// 计算过滤表单是否全部为空
const isFilterFormEmpty = computed(() => {
return (
filterForm.name === '' &&
filterForm.author.length === 0 &&
filterForm.label.length === 0 &&
filterForm.repo.length === 0
)
})
// 插件过滤条件
const installedFilter = ref('')
// 已安装插件过滤窗口
const filterInstalledPluginDialog = ref(false)
// 插件市场过滤窗口
const filterMarketPluginDialog = ref(false)
// 作者过滤项
const authorFilterOptions = ref<string[]>([])
// 标签过滤项
@@ -380,6 +402,13 @@ function handleRepoUrl(url: string | undefined) {
return url.replace('https://github.com/', '').replace('https://raw.githubusercontent.com/', '')
}
// 监测dataList变化或installedFilter变化时更新filteredDataList
watch([dataList, installedFilter], () => {
filteredDataList.value = dataList.value.filter(item => {
return item.plugin_name?.toLowerCase().includes(installedFilter.value.toLowerCase())
})
})
// 加载时获取数据
onMounted(async () => {
await loadPluginOrderConfig()
@@ -399,10 +428,112 @@ onMounted(async () => {
<div>
<VHeaderTab :items="PluginTabs" v-model="activeTab">
<template #append>
<VMenu
v-if="activeTab === '我的插件'"
v-model="filterInstalledPluginDialog"
width="20rem"
:close-on-content-click="false"
>
<template #activator="{ props }">
<VBtn
icon="mdi-filter-cog-outline"
variant="text"
:color="installedFilter ? 'primary' : 'gray'"
size="default"
class="settings-icon-button"
v-bind="props"
/>
</template>
<VCard>
<VCardItem>
<VCardTitle>
<VIcon icon="mdi-filter-cog-outline" class="mr-2" />
筛选插件
</VCardTitle>
<VDialogCloseBtn @click="filterInstalledPluginDialog = false" />
</VCardItem>
<VCardText>
<VTextField v-model="installedFilter" label="名称" density="comfortable" clearable />
</VCardText>
</VCard>
</VMenu>
<VMenu
v-if="activeTab === '插件市场'"
v-model="filterMarketPluginDialog"
width="25rem"
:close-on-content-click="false"
>
<template #activator="{ props }">
<VBtn
icon="mdi-filter-cog-outline"
variant="text"
:color="isFilterFormEmpty ? 'gray' : 'primary'"
size="default"
class="settings-icon-button"
v-bind="props"
/>
</template>
<VCard>
<VCardItem>
<VCardTitle>
<VIcon icon="mdi-filter-cog-outline" class="mr-2" />
筛选插件
</VCardTitle>
<VDialogCloseBtn @click="filterMarketPluginDialog = false" />
</VCardItem>
<VCardText>
<!-- 过滤表单 -->
<div v-if="isAppMarketLoaded">
<VRow>
<VCol cols="12" md="6">
<VTextField v-model="filterForm.name" density="comfortable" label="名称" clearable />
</VCol>
<VCol v-if="authorFilterOptions.length > 0" cols="12" md="6">
<VSelect
v-model="filterForm.author"
:items="authorFilterOptions"
density="comfortable"
chips
label="作者"
multiple
clearable
/>
</VCol>
<VCol v-if="labelFilterOptions.length > 0" cols="12" md="6">
<VSelect
v-model="filterForm.label"
:items="labelFilterOptions"
density="comfortable"
chips
label="标签"
multiple
clearable
/>
</VCol>
<VCol v-if="repoFilterOptions.length > 0" cols="12" md="6">
<VSelect
v-model="filterForm.repo"
:items="repoFilterOptions"
density="comfortable"
chips
label="插件库"
multiple
clearable
/>
</VCol>
<VCol v-if="repoFilterOptions.length > 0" cols="12" md="6">
<VSelect v-model="activeSort" :items="sortOptions" density="comfortable" label="排序" />
</VCol>
</VRow>
</div>
</VCardText>
</VCard>
</VMenu>
<VBtn
v-if="activeTab === '插件市场'"
icon="mdi-store-cog"
variant="text"
color="primary"
color="gray"
size="default"
class="settings-icon-button"
@click="MarketSettingDialog = true"
@@ -415,10 +546,11 @@ onMounted(async () => {
<VWindowItem value="我的插件">
<transition name="fade-slide" appear>
<div>
<VPageContentTitle v-if="installedFilter" :title="`筛选:${installedFilter}`" />
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<draggable
v-if="dataList.length > 0"
v-model="dataList"
v-if="filteredDataList.length > 0"
v-model="filteredDataList"
@end="savePluginOrder"
handle=".cursor-move"
item-key="id"
@@ -437,10 +569,12 @@ onMounted(async () => {
</template>
</draggable>
<NoDataFound
v-if="dataList.length === 0 && isRefreshed"
v-if="filteredDataList.length === 0 && isRefreshed"
error-code="404"
error-title="没有安装插件"
error-description="点击右下角按钮前往插件市场安装插件"
error-title="没有数据"
:error-description="
installedFilter ? '没有搜索到相关内容请更换搜索关键词' : '请先前往插件市场安装插件'
"
/>
</div>
</transition>
@@ -450,50 +584,6 @@ onMounted(async () => {
<transition name="fade-slide" appear>
<div>
<LoadingBanner v-if="!isAppMarketLoaded" class="mt-12" />
<!-- 过滤表单 -->
<div v-if="isAppMarketLoaded" class="bg-transparent mb-3 shadow-none">
<VRow>
<VCol cols="6" md="">
<VTextField v-model="filterForm.name" density="compact" label="名称" clearable />
</VCol>
<VCol v-if="authorFilterOptions.length > 0" cols="6" md="">
<VSelect
v-model="filterForm.author"
:items="authorFilterOptions"
density="compact"
chips
label="作者"
multiple
clearable
/>
</VCol>
<VCol v-if="labelFilterOptions.length > 0" cols="6" md="">
<VSelect
v-model="filterForm.label"
:items="labelFilterOptions"
density="compact"
chips
label="标签"
multiple
clearable
/>
</VCol>
<VCol v-if="repoFilterOptions.length > 0" cols="6" md="">
<VSelect
v-model="filterForm.repo"
:items="repoFilterOptions"
density="compact"
chips
label="插件库"
multiple
clearable
/>
</VCol>
<VCol v-if="repoFilterOptions.length > 0" cols="6" md="">
<VSelect v-model="activeSort" :items="sortOptions" density="compact" label="排序" />
</VCol>
</VRow>
</div>
<div v-if="isAppMarketLoaded" class="grid gap-4 grid-plugin-card">
<template v-for="(data, index) in sortedUninstalledList" :key="`${data.id}_v${data.plugin_version}`">
<PluginAppCard :plugin="data" :count="PluginStatistics[data.id || '0']" @install="pluginInstalled" />
@@ -555,7 +645,7 @@ onMounted(async () => {
class="mx-1"
/>
</VToolbar>
<DialogCloseBtn @click="closeSearchDialog" />
<VDialogCloseBtn @click="closeSearchDialog" />
<VList v-if="filterPlugins.length > 0" lines="three">
<VVirtualScroll :items="filterPlugins">
<template #default="{ item }">

View File

@@ -690,7 +690,7 @@ onMounted(fetchData)
<!-- 底部弹窗 -->
<VBottomSheet v-model="deleteConfirmDialog" inset>
<VCard class="text-center rounded-t">
<DialogCloseBtn @click="deleteConfirmDialog = false" />
<VDialogCloseBtn @click="deleteConfirmDialog = false" />
<VCardTitle class="pe-10">
{{ confirmTitle }}
</VCardTitle>

View File

@@ -258,7 +258,7 @@ onMounted(() => {
<VDialog v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable>
<VCard>
<VCardItem>
<DialogCloseBtn @click="releaseDialog = false" />
<VDialogCloseBtn @click="releaseDialog = false" />
<VCardTitle>{{ releaseDialogTitle }} 变更日志</VCardTitle>
</VCardItem>
<VCardText v-html="releaseDialogBody" />

View File

@@ -554,7 +554,7 @@ onDeactivated(() => {
<VDialog v-if="advancedDialog" v-model="advancedDialog" scrollable max-width="60rem" persistent>
<VCard>
<VCardItem>
<DialogCloseBtn @click="advancedDialog = false" />
<VDialogCloseBtn @click="advancedDialog = false" />
<VCardTitle>高级设置</VCardTitle>
<VCardSubtitle>系统进阶设置特殊情况下才需要调整</VCardSubtitle>
</VCardItem>

View File

@@ -89,16 +89,7 @@ onActivated(() => {
<template>
<div class="card-list-container">
<!-- 页面标题 -->
<div class="my-3 md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1 mx-0 flex align-middle">
<h2
class="mb-3 ms-2 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-3xl sm:leading-9 md:mb-0"
data-testid="page-header"
>
<span class="text-moviepilot">站点管理</span>
</h2>
</div>
</div>
<VPageContentTitle title="站点管理" />
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<draggable
v-if="siteList.length > 0"

View File

@@ -19,11 +19,15 @@ const userStore = useUserStore()
const props = defineProps({
type: String,
subid: String,
keyword: String,
})
// 是否刷新过
let isRefreshed = ref(false)
// 搜索关键字
const keyword = ref(props.keyword || '')
// 顺序存储键值
const localOrderKey = props.type === '电影' ? 'MP_SUBSCRIBE_MOVIE_ORDER' : 'MP_SUBSCRIBE_TV_ORDER'
const orderRequestKey = props.type === '电影' ? 'SubscribeMovieOrder' : 'SubscribeTvOrder'
@@ -50,6 +54,10 @@ watch(dataList, () => {
const userName = userStore.userName
if (superUser) displayList.value = dataList.value.filter(data => data.type === props.type)
else displayList.value = dataList.value.filter(data => data.type === props.type && data.username === userName)
// 过滤关键字
if (keyword.value) {
displayList.value = displayList.value.filter(data => data.name.toLowerCase().includes(keyword.value.toLowerCase()))
}
// 排序
sortSubscribeOrder()
})
@@ -139,6 +147,7 @@ onActivated(async () => {
</script>
<template>
<VPageContentTitle v-if="keyword" :title="`筛选:${keyword}`" />
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<draggable
v-if="displayList.length > 0"
@@ -156,8 +165,8 @@ onActivated(async () => {
<NoDataFound
v-if="displayList.length === 0 && isRefreshed"
error-code="404"
error-title="没有订阅"
error-description="请通过搜索添加电影电视剧订阅"
error-title="没有数据"
:error-description="keyword ? '没有搜索到相关内容请更换搜索关键词' : '请通过搜索添加电影电视剧订阅'"
/>
<!-- 底部操作按钮 -->
<div v-if="isRefreshed">

View File

@@ -21,6 +21,9 @@ const apipath = 'subscribe/shares'
// 当前页码
const page = ref(1)
// 搜索关键字
const keyword = ref(props.keyword)
// 是否加载中
const loading = ref(false)
@@ -36,7 +39,7 @@ function getParams() {
let params = {
page: page.value,
count: 30,
name: props.keyword,
name: keyword.value,
}
return params
}
@@ -112,6 +115,7 @@ function removeData(id: number) {
</script>
<template>
<VPageContentTitle v-if="keyword" :title="`搜索:${keyword}`" />
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-hidden" @load="fetchData">
<template #loading />
@@ -126,7 +130,7 @@ function removeData(id: number) {
error-code="404"
error-title="没有数据"
:error-description="
keyword ? '没有搜索到相关内容请更换搜索关键词' : '未获取到分享订阅数据未开启数据分享或服务器无法连接'
keyword ? '没有搜索到相关内容请更换搜索关键词' : '未获取到分享订阅数据未开启数据分享或服务器无法连接'
"
/>
</VInfiniteScroll>

View File

@@ -54,22 +54,11 @@ onActivated(() => {
</script>
<template>
<!-- 页面标题 -->
<VPageContentTitle title="用户管理" />
<div class="card-list-container">
<!-- 页面标题 -->
<div class="my-3 md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1 mx-0 flex align-middle">
<h2
class="mb-3 ms-2 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-3xl sm:leading-9 md:mb-0"
data-testid="page-header"
>
<span class="text-moviepilot">用户管理</span>
</h2>
</div>
</div>
<!-- 加载中提示 -->
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<!-- 用户卡片网格 -->
<div v-if="allUsers.length > 0 && isRefreshed" class="grid gap- grid-user-card">
<!-- 普通用户卡片 -->

View File

@@ -57,7 +57,7 @@ const accountInfo = ref<User>({
is_otp: false,
permissions: {},
settings: {},
nickname: ''
nickname: '',
})
// 二维码信息
@@ -135,15 +135,15 @@ async function saveAccountInfo() {
}
accountInfo.value.password = newPassword.value
}
// 将nickname保存到settings中后端可以直接处理JSON对象
if (accountInfo.value.nickname) {
if (!accountInfo.value.settings) {
accountInfo.value.settings = {};
accountInfo.value.settings = {}
}
accountInfo.value.settings.nickname = accountInfo.value.nickname;
accountInfo.value.settings.nickname = accountInfo.value.nickname
}
const oldUserName = accountInfo.value.name
const oldAvatar = accountInfo.value.avatar
accountInfo.value.avatar = currentAvatar.value
@@ -151,10 +151,10 @@ async function saveAccountInfo() {
isSaving.value = true
try {
// 创建一个临时对象来保存用户数据,确保所有字段都会发送
const userData = { ...accountInfo.value };
const userData = { ...accountInfo.value }
const result: { [key: string]: any } = await api.put('user/', userData)
if (result.success) {
if (oldUserName !== currentUserName.value) {
$toast.success(`${oldUserName}】更名【${currentUserName.value}】,用户信息保存成功!`)
@@ -312,12 +312,7 @@ watch(
<VForm class="mt-6">
<VRow>
<VCol cols="12" md="6">
<VTextField
v-model="currentUserName"
density="comfortable"
readonly
label="用户名"
/>
<VTextField v-model="currentUserName" density="comfortable" readonly label="用户名" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="accountInfo.email" density="comfortable" clearable label="邮箱" type="email" />
@@ -430,7 +425,7 @@ watch(
<VDialog v-if="otpDialog" v-model="otpDialog" max-width="45rem" scrollable>
<!-- 开启双重验证弹窗内容 -->
<VCard>
<DialogCloseBtn @click="otpDialog = false" />
<VDialogCloseBtn @click="otpDialog = false" />
<VCardText>
<h4 class="text-h4 text-center mb-6 mt-5">登录双重验证</h4>
<h5 class="text-h5 font-weight-medium mb-2">身份验证器</h5>

View File

@@ -46,17 +46,8 @@ onActivated(() => {
<template>
<div>
<!-- 页面标题 -->
<div class="my-3 md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1 mx-0 flex align-middle">
<h2
class="mb-3 ms-2 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-3xl sm:leading-9 md:mb-0"
data-testid="page-header"
>
<span class="text-moviepilot">工作流</span>
</h2>
</div>
</div>
<VPageContentTitle title="工作流" />
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<VRow v-if="workflowList.length > 0" class="match-height">
<VCol cols="12" md="6" lg="4" v-for="item in workflowList" :key="item.id">