diff --git a/components.d.ts b/components.d.ts index 8f4e3f15..adba0235 100644 --- a/components.d.ts +++ b/components.d.ts @@ -10,9 +10,11 @@ declare module 'vue' { DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default'] ErrorHeader: typeof import('./src/@core/components/ErrorHeader.vue')['default'] ExistIcon: typeof import('./src/@core/components/ExistIcon.vue')['default'] + LoadingBanner: typeof import('./src/@core/components/LoadingBanner.vue')['default'] MoreBtn: typeof import('./src/@core/components/MoreBtn.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + StatIcon: typeof import('./src/@core/components/StatIcon.vue')['default'] ThemeSwitcher: typeof import('./src/@core/components/ThemeSwitcher.vue')['default'] } } diff --git a/package.json b/package.json index db1fd896..a436ed85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moviepilot", - "version": "1.8.1-3", + "version": "1.8.3-1", "private": true, "bin": "dist/service.js", "scripts": { @@ -81,6 +81,7 @@ "@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue-jsx": "^3.0.0", "autoprefixer": "^10.4.14", + "dayjs": "^1.11.10", "eslint": "^9.0.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-import-resolver-typescript": "^3.5.1", diff --git a/src/@core/components/DialogCloseBtn.vue b/src/@core/components/DialogCloseBtn.vue index 1bd12764..bc661431 100644 --- a/src/@core/components/DialogCloseBtn.vue +++ b/src/@core/components/DialogCloseBtn.vue @@ -1,9 +1,9 @@ diff --git a/src/@core/components/LoadingBanner.vue b/src/@core/components/LoadingBanner.vue new file mode 100644 index 00000000..8e97558c --- /dev/null +++ b/src/@core/components/LoadingBanner.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/@core/components/StatIcon.vue b/src/@core/components/StatIcon.vue new file mode 100644 index 00000000..e5c82df3 --- /dev/null +++ b/src/@core/components/StatIcon.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/@core/utils/formatters.ts b/src/@core/utils/formatters.ts index 342de36b..ae696ba6 100644 --- a/src/@core/utils/formatters.ts +++ b/src/@core/utils/formatters.ts @@ -1,5 +1,12 @@ +import dayjs from 'dayjs' +import relativeTime from 'dayjs/plugin/relativeTime' +import ZH_CN from 'dayjs/locale/zh-cn' + import { isToday } from './index' +dayjs.extend(relativeTime) +dayjs.locale(ZH_CN) + export function avatarText(value: string) { if (!value) return '' @@ -19,7 +26,7 @@ export function kFormatter(num: number) { * Format and return date in Humanize format * Intl docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/format * Intl Constructor: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat - * @param {String} value date to format + * @param {string} value date to format * @param {Intl.DateTimeFormatOptions} formatting Intl object to format with */ export function formatDate(value: string, formatting: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric' }) { @@ -32,8 +39,8 @@ export function formatDate(value: string, formatting: Intl.DateTimeFormatOptions /** * Return short human friendly month representation of date * Can also convert date to only time if date is of today (Better UX) - * @param {String} value date to format - * @param {Boolean} toTimeForCurrentDay Shall convert to time if day is today/current + * @param {string} value date to format + * @param {boolean} toTimeForCurrentDay Shall convert to time if day is today/current */ export function formatDateToMonthShort(value: string, toTimeForCurrentDay = true) { const date = new Date(value) @@ -107,7 +114,7 @@ export function formatBytes(bytes: number, decimals = 2) { const i = Math.floor(Math.log(bytes) / Math.log(k)) - return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}` + return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}` } // 格式化剧集列表 @@ -150,20 +157,21 @@ export function formatEp(nums: number[]): string { // 将yyyy-mm-dd hh:mm:ss转换为时间差,如:1小时前,1天前 export function formatDateDifference(dateString: string): string { - const date = new Date(dateString) - const currentDate = new Date() - const timeDifference = currentDate.getTime() - date.getTime() - const secondsDifference = Math.floor(timeDifference / 1000) - const minutesDifference = Math.floor(secondsDifference / 60) - const hoursDifference = Math.floor(minutesDifference / 60) - const daysDifference = Math.floor(hoursDifference / 24) + // const timeDifference = dayjs().millisecond() - dayjs(dateString).millisecond() + // const secondsDifference = Math.floor(timeDifference / 1000) + // const minutesDifference = Math.floor(secondsDifference / 60) + // const hoursDifference = Math.floor(minutesDifference / 60) + // const daysDifference = Math.floor(hoursDifference / 24) - if (daysDifference > 0) - return `${daysDifference}天前` - else if (hoursDifference > 0) - return `${hoursDifference}小时前` - else if (minutesDifference > 0) - return `${minutesDifference}分钟前` - else - return '刚刚' + // if (daysDifference > 0) + // return `${daysDifference}天前` + // else if (hoursDifference > 0) + // return `${hoursDifference}小时前` + // else if (minutesDifference > 0) + // return `${minutesDifference}分钟前` + // else + // return '刚刚' + if (!dateString) + return '' + return dayjs(dateString).fromNow() } diff --git a/src/@core/utils/index.ts b/src/@core/utils/index.ts index e74b1717..2c97f674 100644 --- a/src/@core/utils/index.ts +++ b/src/@core/utils/index.ts @@ -33,12 +33,16 @@ export function isToday(date: Date) { ) } -// 计算时间差,返回xx天/xx小时/xx分钟/xx秒 +/** + * 计算时间差,返回xx天/xx小时/xx分钟/xx秒 + * + * @deprecated 建议使用:@core/utils/formatters.ts formatDateDifference + */ export function calculateTimeDifference(inputTime: string): string { if (!inputTime) return '' - const inputDate = new Date(inputTime) + const inputDate = new Date(inputTime.replaceAll(/-/g, '/')) const currentDate = new Date() const timeDifference = currentDate.getTime() - inputDate.getTime() @@ -70,7 +74,7 @@ export function calculateTimeDiff(inputTime: string): string { return '' // 使用当前时区 - const inputDate = new Date(inputTime) + const inputDate = new Date(inputTime.replaceAll(/-/g, '/')) const currentDate = new Date() const timeDifference = currentDate.getTime() - inputDate.getTime() diff --git a/src/App.vue b/src/App.vue index 55cea95f..1f367415 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,13 +1,9 @@ diff --git a/src/api/types.ts b/src/api/types.ts index 32ab43f6..ff6ebb33 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -88,6 +88,9 @@ export interface Subscribe { // 保存目录 save_path: string + + // 时间 + date: string } // 历史记录 @@ -505,6 +508,33 @@ export interface Site { is_active: boolean } +// 站点使用统计 +export interface SiteStatistic { + + // 站点主域名Key + domain?: string + + // 成功次数 + success?: number + + // 失败次数 + fail?: number + + // 平均耗时 + seconds?: number + + // 最后一次访问状态 0-成功 1-失败 + lst_state?: number + + // 最后访问时间 + lst_mod_date?: string + + // 耗时记录 JSON + note?: string + + +} + // 正在下载 export interface DownloadingInfo { diff --git a/src/components/FileBrowser.vue b/src/components/FileBrowser.vue index a3ec9e19..e4c7e9a3 100644 --- a/src/components/FileBrowser.vue +++ b/src/components/FileBrowser.vue @@ -1,9 +1,8 @@ diff --git a/src/components/cards/CardStatisticsHorizontal.vue b/src/components/cards/CardStatisticsHorizontal.vue deleted file mode 100644 index 6f083fd6..00000000 --- a/src/components/cards/CardStatisticsHorizontal.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/src/components/cards/CardStatisticsVertical.vue b/src/components/cards/CardStatisticsVertical.vue deleted file mode 100644 index 67ad117a..00000000 --- a/src/components/cards/CardStatisticsVertical.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - diff --git a/src/components/cards/CardStatisticsWithImages.vue b/src/components/cards/CardStatisticsWithImages.vue deleted file mode 100644 index 1d2094df..00000000 --- a/src/components/cards/CardStatisticsWithImages.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - - - diff --git a/src/components/cards/LibraryCard.vue b/src/components/cards/LibraryCard.vue index 43ee7d98..f7c8d5b4 100644 --- a/src/components/cards/LibraryCard.vue +++ b/src/components/cards/LibraryCard.vue @@ -56,7 +56,7 @@ function getImgUrl(url: string) { if (!url) return getDefaultImage() else - return `${import.meta.env.VITE_API_BASE_URL}system/img/0/${encodeURIComponent(url).replace(/%2F/g, '/')}` + return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}` } // 根据多张图片生成媒体库封面 @@ -68,7 +68,7 @@ async function drawImages(imageList: string[]) { // 为所有图片添加system/img前缀 for (let i = 0; i < IMAGES.length; i++) - IMAGES[i] = `${import.meta.env.VITE_API_BASE_URL}system/img/0/${encodeURIComponent(IMAGES[i]).replace(/%2F/g, '/')}` + IMAGES[i] = `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(IMAGES[i])}` // canvas const canvas = canvasRef.value diff --git a/src/components/cards/MediaCard.vue b/src/components/cards/MediaCard.vue index 05155b98..663eefdc 100644 --- a/src/components/cards/MediaCard.vue +++ b/src/components/cards/MediaCard.vue @@ -1,7 +1,7 @@ @@ -440,7 +447,7 @@ function getYear(airDate: string) { 'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering, 'ring-1': isImageLoaded, }" - @click.stop="goMediaDetail" + @click.stop="goMediaDetail(hover.isHovering)" > - = computed(() => { return noImage // 如果是网络图片则使用代理后返回 if (props.plugin?.plugin_icon?.startsWith('http')) - return `${import.meta.env.VITE_API_BASE_URL}system/img/1/${encodeURIComponent(props.plugin?.plugin_icon).replace(/%2F/g, '/')}` + return `${import.meta.env.VITE_API_BASE_URL}system/img/1?imgurl=${encodeURIComponent(props.plugin?.plugin_icon)}` return `./plugin_icon/${props.plugin?.plugin_icon}` }) diff --git a/src/components/cards/PluginCard.vue b/src/components/cards/PluginCard.vue index 20f1a3e9..90e90094 100644 --- a/src/components/cards/PluginCard.vue +++ b/src/components/cards/PluginCard.vue @@ -223,7 +223,7 @@ const iconPath: Ref = computed(() => { return noImage // 如果是网络图片则使用代理后返回 if (props.plugin?.plugin_icon?.startsWith('http')) - return `${import.meta.env.VITE_API_BASE_URL}system/img/1/${encodeURIComponent(props.plugin?.plugin_icon).replace(/%2F/g, '/')}` + return `${import.meta.env.VITE_API_BASE_URL}system/img/1?imgurl=${encodeURIComponent(props.plugin?.plugin_icon)}` return `./plugin_icon/${props.plugin?.plugin_icon}` }) @@ -482,7 +482,7 @@ watch(() => props.plugin?.has_update, (newHasUpdate, oldHasUpdate) => { :title="`${props.plugin?.plugin_name} - 配置`" class="rounded-t" > - + props.plugin?.has_update, (newHasUpdate, oldHasUpdate) => { :title="`${props.plugin?.plugin_name}`" class="rounded-t" > - + { if (imageLoadError.value) return noImage const image = props.media?.image || '' - return `${import.meta.env.VITE_API_BASE_URL}system/img/0/${encodeURIComponent(image).replace(/%2F/g, '/')}` + return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(image)}` }) // 跳转播放 -function goPlay() { - if (props.media?.link) +function goPlay(isHovering = false) { + if (props.media?.link && isHovering) window.open(props.media?.link, '_blank') } @@ -53,7 +53,7 @@ function goPlay() { 'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering, 'ring-1': isImageLoaded, }" - @click.stop="goPlay" + @click.stop="goPlay(hover.isHovering)" > import type { PropType } from 'vue' import { useToast } from 'vue-toast-notification' -import SiteAddEditForm from '../form/SiteAddEditForm.vue' +import SiteAddEditDialog from '../dialog/SiteAddEditDialog.vue' import SiteTorrentTable from '../table/SiteTorrentTable.vue' import { requiredValidator } from '@/@validators' import api from '@/api' -import type { Site } from '@/api/types' +import type { Site, SiteStatistic } from '@/api/types' import ExistIcon from '@core/components/ExistIcon.vue' +import { isNullOrEmptyObject } from '@/@core/utils' // 输入参数 const cardProps = defineProps({ @@ -58,6 +59,9 @@ const userPwForm = ref({ code: '', }) +// 站点使用统计 +const siteStats = ref({}) + // 查询站点图标 async function getSiteIcon() { try { @@ -82,6 +86,18 @@ async function testSite() { testButtonText.value = '测试' testButtonDisable.value = false + + getSiteStats() + } + catch (error) { + console.error(error) + } +} + +// 查询站点使用统计 +async function getSiteStats() { + try { + siteStats.value = (await api.get(`site/statistic/${cardProps.site?.domain}`)) } catch (error) { console.error(error) @@ -140,9 +156,33 @@ function openSitePage() { window.open(cardProps.site?.url, '_blank') } +// 根据站点状态显示不同的状态图标 +const statColor = computed(() => { + if (isNullOrEmptyObject(siteStats.value)){ + return 'secondary' + } + if (siteStats.value?.lst_state == 1){ + return 'error' + } + else if (siteStats.value?.lst_state == 0){ + if (!siteStats.value?.seconds) + return 'secondary' + if (siteStats.value?.seconds >= 5) + return 'warning' + return 'success' + } +}) + +// 监听resourceDialog,如果为false则重新查询站点使用统计 +watch(resourceDialog, (value) => { + if (!value) + getSiteStats() +}) + // 装载时查询站点图标 onMounted(() => { getSiteIcon() + getSiteStats() }) @@ -163,16 +203,17 @@ onMounted(() => { + {{ cardProps.site?.name }} - {{ cardProps.site?.url }} + {{ cardProps.site?.url }} - + { - import { useToast } from 'vue-toast-notification' -import SubscribeEditForm from '../form/SubscribeEditForm.vue' -import { calculateTimeDifference } from '@/@core/utils' +import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue' +import { formatDateDifference } from '@/@core/utils/formatters' import { formatSeason } from '@/@core/utils/formatters' import api from '@/api' import type { Subscribe } from '@/api/types' @@ -26,11 +26,9 @@ const subscribeEditDialog = ref(false) // 上一次更新时间 const lastUpdateText = ref( - `${ - props.media?.last_update - ? `${calculateTimeDifference(props.media?.last_update || '')}前` - : '' - }`, + props.media && props.media.last_update + ? formatDateDifference(props.media.last_update) + : '', ) // 图片加载完成响应 @@ -284,7 +282,7 @@ const dropdownItems = ref([ /> - {
- + +// 输入参数 +const props = defineProps({ + title: String, +}) + +// 定义事件 +const emit = defineEmits(['update:modelValue', 'close']) + +// 代码 +const codeString = ref('') + +// 导入 +function handleImport() { + emit('update:modelValue', codeString.value) + emit('close') +} + + + diff --git a/src/components/form/ReorganizeForm.vue b/src/components/dialog/ReorganizeDialog.vue similarity index 99% rename from src/components/form/ReorganizeForm.vue rename to src/components/dialog/ReorganizeDialog.vue index c4fcc8f4..fa851c86 100644 --- a/src/components/form/ReorganizeForm.vue +++ b/src/components/dialog/ReorganizeDialog.vue @@ -1,6 +1,6 @@ + +