Compare commits

..

18 Commits

Author SHA1 Message Date
jxxghp
356ffddb1c release 2024-07-09 08:00:11 +08:00
jxxghp
de69be7c4e Merge pull request #168 from s0urcelab/main 2024-07-09 06:28:30 +08:00
s0urce
e962f555ae fix: issue #167 2024-07-09 02:56:48 +08:00
jxxghp
1987246585 Merge pull request #166 from JavaZeroo/main 2024-07-08 19:05:23 +08:00
JavaZero
393264f66b 实现根据操作系统动态显示不同的搜索快捷键提示 2024-07-08 13:51:00 +08:00
jxxghp
9b50020b3b Merge pull request #165 from BrettDean/main 2024-07-05 15:26:31 +08:00
Dean
5e5545fe01 优化"最近入库"数量显示 2024-07-05 15:19:50 +08:00
jxxghp
0e8da35b0a fix bug 2024-07-01 10:55:46 +08:00
jxxghp
4d2cf73330 fix https://github.com/jxxghp/MoviePilot/issues/2471 2024-07-01 10:20:50 +08:00
jxxghp
5df89f2ce4 release 2024-06-28 10:48:20 +08:00
jxxghp
045c0b4c0c fix ui 2024-06-28 10:47:50 +08:00
jxxghp
8b4ffa0795 Update SubscribeCard.vue 2024-06-28 10:03:28 +08:00
jxxghp
14359a37ae Update package.json 2024-06-26 16:14:24 +08:00
jxxghp
a8e4a1c2e0 Merge pull request #162 from jxxghp/main
fix bugs
2024-06-24 12:59:49 +08:00
jxxghp
9048d181af Merge branch 'dev' into main 2024-06-24 12:59:22 +08:00
jxxghp
1cb02994bf fix 登录失败的提示信息 2024-06-24 11:51:39 +08:00
jxxghp
6fad85e957 feat:仪表盘不活跃时不刷新 && 网盘整理联动刮削 2024-06-24 09:13:22 +08:00
jxxghp
db9b2ee6b3 init 2024-06-23 09:35:00 +08:00
16 changed files with 135 additions and 39 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "moviepilot", "name": "moviepilot",
"version": "1.9.8", "version": "1.9.11",
"private": true, "private": true,
"bin": "dist/service.js", "bin": "dist/service.js",
"scripts": { "scripts": {

View File

@@ -32,7 +32,7 @@ api.interceptors.response.use(
router.push('/login') router.push('/login')
} }
return Promise.reject(new Error(error)) return Promise.reject(error)
}, },
) )

View File

@@ -182,6 +182,7 @@ watch(
'outline-dashed outline-1': props.media?.best_version && imageLoaded, 'outline-dashed outline-1': props.media?.best_version && imageLoaded,
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering, 'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
}" }"
min-height="170"
@click="editSubscribeDialog" @click="editSubscribeDialog"
> >
<div class="me-n3 absolute top-1 right-2"> <div class="me-n3 absolute top-1 right-2">

View File

@@ -350,7 +350,7 @@ onMounted(() => {
</VCol> </VCol>
</VRow> </VRow>
<VRow> <VRow>
<VCol cols="12" md="6" v-if="props.storage == 'local'"> <VCol cols="12" md="6">
<VSwitch <VSwitch
v-model="transferForm.scrape" v-model="transferForm.scrape"
label="刮削元数据" label="刮削元数据"

View File

@@ -116,7 +116,7 @@ const transferItems = ref<FileItem[]>([])
// 大小控制 // 大小控制
const scrollStyle = computed(() => { const scrollStyle = computed(() => {
return appMode return appMode.value
? 'height: calc(100vh - 15.5rem - env(safe-area-inset-bottom) - 3.5rem)' ? 'height: calc(100vh - 15.5rem - env(safe-area-inset-bottom) - 3.5rem)'
: 'height: calc(100vh - 14.5rem - env(safe-area-inset-bottom)' : 'height: calc(100vh - 14.5rem - env(safe-area-inset-bottom)'
}) })

View File

@@ -12,12 +12,18 @@ import MediaServerLibrary from '@/views/dashboard/MediaServerLibrary.vue'
import MediaServerPlaying from '@/views/dashboard/MediaServerPlaying.vue' import MediaServerPlaying from '@/views/dashboard/MediaServerPlaying.vue'
import DashboardRender from '@/components/render/DashboardRender.vue' import DashboardRender from '@/components/render/DashboardRender.vue'
import { isNullOrEmptyObject } from '@/@core/utils' import { isNullOrEmptyObject } from '@/@core/utils'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
// 仪表板配置 // 仪表板配置
config: Object as PropType<DashboardItem>, config: Object as PropType<DashboardItem>,
// 刷新状态 // 刷新状态
refreshStatus: Boolean, refreshStatus: Boolean,
// 是否允许刷新数据
allowRefresh: {
type: Boolean,
default: true,
},
}) })
const emit = defineEmits(['update:refreshStatus']) const emit = defineEmits(['update:refreshStatus'])
@@ -32,10 +38,10 @@ onUnmounted(() => {
<AnalyticsStorage v-if="config?.id === 'storage'" /> <AnalyticsStorage v-if="config?.id === 'storage'" />
<AnalyticsMediaStatistic v-else-if="config?.id === 'mediaStatistic'" /> <AnalyticsMediaStatistic v-else-if="config?.id === 'mediaStatistic'" />
<AnalyticsWeeklyOverview v-else-if="config?.id === 'weeklyOverview'" /> <AnalyticsWeeklyOverview v-else-if="config?.id === 'weeklyOverview'" />
<AnalyticsSpeed v-else-if="config?.id === 'speed'" /> <AnalyticsSpeed v-else-if="config?.id === 'speed'" :allowRefresh="props.allowRefresh" />
<AnalyticsScheduler v-else-if="config?.id === 'scheduler'" /> <AnalyticsScheduler v-else-if="config?.id === 'scheduler'" :allowRefresh="props.allowRefresh" />
<AnalyticsCpu v-else-if="config?.id === 'cpu'" /> <AnalyticsCpu v-else-if="config?.id === 'cpu'" :allowRefresh="props.allowRefresh" />
<AnalyticsMemory v-else-if="config?.id === 'memory'" /> <AnalyticsMemory v-else-if="config?.id === 'memory'" :allowRefresh="props.allowRefresh" />
<MediaServerLibrary v-else-if="config?.id === 'library'" /> <MediaServerLibrary v-else-if="config?.id === 'library'" />
<MediaServerPlaying v-else-if="config?.id === 'playing'" /> <MediaServerPlaying v-else-if="config?.id === 'playing'" />
<MediaServerLatest v-else-if="config?.id === 'latest'" /> <MediaServerLatest v-else-if="config?.id === 'latest'" />

View File

@@ -2,6 +2,7 @@
import * as Mousetrap from 'mousetrap' import * as Mousetrap from 'mousetrap'
import SearchBarView from '@/views/system/SearchBarView.vue' import SearchBarView from '@/views/system/SearchBarView.vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { ref, computed } from 'vue'
const display = useDisplay() const display = useDisplay()
@@ -15,6 +16,14 @@ function openSearchDialog() {
searchDialog.value = true searchDialog.value = true
return false return false
} }
// 检测操作系统是否是Mac
function isMac() {
return navigator.platform.toUpperCase().indexOf('MAC') >= 0
}
// 计算属性:根据操作系统显示不同的按键提示
const metaKey = computed(() => (isMac() ? '⌘+K' : 'Ctrl+K'))
</script> </script>
<template> <template>
@@ -25,12 +34,13 @@ function openSearchDialog() {
</IconBtn> </IconBtn>
<span v-if="display.lgAndUp.value" class="flex align-center text-disabled ms-2" @click="openSearchDialog"> <span v-if="display.lgAndUp.value" class="flex align-center text-disabled ms-2" @click="openSearchDialog">
<span class="me-3">搜索</span> <span class="me-3">搜索</span>
<span class="meta-key">K</span> <span class="meta-key">{{ metaKey }}</span>
</span> </span>
</div> </div>
<!-- 搜索弹窗 --> <!-- 搜索弹窗 -->
<SearchBarView v-model="searchDialog" v-if="searchDialog" @close="searchDialog = false" /> <SearchBarView v-model="searchDialog" v-if="searchDialog" @close="searchDialog = false" />
</template> </template>
<style type="scss" scoped> <style type="scss" scoped>
.meta-key { .meta-key {
border: thin solid rgba(var(--v-border-color), var(--v-border-opacity)); border: thin solid rgba(var(--v-border-color), var(--v-border-opacity));

View File

@@ -19,6 +19,9 @@ const superUser = store.state.auth.superUser
// 是否拉升高度 // 是否拉升高度
const isElevated = ref(true) const isElevated = ref(true)
// 是否发送请求的总开关
const isRequest = ref(true)
// 计算属性,控制是否拉升高度 // 计算属性,控制是否拉升高度
const elevatedConf = controlledComputed( const elevatedConf = controlledComputed(
() => isElevated.value, () => isElevated.value,
@@ -269,7 +272,8 @@ async function getPluginDashboard(id: string, key: string) {
if ( if (
res.attrs?.refresh && res.attrs?.refresh &&
pluginDashboardRefreshStatus.value[pluginDashboardId] && pluginDashboardRefreshStatus.value[pluginDashboardId] &&
enableConfig.value[pluginDashboardId] enableConfig.value[pluginDashboardId] &&
isRequest.value
) { ) {
// 清除之前的定时器 // 清除之前的定时器
if (refreshTimers.value[pluginDashboardId]) { if (refreshTimers.value[pluginDashboardId]) {
@@ -298,6 +302,14 @@ onBeforeMount(async () => {
await loadDashboardConfig() await loadDashboardConfig()
getPluginDashboardMeta() getPluginDashboardMeta()
}) })
onActivated(async () => {
isRequest.value = true
})
onDeactivated(() => {
isRequest.value = false
})
</script> </script>
<template> <template>
@@ -314,6 +326,7 @@ onBeforeMount(async () => {
<VCol v-if="enableConfig[buildPluginDashboardId(element.id, element.key)] && element.cols" v-bind:="element.cols"> <VCol v-if="enableConfig[buildPluginDashboardId(element.id, element.key)] && element.cols" v-bind:="element.cols">
<DashboardElement <DashboardElement
:config="element" :config="element"
:allow-refresh="isRequest"
v-model:refreshStatus="pluginDashboardRefreshStatus[buildPluginDashboardId(element.id, element.key)]" v-model:refreshStatus="pluginDashboardRefreshStatus[buildPluginDashboardId(element.id, element.key)]"
/> />
</VCol> </VCol>

View File

@@ -4,6 +4,15 @@ import { useTheme } from 'vuetify'
import { hexToRgb } from '@layouts/utils' import { hexToRgb } from '@layouts/utils'
import api from '@/api' import api from '@/api'
// 输入参数
const props = defineProps({
// 是否允许刷新数据
allowRefresh: {
type: Boolean,
default: true,
},
})
const vuetifyTheme = useTheme() const vuetifyTheme = useTheme()
const currentTheme = controlledComputed( const currentTheme = controlledComputed(
@@ -94,6 +103,7 @@ const chartOptions = controlledComputed(
// 调用API接口获取最新CPU使用率 // 调用API接口获取最新CPU使用率
async function getCpuUsage() { async function getCpuUsage() {
if (!props.allowRefresh) return
try { try {
// 请求数据 // 请求数据
current.value = (await api.get('dashboard/cpu')) ?? 0 current.value = (await api.get('dashboard/cpu')) ?? 0

View File

@@ -5,6 +5,15 @@ import { hexToRgb } from '@layouts/utils'
import api from '@/api' import api from '@/api'
import { formatBytes } from '@/@core/utils/formatters' import { formatBytes } from '@/@core/utils/formatters'
// 输入参数
const props = defineProps({
// 是否允许刷新数据
allowRefresh: {
type: Boolean,
default: true,
},
})
const vuetifyTheme = useTheme() const vuetifyTheme = useTheme()
const currentTheme = controlledComputed( const currentTheme = controlledComputed(
@@ -100,6 +109,7 @@ const chartOptions = controlledComputed(
// 调用API接口获取最新内存使用量 // 调用API接口获取最新内存使用量
async function getMemorgUsage() { async function getMemorgUsage() {
if (!props.allowRefresh) return
try { try {
// 请求数据 // 请求数据
;[usedMemory.value, memoryUsage.value] = await api.get('dashboard/memory') ;[usedMemory.value, memoryUsage.value] = await api.get('dashboard/memory')

View File

@@ -2,6 +2,15 @@
import api from '@/api' import api from '@/api'
import type { ScheduleInfo } from '@/api/types' import type { ScheduleInfo } from '@/api/types'
// 输入参数
const props = defineProps({
// 是否允许刷新数据
allowRefresh: {
type: Boolean,
default: true,
},
})
// 定时服务列表 // 定时服务列表
const schedulerList = ref<ScheduleInfo[]>([]) const schedulerList = ref<ScheduleInfo[]>([])
@@ -10,6 +19,9 @@ let refreshTimer: NodeJS.Timeout | null = null
// 调用API加载定时服务列表 // 调用API加载定时服务列表
async function loadSchedulerList() { async function loadSchedulerList() {
if (!props.allowRefresh) {
return
}
try { try {
const res: ScheduleInfo[] = await api.get('dashboard/schedule') const res: ScheduleInfo[] = await api.get('dashboard/schedule')

View File

@@ -3,6 +3,15 @@ import { formatFileSize } from '@/@core/utils/formatters'
import api from '@/api' import api from '@/api'
import type { DownloaderInfo } from '@/api/types' import type { DownloaderInfo } from '@/api/types'
// 输入参数
const props = defineProps({
// 是否允许刷新数据
allowRefresh: {
type: Boolean,
default: true,
},
})
// 定时器 // 定时器
let refreshTimer: NodeJS.Timeout | null = null let refreshTimer: NodeJS.Timeout | null = null
@@ -35,6 +44,10 @@ const infoItems = ref([
// 调用API查询下载器数据 // 调用API查询下载器数据
async function loadDownloaderInfo() { async function loadDownloaderInfo() {
if (!props.allowRefresh) {
return
}
try { try {
const res: DownloaderInfo = await api.get('dashboard/downloader') const res: DownloaderInfo = await api.get('dashboard/downloader')

View File

@@ -80,7 +80,13 @@ const options = controlledComputed(
fontSize: '12px', fontSize: '12px',
}, },
formatter: (value: number) => (value > 999 ? (value / 1000).toFixed(0) : value), formatter: (value: number) => {
if (value > 999) {
return (value / 1000).toFixed(1) + 'k'
} else {
return value.toString()
}
},
}, },
}, },
} }

View File

@@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Context } from '@/api/types' import type { Context } from '@/api/types'
import TorrentItem from '@/components/cards/TorrentItem.vue' import TorrentItem from '@/components/cards/TorrentItem.vue'
import { list } from 'postcss'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
// 显示器宽度 // 显示器宽度
@@ -35,6 +36,13 @@ const filterForm = reactive({
resolution: [] as string[], resolution: [] as string[],
}) })
// 列表样式
const listStyle = computed(() => {
return appMode.value
? 'height: calc(100vh - 7.5rem - env(safe-area-inset-bottom) - 3.5rem)'
: 'height: calc(100vh - 6.5rem - env(safe-area-inset-bottom)'
})
// 排序字段 // 排序字段
const sortField = ref('default') const sortField = ref('default')
@@ -167,14 +175,7 @@ onMounted(() => {
</VListItem> </VListItem>
</VList> </VList>
<VList v-if="dataList.length !== 0" lines="three" class="rounded p-0 torrent-list-vscroll shadow-lg"> <VList v-if="dataList.length !== 0" lines="three" class="rounded p-0 torrent-list-vscroll shadow-lg">
<VVirtualScroll <VVirtualScroll :items="dataList" :style="listStyle">
:items="dataList"
:style="
appMode
? 'height: calc(100vh - 7.5rem - env(safe-area-inset-bottom) - 3.5rem)'
: 'height: calc(100vh - 6.5rem - env(safe-area-inset-bottom)'
"
>
<template #default="{ item }"> <template #default="{ item }">
<TorrentItem :torrent="item" :key="`${item.torrent_info.page_url}`" /> <TorrentItem :torrent="item" :key="`${item.torrent_info.page_url}`" />
</template> </template>
@@ -182,11 +183,7 @@ onMounted(() => {
</VList> </VList>
</VCol> </VCol>
<VCol xl="2" md="3" v-if="display.mdAndUp.value"> <VCol xl="2" md="3" v-if="display.mdAndUp.value">
<VList <VList lines="one" class="rounded shadow-lg" :style="listStyle">
lines="one"
class="rounded shadow-lg"
style="block-size: calc(100vh - 6.5rem - env(safe-area-inset-bottom))"
>
<VListSubheader> 排序 </VListSubheader> <VListSubheader> 排序 </VListSubheader>
<VListItem> <VListItem>
<VChipGroup column v-model="sortField"> <VChipGroup column v-model="sortField">

View File

@@ -124,6 +124,12 @@ const TransferDict: { [key: string]: string } = {
rclone_move: 'Rclone移动', rclone_move: 'Rclone移动',
} }
const tableStyle = computed(() => {
return appMode.value
? 'height: calc(100vh - 15.5rem - env(safe-area-inset-bottom) - 3.5rem)'
: 'height: calc(100vh - 14.5rem - env(safe-area-inset-bottom)'
})
// 分页提示 // 分页提示
const pageTip = computed(() => { const pageTip = computed(() => {
const begin = itemsPerPage.value * (currentPage.value - 1) + 1 const begin = itemsPerPage.value * (currentPage.value - 1) + 1
@@ -142,12 +148,19 @@ const totalPage = computed(() => {
// 切换页签和搜索词 // 切换页签和搜索词
watch( watch(
[() => currentPage.value, () => itemsPerPage.value, () => search.value], [() => currentPage.value, () => itemsPerPage.value],
debounce(async () => { debounce(async () => {
reloadPage() reloadPage()
}, 1000), }, 1000),
) )
watch(
[() => search.value],
debounce(async () => {
reloadPage(true)
}, 1000),
)
// 获取订阅列表数据 // 获取订阅列表数据
async function fetchData(page = currentPage.value, count = itemsPerPage.value) { async function fetchData(page = currentPage.value, count = itemsPerPage.value) {
loading.value = true loading.value = true
@@ -329,7 +342,7 @@ function addUrlQuery(url: string, name: string, value: any) {
} }
// 重载页面 // 重载页面
function reloadPage() { function reloadPage(resetPage = false) {
let url = '/history' let url = '/history'
if (search.value) { if (search.value) {
url = addUrlQuery(url, 'search', search.value) url = addUrlQuery(url, 'search', search.value)
@@ -338,7 +351,7 @@ function reloadPage() {
url = addUrlQuery(url, 'itemsPerPage', itemsPerPage.value) url = addUrlQuery(url, 'itemsPerPage', itemsPerPage.value)
} }
if (currentPage.value) { if (currentPage.value) {
url = addUrlQuery(url, 'currentPage', currentPage.value) url = addUrlQuery(url, 'currentPage', resetPage ? 1 : currentPage.value)
} }
router.push(url) router.push(url)
} }
@@ -394,11 +407,7 @@ onMounted(fetchData)
show-select show-select
loading-text="加载中..." loading-text="加载中..."
hover hover
:style=" :style="tableStyle"
appMode
? 'height: calc(100vh - 15.5rem - env(safe-area-inset-bottom) - 3.5rem)'
: 'height: calc(100vh - 14.5rem - env(safe-area-inset-bottom)'
"
> >
<template #item.title="{ item }"> <template #item.title="{ item }">
<div class="d-flex align-center"> <div class="d-flex align-center">

View File

@@ -1,12 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import api from '@/api' import api from '@/api'
import type { Plugin, Subscribe } from '@/api/types' import type { Plugin, Subscribe } from '@/api/types'
import { SystemNavMenus, UserfulMenus, PluginTabs, SettingTabs } from '@/router/menu' import { SystemNavMenus, UserfulMenus, SettingTabs } from '@/router/menu'
import { NavMenu } from '@/@layouts/types' import { NavMenu } from '@/@layouts/types'
import store from '@/store'
// 路由 // 路由
const router = useRouter() const router = useRouter()
// 超级用户
const superUser = store.state.auth.superUser
// 当前用户名
const userName = store.state.auth.userName
// 定义事件 // 定义事件
const emit = defineEmits(['close']) const emit = defineEmits(['close'])
@@ -74,6 +81,7 @@ function getMenus(): NavMenu[] {
// 匹配的菜单列表 // 匹配的菜单列表
const matchedMenuItems = computed(() => { const matchedMenuItems = computed(() => {
if (!searchWord.value) return [] if (!searchWord.value) return []
if (!superUser) return []
const lowerWord = (searchWord.value as string).toLowerCase() const lowerWord = (searchWord.value as string).toLowerCase()
const menuItems = getMenus() const menuItems = getMenus()
if (menuItems) if (menuItems)
@@ -104,6 +112,7 @@ async function fetchInstalledPlugins() {
// 区配的插件列表 // 区配的插件列表
const matchedPluginItems = computed(() => { const matchedPluginItems = computed(() => {
if (!searchWord.value) return [] if (!searchWord.value) return []
if (!superUser) return []
const lowerWord = (searchWord.value as string).toLowerCase() const lowerWord = (searchWord.value as string).toLowerCase()
return pluginItems.value.filter((item: Plugin) => { return pluginItems.value.filter((item: Plugin) => {
if (!item.plugin_name && !item.plugin_desc) return false if (!item.plugin_name && !item.plugin_desc) return false
@@ -114,7 +123,7 @@ const matchedPluginItems = computed(() => {
// 所有订阅数据 // 所有订阅数据
const SubscribeItems = ref<Subscribe[]>([]) const SubscribeItems = ref<Subscribe[]>([])
// 获取电影订阅列表数据 // 获取订阅列表数据
async function fetchSubscribes() { async function fetchSubscribes() {
try { try {
SubscribeItems.value = await api.get('subscribe/') SubscribeItems.value = await api.get('subscribe/')
@@ -128,7 +137,7 @@ const matchedSubscribeItems = computed(() => {
if (!searchWord.value) return [] if (!searchWord.value) return []
const lowerWord = (searchWord.value as string).toLowerCase() const lowerWord = (searchWord.value as string).toLowerCase()
return SubscribeItems.value.filter((item: Subscribe) => { return SubscribeItems.value.filter((item: Subscribe) => {
return item.name.toLowerCase().includes(lowerWord) return (item.name.toLowerCase().includes(lowerWord) && (superUser || userName === item.username)) || false
}) })
}) })
@@ -287,7 +296,7 @@ onMounted(() => {
</VListItem> </VListItem>
</template> </template>
</VHover> </VHover>
<VHover> <VHover v-if="superUser">
<template #default="hover"> <template #default="hover">
<VListItem prepend-icon="mdi-history" link v-bind="hover.props" @click="searchHistory"> <VListItem prepend-icon="mdi-history" link v-bind="hover.props" @click="searchHistory">
<VListItemTitle class="break-words whitespace-break-spaces"> <VListItemTitle class="break-words whitespace-break-spaces">
@@ -382,7 +391,7 @@ onMounted(() => {
</div> </div>
</VCol> </VCol>
</VRow> </VRow>
<VRow> <VRow v-if="superUser">
<VCol cols="12" md="6"> <VCol cols="12" md="6">
<p class="custom-letter-spacing text-sm text-disabled text-uppercase py-2 px-4 mb-0">常用功能</p> <p class="custom-letter-spacing text-sm text-disabled text-uppercase py-2 px-4 mb-0">常用功能</p>
<VList lines="one"> <VList lines="one">